kopia lustrzana https://github.com/wagtail/wagtail
				
				
				
			Update oembed finder to use requests instead of urllib.request (#13102)
							rodzic
							
								
									462673c3b0
								
							
						
					
					
						commit
						4bb0bb24f5
					
				|  | @ -11,6 +11,7 @@ Changelog | ||||||
|  * Render listing buttons as template components (Sage Abdullah) |  * Render listing buttons as template components (Sage Abdullah) | ||||||
|  * Define default `GenericRelations` for `RevisionMixin` and `WorkflowMixin`, to avoid issues with deletion cascades (Sage Abdullah) |  * Define default `GenericRelations` for `RevisionMixin` and `WorkflowMixin`, to avoid issues with deletion cascades (Sage Abdullah) | ||||||
|  * Document and relocate the `init_new_page` signal (Maciek Baron) |  * Document and relocate the `init_new_page` signal (Maciek Baron) | ||||||
|  |  * Use `requests` to access oEmbed endpoints, for more robust SSL certificate handling (Matt Westcott) | ||||||
|  * Fix: Handle lazy translation strings as `preview_value` for `RichTextBlock` (Seb Corbin) |  * Fix: Handle lazy translation strings as `preview_value` for `RichTextBlock` (Seb Corbin) | ||||||
|  * Fix: Fix handling of newline-separated choices in form builder when using non-windows newline characters (Baptiste Mispelon) |  * Fix: Fix handling of newline-separated choices in form builder when using non-windows newline characters (Baptiste Mispelon) | ||||||
|  * Fix: Ensure `WAGTAILADMIN_LOGIN_URL` is respected when logging out of the admin (Antoine Rodriguez, Ramon de Jezus) |  * Fix: Ensure `WAGTAILADMIN_LOGIN_URL` is respected when logging out of the admin (Antoine Rodriguez, Ramon de Jezus) | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ depth: 1 | ||||||
|  * Define default `GenericRelations` for `RevisionMixin` and `WorkflowMixin`, to avoid issues with deletion cascades (Sage Abdullah) |  * Define default `GenericRelations` for `RevisionMixin` and `WorkflowMixin`, to avoid issues with deletion cascades (Sage Abdullah) | ||||||
|  * Update Twitter oembed provider to recognise x.com links (manu) |  * Update Twitter oembed provider to recognise x.com links (manu) | ||||||
|  * Document and relocate the `init_new_page` signal (Maciek Baron) |  * Document and relocate the `init_new_page` signal (Maciek Baron) | ||||||
|  |  * Use `requests` to access oEmbed endpoints, for more robust SSL certificate handling (Matt Westcott) | ||||||
| 
 | 
 | ||||||
| ### Bug fixes | ### Bug fixes | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -71,6 +71,7 @@ testing = [ | ||||||
|   "azure-mgmt-cdn>=12.0,<13.0", |   "azure-mgmt-cdn>=12.0,<13.0", | ||||||
|   "azure-mgmt-frontdoor>=1.0,<1.1", |   "azure-mgmt-frontdoor>=1.0,<1.1", | ||||||
|   "django-pattern-library>=0.7", |   "django-pattern-library>=0.7", | ||||||
|  |   "responses>=0.25,<1", | ||||||
|   # For coverage and PEP8 linting |   # For coverage and PEP8 linting | ||||||
|   "coverage>=3.7.0", |   "coverage>=3.7.0", | ||||||
|   "doc8==1.1.2", |   "doc8==1.1.2", | ||||||
|  |  | ||||||
|  | @ -1,11 +1,7 @@ | ||||||
| import json |  | ||||||
| import re | import re | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
| from urllib import request as urllib_request |  | ||||||
| from urllib.error import URLError |  | ||||||
| from urllib.parse import urlencode |  | ||||||
| from urllib.request import Request |  | ||||||
| 
 | 
 | ||||||
|  | import requests | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| 
 | 
 | ||||||
| from wagtail.embeds.exceptions import EmbedNotFoundException | from wagtail.embeds.exceptions import EmbedNotFoundException | ||||||
|  | @ -60,12 +56,12 @@ class OEmbedFinder(EmbedFinder): | ||||||
|             params["maxheight"] = max_height |             params["maxheight"] = max_height | ||||||
| 
 | 
 | ||||||
|         # Perform request |         # Perform request | ||||||
|         request = Request(endpoint + "?" + urlencode(params)) |  | ||||||
|         request.add_header("User-agent", "Mozilla/5.0") |  | ||||||
|         try: |         try: | ||||||
|             r = urllib_request.urlopen(request) |             r = requests.get( | ||||||
|             oembed = json.loads(r.read().decode("utf-8")) |                 endpoint, params=params, headers={"User-agent": "Mozilla/5.0"} | ||||||
|         except (URLError, json.decoder.JSONDecodeError): |             ) | ||||||
|  |             oembed = r.json() | ||||||
|  |         except requests.RequestException: | ||||||
|             raise EmbedNotFoundException |             raise EmbedNotFoundException | ||||||
| 
 | 
 | ||||||
|         # Convert photos into HTML |         # Convert photos into HTML | ||||||
|  |  | ||||||
|  | @ -5,11 +5,13 @@ import urllib.request | ||||||
| from unittest.mock import patch | from unittest.mock import patch | ||||||
| from urllib.error import HTTPError, URLError | from urllib.error import HTTPError, URLError | ||||||
| 
 | 
 | ||||||
|  | import responses | ||||||
| from django import template | from django import template | ||||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||||
| from django.test import TestCase, override_settings | from django.test import TestCase, override_settings | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from django.utils.timezone import make_aware, now | from django.utils.timezone import make_aware, now | ||||||
|  | from responses import matchers | ||||||
| 
 | 
 | ||||||
| from wagtail import blocks | from wagtail import blocks | ||||||
| from wagtail.embeds import oembed_providers | from wagtail.embeds import oembed_providers | ||||||
|  | @ -477,60 +479,75 @@ class TestEmbedly(TestCase): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestOembed(TestCase): | class TestOembed(TestCase): | ||||||
|     def setUp(self): |  | ||||||
|         class DummyResponse: |  | ||||||
|             def read(self): |  | ||||||
|                 return b"foo" |  | ||||||
| 
 |  | ||||||
|         self.dummy_response = DummyResponse() |  | ||||||
| 
 |  | ||||||
|     def test_oembed_invalid_provider(self): |     def test_oembed_invalid_provider(self): | ||||||
|         self.assertRaises(EmbedNotFoundException, OEmbedFinder().find_embed, "foo") |         self.assertRaises(EmbedNotFoundException, OEmbedFinder().find_embed, "foo") | ||||||
| 
 | 
 | ||||||
|  |     @responses.activate | ||||||
|     def test_oembed_invalid_request(self): |     def test_oembed_invalid_request(self): | ||||||
|         config = {"side_effect": URLError("foo")} |         # no response set up, so responses will raise a ConnectionError | ||||||
|         with patch.object(urllib.request, "urlopen", **config): |         self.assertRaises( | ||||||
|             self.assertRaises( |             EmbedNotFoundException, | ||||||
|                 EmbedNotFoundException, |             OEmbedFinder().find_embed, | ||||||
|                 OEmbedFinder().find_embed, |             "https://www.youtube.com/watch/", | ||||||
|                 "http://www.youtube.com/watch/", |         ) | ||||||
|             ) |  | ||||||
| 
 | 
 | ||||||
|     @patch("urllib.request.urlopen") |     @responses.activate | ||||||
|     def test_oembed_non_json_response(self, urlopen): |     def test_oembed_non_json_response(self): | ||||||
|         urlopen.return_value = self.dummy_response |         responses.get( | ||||||
|  |             url="https://www.youtube.com/oembed", | ||||||
|  |             match=[ | ||||||
|  |                 matchers.query_param_matcher( | ||||||
|  |                     { | ||||||
|  |                         "url": "https://www.youtube.com/watch?v=ReblZ7o7lu4", | ||||||
|  |                         "format": "json", | ||||||
|  |                     } | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             body="foo", | ||||||
|  |         ) | ||||||
|         self.assertRaises( |         self.assertRaises( | ||||||
|             EmbedNotFoundException, |             EmbedNotFoundException, | ||||||
|             OEmbedFinder().find_embed, |             OEmbedFinder().find_embed, | ||||||
|             "https://www.youtube.com/watch?v=ReblZ7o7lu4", |             "https://www.youtube.com/watch?v=ReblZ7o7lu4", | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     @patch("urllib.request.urlopen") |     @responses.activate | ||||||
|     @patch("json.loads") |     def test_oembed_photo_request(self): | ||||||
|     def test_oembed_photo_request(self, loads, urlopen): |         responses.get( | ||||||
|         urlopen.return_value = self.dummy_response |             url="https://www.youtube.com/oembed", | ||||||
|         loads.return_value = {"type": "photo", "url": "http://www.example.com"} |             match=[ | ||||||
|         result = OEmbedFinder().find_embed("http://www.youtube.com/watch/") |                 matchers.query_param_matcher( | ||||||
|  |                     {"url": "https://www.youtube.com/watch/", "format": "json"} | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             json={"type": "photo", "url": "http://www.example.com"}, | ||||||
|  |         ) | ||||||
|  |         result = OEmbedFinder().find_embed("https://www.youtube.com/watch/") | ||||||
|         self.assertEqual(result["type"], "photo") |         self.assertEqual(result["type"], "photo") | ||||||
|         self.assertEqual(result["html"], '<img src="http://www.example.com" alt="">') |         self.assertEqual(result["html"], '<img src="http://www.example.com" alt="">') | ||||||
|         loads.assert_called_with("foo") |  | ||||||
| 
 | 
 | ||||||
|     @patch("urllib.request.urlopen") |     @responses.activate | ||||||
|     @patch("json.loads") |     def test_oembed_return_values(self): | ||||||
|     def test_oembed_return_values(self, loads, urlopen): |         responses.get( | ||||||
|         urlopen.return_value = self.dummy_response |             url="https://www.youtube.com/oembed", | ||||||
|         loads.return_value = { |             match=[ | ||||||
|             "type": "something", |                 matchers.query_param_matcher( | ||||||
|             "url": "http://www.example.com", |                     {"url": "https://www.youtube.com/watch/", "format": "json"} | ||||||
|             "title": "test_title", |                 ), | ||||||
|             "author_name": "test_author", |             ], | ||||||
|             "provider_name": "test_provider_name", |             json={ | ||||||
|             "thumbnail_url": "test_thumbail_url", |                 "type": "something", | ||||||
|             "width": "test_width", |                 "url": "http://www.example.com", | ||||||
|             "height": "test_height", |                 "title": "test_title", | ||||||
|             "html": "test_html", |                 "author_name": "test_author", | ||||||
|         } |                 "provider_name": "test_provider_name", | ||||||
|         result = OEmbedFinder().find_embed("http://www.youtube.com/watch/") |                 "thumbnail_url": "test_thumbail_url", | ||||||
|  |                 "width": "test_width", | ||||||
|  |                 "height": "test_height", | ||||||
|  |                 "html": "test_html", | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         result = OEmbedFinder().find_embed("https://www.youtube.com/watch/") | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             result, |             result, | ||||||
|             { |             { | ||||||
|  | @ -546,24 +563,30 @@ class TestOembed(TestCase): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     @patch("django.utils.timezone.now") |     @patch("django.utils.timezone.now") | ||||||
|     @patch("urllib.request.urlopen") |     @responses.activate | ||||||
|     @patch("json.loads") |     def test_oembed_cache_until(self, now): | ||||||
|     def test_oembed_cache_until(self, loads, urlopen, now): |         responses.get( | ||||||
|         urlopen.return_value = self.dummy_response |             url="https://www.youtube.com/oembed", | ||||||
|         loads.return_value = { |             match=[ | ||||||
|             "type": "something", |                 matchers.query_param_matcher( | ||||||
|             "url": "http://www.example.com", |                     {"url": "https://www.youtube.com/watch/", "format": "json"} | ||||||
|             "title": "test_title", |                 ), | ||||||
|             "author_name": "test_author", |             ], | ||||||
|             "provider_name": "test_provider_name", |             json={ | ||||||
|             "thumbnail_url": "test_thumbail_url", |                 "type": "something", | ||||||
|             "width": "test_width", |                 "url": "http://www.example.com", | ||||||
|             "height": "test_height", |                 "title": "test_title", | ||||||
|             "html": "test_html", |                 "author_name": "test_author", | ||||||
|             "cache_age": 3600, |                 "provider_name": "test_provider_name", | ||||||
|         } |                 "thumbnail_url": "test_thumbail_url", | ||||||
|  |                 "width": "test_width", | ||||||
|  |                 "height": "test_height", | ||||||
|  |                 "html": "test_html", | ||||||
|  |                 "cache_age": 3600, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|         now.return_value = make_aware(datetime.datetime(2001, 2, 3)) |         now.return_value = make_aware(datetime.datetime(2001, 2, 3)) | ||||||
|         result = OEmbedFinder().find_embed("http://www.youtube.com/watch/") |         result = OEmbedFinder().find_embed("https://www.youtube.com/watch/") | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             result, |             result, | ||||||
|             { |             { | ||||||
|  | @ -580,24 +603,30 @@ class TestOembed(TestCase): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     @patch("django.utils.timezone.now") |     @patch("django.utils.timezone.now") | ||||||
|     @patch("urllib.request.urlopen") |     @responses.activate | ||||||
|     @patch("json.loads") |     def test_oembed_cache_until_as_string(self, now): | ||||||
|     def test_oembed_cache_until_as_string(self, loads, urlopen, now): |         responses.get( | ||||||
|         urlopen.return_value = self.dummy_response |             url="https://www.youtube.com/oembed", | ||||||
|         loads.return_value = { |             match=[ | ||||||
|             "type": "something", |                 matchers.query_param_matcher( | ||||||
|             "url": "http://www.example.com", |                     {"url": "https://www.youtube.com/watch/", "format": "json"} | ||||||
|             "title": "test_title", |                 ), | ||||||
|             "author_name": "test_author", |             ], | ||||||
|             "provider_name": "test_provider_name", |             json={ | ||||||
|             "thumbnail_url": "test_thumbail_url", |                 "type": "something", | ||||||
|             "width": "test_width", |                 "url": "http://www.example.com", | ||||||
|             "height": "test_height", |                 "title": "test_title", | ||||||
|             "html": "test_html", |                 "author_name": "test_author", | ||||||
|             "cache_age": "3600", |                 "provider_name": "test_provider_name", | ||||||
|         } |                 "thumbnail_url": "test_thumbail_url", | ||||||
|  |                 "width": "test_width", | ||||||
|  |                 "height": "test_height", | ||||||
|  |                 "html": "test_html", | ||||||
|  |                 "cache_age": "3600", | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|         now.return_value = make_aware(datetime.datetime(2001, 2, 3)) |         now.return_value = make_aware(datetime.datetime(2001, 2, 3)) | ||||||
|         result = OEmbedFinder().find_embed("http://www.youtube.com/watch/") |         result = OEmbedFinder().find_embed("https://www.youtube.com/watch/") | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             result, |             result, | ||||||
|             { |             { | ||||||
|  | @ -615,24 +644,25 @@ class TestOembed(TestCase): | ||||||
| 
 | 
 | ||||||
|     def test_oembed_accepts_known_provider(self): |     def test_oembed_accepts_known_provider(self): | ||||||
|         finder = OEmbedFinder(providers=[oembed_providers.youtube]) |         finder = OEmbedFinder(providers=[oembed_providers.youtube]) | ||||||
|         self.assertTrue(finder.accept("http://www.youtube.com/watch/")) |         self.assertTrue(finder.accept("https://www.youtube.com/watch/")) | ||||||
| 
 | 
 | ||||||
|     def test_oembed_doesnt_accept_unknown_provider(self): |     def test_oembed_doesnt_accept_unknown_provider(self): | ||||||
|         finder = OEmbedFinder(providers=[oembed_providers.twitter]) |         finder = OEmbedFinder(providers=[oembed_providers.twitter]) | ||||||
|         self.assertFalse(finder.accept("http://www.youtube.com/watch/")) |         self.assertFalse(finder.accept("https://www.youtube.com/watch/")) | ||||||
| 
 | 
 | ||||||
|     @patch("urllib.request.urlopen") |     @responses.activate | ||||||
|     @patch("json.loads") |     def test_endpoint_with_format_param(self): | ||||||
|     def test_endpoint_with_format_param(self, loads, urlopen): |         responses.get( | ||||||
|         urlopen.return_value = self.dummy_response |             url="https://www.vimeo.com/api/oembed.json", | ||||||
|         loads.return_value = {"type": "video", "url": "http://www.example.com"} |             match=[ | ||||||
|  |                 matchers.query_param_matcher( | ||||||
|  |                     {"url": "https://vimeo.com/217403396", "format": "json"} | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             json={"type": "video", "url": "http://www.example.com"}, | ||||||
|  |         ) | ||||||
|         result = OEmbedFinder().find_embed("https://vimeo.com/217403396") |         result = OEmbedFinder().find_embed("https://vimeo.com/217403396") | ||||||
|         self.assertEqual(result["type"], "video") |         self.assertEqual(result["type"], "video") | ||||||
|         request = urlopen.call_args[0][0] |  | ||||||
|         self.assertEqual( |  | ||||||
|             request.get_full_url().split("?")[0], |  | ||||||
|             "https://www.vimeo.com/api/oembed.json", |  | ||||||
|         ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestInstagramOEmbed(TestCase): | class TestInstagramOEmbed(TestCase): | ||||||
|  |  | ||||||
		Ładowanie…
	
		Reference in New Issue
	
	 Matt Westcott
						Matt Westcott