import datetime from unittest import mock from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.views import shortcut from django.contrib.sites.models import Site from django.contrib.sites.shortcuts import get_current_site from django.http import Http404, HttpRequest from django.test import TestCase, override_settings from .models import ( Article, Author, FooWithBrokenAbsoluteUrl, FooWithoutUrl, FooWithUrl, ModelWithM2MToSite, ModelWithNullFKToSite, SchemeIncludedURL, ) from .models import Site as MockSite @override_settings(ROOT_URLCONF="contenttypes_tests.urls") class ContentTypesViewsTests(TestCase): @classmethod def setUpTestData(cls): # Don't use the manager to ensure the site exists with pk=1, regardless # of whether or not it already exists. cls.site1 = Site(pk=1, domain="testserver", name="testserver") cls.site1.save() cls.author1 = Author.objects.create(name="Boris") cls.article1 = Article.objects.create( title="Old Article", slug="old_article", author=cls.author1, date_created=datetime.datetime(2001, 1, 1, 21, 22, 23), ) cls.article2 = Article.objects.create( title="Current Article", slug="current_article", author=cls.author1, date_created=datetime.datetime(2007, 9, 17, 21, 22, 23), ) cls.article3 = Article.objects.create( title="Future Article", slug="future_article", author=cls.author1, date_created=datetime.datetime(3000, 1, 1, 21, 22, 23), ) cls.scheme1 = SchemeIncludedURL.objects.create( url="http://test_scheme_included_http/" ) cls.scheme2 = SchemeIncludedURL.objects.create( url="https://test_scheme_included_https/" ) cls.scheme3 = SchemeIncludedURL.objects.create( url="//test_default_scheme_kept/" ) def setUp(self): Site.objects.clear_cache() def test_shortcut_with_absolute_url(self): "Can view a shortcut for an Author object that has a get_absolute_url method" for obj in Author.objects.all(): with self.subTest(obj=obj): short_url = "/shortcut/%s/%s/" % ( ContentType.objects.get_for_model(Author).id, obj.pk, ) response = self.client.get(short_url) self.assertRedirects( response, "http://testserver%s" % obj.get_absolute_url(), target_status_code=404, ) def test_shortcut_with_absolute_url_including_scheme(self): """ Can view a shortcut when object's get_absolute_url returns a full URL the tested URLs are: "http://...", "https://..." and "//..." """ for obj in SchemeIncludedURL.objects.all(): with self.subTest(obj=obj): short_url = "/shortcut/%s/%s/" % ( ContentType.objects.get_for_model(SchemeIncludedURL).id, obj.pk, ) response = self.client.get(short_url) self.assertRedirects( response, obj.get_absolute_url(), fetch_redirect_response=False ) def test_shortcut_no_absolute_url(self): """ Shortcuts for an object that has no get_absolute_url() method raise 404. """ for obj in Article.objects.all(): with self.subTest(obj=obj): short_url = "/shortcut/%s/%s/" % ( ContentType.objects.get_for_model(Article).id, obj.pk, ) response = self.client.get(short_url) self.assertEqual(response.status_code, 404) def test_wrong_type_pk(self): short_url = "/shortcut/%s/%s/" % ( ContentType.objects.get_for_model(Author).id, "nobody/expects", ) response = self.client.get(short_url) self.assertEqual(response.status_code, 404) def test_shortcut_bad_pk(self): short_url = "/shortcut/%s/%s/" % ( ContentType.objects.get_for_model(Author).id, "42424242", ) response = self.client.get(short_url) self.assertEqual(response.status_code, 404) def test_nonint_content_type(self): an_author = Author.objects.all()[0] short_url = "/shortcut/%s/%s/" % ("spam", an_author.pk) response = self.client.get(short_url) self.assertEqual(response.status_code, 404) def test_bad_content_type(self): an_author = Author.objects.all()[0] short_url = "/shortcut/%s/%s/" % (42424242, an_author.pk) response = self.client.get(short_url) self.assertEqual(response.status_code, 404) @override_settings(ROOT_URLCONF="contenttypes_tests.urls") class ContentTypesViewsSiteRelTests(TestCase): def setUp(self): Site.objects.clear_cache() @classmethod def setUpTestData(cls): cls.site_2 = Site.objects.create(domain="example2.com", name="example2.com") cls.site_3 = Site.objects.create(domain="example3.com", name="example3.com") @mock.patch("django.apps.apps.get_model") def test_shortcut_view_with_null_site_fk(self, get_model): """ The shortcut view works if a model's ForeignKey to site is None. """ get_model.side_effect = ( lambda *args, **kwargs: MockSite if args[0] == "sites.Site" else ModelWithNullFKToSite ) obj = ModelWithNullFKToSite.objects.create(title="title") url = "/shortcut/%s/%s/" % ( ContentType.objects.get_for_model(ModelWithNullFKToSite).id, obj.pk, ) response = self.client.get(url) expected_url = "http://example.com%s" % obj.get_absolute_url() self.assertRedirects(response, expected_url, fetch_redirect_response=False) @mock.patch("django.apps.apps.get_model") def test_shortcut_view_with_site_m2m(self, get_model): """ When the object has a ManyToManyField to Site, redirect to the current site if it's attached to the object or to the domain of the first site found in the m2m relationship. """ get_model.side_effect = ( lambda *args, **kwargs: MockSite if args[0] == "sites.Site" else ModelWithM2MToSite ) # get_current_site() will lookup a Site object, so these must match the # domains in the MockSite model. MockSite.objects.bulk_create( [ MockSite(pk=1, domain="example.com"), MockSite(pk=self.site_2.pk, domain=self.site_2.domain), MockSite(pk=self.site_3.pk, domain=self.site_3.domain), ] ) ct = ContentType.objects.get_for_model(ModelWithM2MToSite) site_3_obj = ModelWithM2MToSite.objects.create( title="Not Linked to Current Site" ) site_3_obj.sites.add(MockSite.objects.get(pk=self.site_3.pk)) expected_url = "http://%s%s" % ( self.site_3.domain, site_3_obj.get_absolute_url(), ) with self.settings(SITE_ID=self.site_2.pk): # Redirects to the domain of the first Site found in the m2m # relationship (ordering is arbitrary). response = self.client.get("/shortcut/%s/%s/" % (ct.pk, site_3_obj.pk)) self.assertRedirects(response, expected_url, fetch_redirect_response=False) obj_with_sites = ModelWithM2MToSite.objects.create( title="Linked to Current Site" ) obj_with_sites.sites.set(MockSite.objects.all()) shortcut_url = "/shortcut/%s/%s/" % (ct.pk, obj_with_sites.pk) expected_url = "http://%s%s" % ( self.site_2.domain, obj_with_sites.get_absolute_url(), ) with self.settings(SITE_ID=self.site_2.pk): # Redirects to the domain of the Site matching the current site's # domain. response = self.client.get(shortcut_url) self.assertRedirects(response, expected_url, fetch_redirect_response=False) with self.settings(SITE_ID=None, ALLOWED_HOSTS=[self.site_2.domain]): # Redirects to the domain of the Site matching the request's host # header. response = self.client.get(shortcut_url, SERVER_NAME=self.site_2.domain) self.assertRedirects(response, expected_url, fetch_redirect_response=False) class ShortcutViewTests(TestCase): def setUp(self): self.request = HttpRequest() self.request.META = {"SERVER_NAME": "Example.com", "SERVER_PORT": "80"} @override_settings(ALLOWED_HOSTS=["example.com"]) def test_not_dependent_on_sites_app(self): """ The view returns a complete URL regardless of whether the sites framework is installed. """ user_ct = ContentType.objects.get_for_model(FooWithUrl) obj = FooWithUrl.objects.create(name="john") with self.modify_settings(INSTALLED_APPS={"append": "django.contrib.sites"}): response = shortcut(self.request, user_ct.id, obj.id) self.assertEqual( "http://%s/users/john/" % get_current_site(self.request).domain, response.headers.get("location"), ) with self.modify_settings(INSTALLED_APPS={"remove": "django.contrib.sites"}): response = shortcut(self.request, user_ct.id, obj.id) self.assertEqual( "http://Example.com/users/john/", response.headers.get("location") ) def test_model_without_get_absolute_url(self): """The view returns 404 when Model.get_absolute_url() isn't defined.""" user_ct = ContentType.objects.get_for_model(FooWithoutUrl) obj = FooWithoutUrl.objects.create(name="john") with self.assertRaises(Http404): shortcut(self.request, user_ct.id, obj.id) def test_model_with_broken_get_absolute_url(self): """ The view doesn't catch an AttributeError raised by Model.get_absolute_url() (#8997). """ user_ct = ContentType.objects.get_for_model(FooWithBrokenAbsoluteUrl) obj = FooWithBrokenAbsoluteUrl.objects.create(name="john") with self.assertRaises(AttributeError): shortcut(self.request, user_ct.id, obj.id)