From 40ac053e5d11c6fc47d2cee5e2f9f639c3248f60 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 25 Jun 2014 10:58:05 +0100 Subject: [PATCH 1/9] Implement field validation for Site.is_default_site; resolves #289 --- wagtail/wagtailcore/models.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 40a10a9c54..c701136043 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -15,6 +15,7 @@ from django.contrib.auth.models import Group from django.conf import settings from django.template.response import TemplateResponse from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError from treebeard.mp_tree import MP_Node @@ -63,6 +64,25 @@ class Site(models.Model): else: return 'http://%s:%d' % (self.hostname, self.port) + def clean_fields(self, exclude=None): + super(Site, self).clean_fields(exclude) + # Only one site can have the is_default_site flag set + try: + default = Site.objects.get(is_default_site=True) + except Site.DoesNotExist: + pass + except Site.MultipleObjectsReturned: + raise + else: + if self.is_default_site and self.pk != default.pk: + raise ValidationError( + {'is_default_site': [ + _("%(hostname)s is already configured as the default site. You must unset that before you can save this site as default.") + % { 'hostname': default.hostname } + ] + }, + ) + # clear the wagtail_site_root_paths cache whenever Site records are updated def save(self, *args, **kwargs): result = super(Site, self).save(*args, **kwargs) From 49cb5b10250fb28c746067fd66bbc8bfddc55785 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 25 Jun 2014 11:02:52 +0100 Subject: [PATCH 2/9] Allow multiple sites with the same hostname but different ports; resolves #290 --- ...hostname__add_unique_site_hostname_port.py | 106 ++++++++++++++++++ wagtail/wagtailcore/models.py | 30 ++++- 2 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 wagtail/wagtailcore/migrations/0003_auto__del_unique_site_hostname__add_unique_site_hostname_port.py diff --git a/wagtail/wagtailcore/migrations/0003_auto__del_unique_site_hostname__add_unique_site_hostname_port.py b/wagtail/wagtailcore/migrations/0003_auto__del_unique_site_hostname__add_unique_site_hostname_port.py new file mode 100644 index 0000000000..16686e0a9b --- /dev/null +++ b/wagtail/wagtailcore/migrations/0003_auto__del_unique_site_hostname__add_unique_site_hostname_port.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Removing unique constraint on 'Site', fields ['hostname'] + db.delete_unique(u'wagtailcore_site', ['hostname']) + + # Adding unique constraint on 'Site', fields ['hostname', 'port'] + db.create_unique(u'wagtailcore_site', ['hostname', 'port']) + + + def backwards(self, orm): + # Removing unique constraint on 'Site', fields ['hostname', 'port'] + db.delete_unique(u'wagtailcore_site', ['hostname', 'port']) + + # Adding unique constraint on 'Site', fields ['hostname'] + db.create_unique(u'wagtailcore_site', ['hostname']) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'wagtailcore.grouppagepermission': { + 'Meta': {'object_name': 'GroupPagePermission'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_permissions'", 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_permissions'", 'to': u"orm['wagtailcore.Page']"}), + 'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}) + }, + u'wagtailcore.page': { + 'Meta': {'object_name': 'Page'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': u"orm['contenttypes.ContentType']"}), + 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'has_unpublished_changes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'live': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_pages'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'search_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'seo_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'show_in_menus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'url_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}) + }, + u'wagtailcore.pagerevision': { + 'Meta': {'object_name': 'PageRevision'}, + 'content_json': ('django.db.models.fields.TextField', [], {}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': u"orm['wagtailcore.Page']"}), + 'submitted_for_moderation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + u'wagtailcore.site': { + 'Meta': {'unique_together': "(('hostname', 'port'),)", 'object_name': 'Site'}, + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_default_site': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'port': ('django.db.models.fields.IntegerField', [], {'default': '80'}), + 'root_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sites_rooted_here'", 'to': u"orm['wagtailcore.Page']"}) + } + } + + complete_apps = ['wagtailcore'] \ No newline at end of file diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index c701136043..2b8cc36ec3 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -31,11 +31,14 @@ class SiteManager(models.Manager): class Site(models.Model): - hostname = models.CharField(max_length=255, unique=True, db_index=True) + hostname = models.CharField(max_length=255, db_index=True) port = models.IntegerField(default=80, help_text=_("Set this to something other than 80 if you need a specific port number to appear in URLs (e.g. development on port 8000). Does not affect request handling (so port forwarding still works).")) root_page = models.ForeignKey('Page', related_name='sites_rooted_here') is_default_site = models.BooleanField(default=False, help_text=_("If true, this site will handle requests for all other hostnames that do not have a site entry of their own")) + class Meta: + unique_together = ('hostname', 'port') + def natural_key(self): return (self.hostname,) @@ -46,9 +49,25 @@ class Site(models.Model): def find_for_request(request): """Find the site object responsible for responding to this HTTP request object""" try: - hostname = request.META['HTTP_HOST'].split(':')[0] - # find a Site matching this specific hostname - return Site.objects.get(hostname=hostname) + try: + hostname, port = request.META['HTTP_HOST'].split(':') + except ValueError: + hostname = request.META['HTTP_HOST'] + port = '80' # FIXME do we want this default? + except KeyError: + # explicit routing straight to the final except clause + raise + try: + # find a Site matching this specific hostname + return Site.objects.get(hostname=hostname) + except Site.MultipleObjectsReturned: + try: + # as there were more than one, try matching by port too + return Site.objects.get(hostname=hostname, port=int(port)) + except Site.DoesNotExist: + # explicit acknowledgement that this is another route to + # the final except clause + raise except (Site.DoesNotExist, KeyError): # If no matching site exists, or request does not specify an HTTP_HOST (which # will often be the case for the Django test client), look for a catch-all Site. @@ -79,8 +98,7 @@ class Site(models.Model): {'is_default_site': [ _("%(hostname)s is already configured as the default site. You must unset that before you can save this site as default.") % { 'hostname': default.hostname } - ] - }, + ]} ) # clear the wagtail_site_root_paths cache whenever Site records are updated From eccb87d9a03e8021c0b55620db3cbcc34f4e02bb Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Wed, 25 Jun 2014 11:03:30 +0100 Subject: [PATCH 3/9] Add routing test for when two sites have the same hostname but different ports --- wagtail/wagtailcore/tests/test_page_model.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index 1bae57952c..cf9a8b7bce 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -16,6 +16,7 @@ class TestRouting(TestCase): default_site = Site.objects.get(is_default_site=True) events_page = Page.objects.get(url_path='/home/events/') events_site = Site.objects.create(hostname='events.example.com', root_page=events_page) + alternate_port_events_site = Site.objects.create(hostname='events.example.com', root_page=events_page, port='8765') # requests without a Host: header should be directed to the default site request = HttpRequest() @@ -28,6 +29,12 @@ class TestRouting(TestCase): request.META['HTTP_HOST'] = 'events.example.com' self.assertEqual(Site.find_for_request(request), events_site) + # ports in the Host: header should be respected + request = HttpRequest() + request.path = '/' + request.META['HTTP_HOST'] = 'events.example.com:8765' + self.assertEqual(Site.find_for_request(request), alternate_port_events_site) + # requests with an unrecognised Host: header should be directed to the default site request = HttpRequest() request.path = '/' From ee9297b4453a1edeb6c21d29a3212f9689bb9b6c Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 27 Jun 2014 11:17:44 +0100 Subject: [PATCH 4/9] Update Site.objects.get_by_natural_key() to include port --- wagtail/wagtailcore/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 2b8cc36ec3..4bf818fb09 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -26,8 +26,8 @@ from wagtail.wagtailsearch import Indexed, get_search_backend class SiteManager(models.Manager): - def get_by_natural_key(self, hostname): - return self.get(hostname=hostname) + def get_by_natural_key(self, hostname, port): + return self.get(hostname=hostname, port=port) class Site(models.Model): @@ -40,7 +40,7 @@ class Site(models.Model): unique_together = ('hostname', 'port') def natural_key(self): - return (self.hostname,) + return (self.hostname, self.port) def __unicode__(self): return self.hostname + ("" if self.port == 80 else (":%d" % self.port)) + (" [default]" if self.is_default_site else "") From 781aabad97bd5552eb00d9f2b36137b0c166fe62 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 27 Jun 2014 11:18:11 +0100 Subject: [PATCH 5/9] Update Site.find_for_request to respect https --- wagtail/wagtailcore/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 4bf818fb09..59742ffb7d 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -53,7 +53,7 @@ class Site(models.Model): hostname, port = request.META['HTTP_HOST'].split(':') except ValueError: hostname = request.META['HTTP_HOST'] - port = '80' # FIXME do we want this default? + port = '443' if request.is_secure() else '80' except KeyError: # explicit routing straight to the final except clause raise From 1ac20bc76653c3aa0104f70f8bdb91381df2dcc6 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 27 Jun 2014 12:49:22 +0100 Subject: [PATCH 6/9] Change the way we lookup the request port --- wagtail/wagtailcore/models.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 59742ffb7d..dd7e5ac8cc 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -47,27 +47,26 @@ class Site(models.Model): @staticmethod def find_for_request(request): - """Find the site object responsible for responding to this HTTP request object""" + """ + Find the site object responsible for responding to this HTTP + request object. Try: + - unique hostname first + - then hostname and port + - if there is no matching hostname at all, or no matching + hostname:port combination, fall back to the unique default site, + or raise an exception + NB this means that high-numbered ports on an extant hostname may + still be routed to a different hostname which is set as the default + """ try: - try: - hostname, port = request.META['HTTP_HOST'].split(':') - except ValueError: - hostname = request.META['HTTP_HOST'] - port = '443' if request.is_secure() else '80' - except KeyError: - # explicit routing straight to the final except clause - raise + hostname = request.META['HTTP_HOST'].split(':')[0] # KeyError here goes to the final except clause try: # find a Site matching this specific hostname - return Site.objects.get(hostname=hostname) + return Site.objects.get(hostname=hostname) # Site.DoesNotExist here goes to the final except clause except Site.MultipleObjectsReturned: - try: - # as there were more than one, try matching by port too - return Site.objects.get(hostname=hostname, port=int(port)) - except Site.DoesNotExist: - # explicit acknowledgement that this is another route to - # the final except clause - raise + # as there were more than one, try matching by port too + port = request.META['SERVER_PORT'] # KeyError here goes to the final except clause + return Site.objects.get(hostname=hostname, port=int(port)) # Site.DoesNotExist here goes to the final except clause except (Site.DoesNotExist, KeyError): # If no matching site exists, or request does not specify an HTTP_HOST (which # will often be the case for the Django test client), look for a catch-all Site. From e87e9eebc27b3eded788d901024265216ef33887 Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 27 Jun 2014 12:50:49 +0100 Subject: [PATCH 7/9] Modify tests to include ports differently, add tests for unrecognised ports --- wagtail/wagtailcore/tests/test_page_model.py | 41 ++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index cf9a8b7bce..874b4bed3e 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -17,6 +17,8 @@ class TestRouting(TestCase): events_page = Page.objects.get(url_path='/home/events/') events_site = Site.objects.create(hostname='events.example.com', root_page=events_page) alternate_port_events_site = Site.objects.create(hostname='events.example.com', root_page=events_page, port='8765') + unrecognised_port = '8000' + unrecognised_hostname = 'unknown.site.com' # requests without a Host: header should be directed to the default site request = HttpRequest() @@ -26,21 +28,54 @@ class TestRouting(TestCase): # requests with a known Host: header should be directed to the specific site request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = 'events.example.com' + request.META['HTTP_HOST'] = events_site.hostname + request.META['SERVER_PORT'] = events_site.port self.assertEqual(Site.find_for_request(request), events_site) # ports in the Host: header should be respected request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = 'events.example.com:8765' + request.META['HTTP_HOST'] = alternate_port_events_site.hostname + request.META['SERVER_PORT'] = alternate_port_events_site.port self.assertEqual(Site.find_for_request(request), alternate_port_events_site) # requests with an unrecognised Host: header should be directed to the default site request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = 'unknown.example.com' + request.META['HTTP_HOST'] = unrecognised_hostname + request.META['SERVER_PORT'] = '80' self.assertEqual(Site.find_for_request(request), default_site) + # requests on an unrecognised port should be directed to the default site + request = HttpRequest() + request.path = '/' + request.META['HTTP_HOST'] = default_site.hostname + request.META['SERVER_PORT'] = unrecognised_port + self.assertEqual(Site.find_for_request(request), default_site) + + # requests with an unrecognised Host: header _and_ an unrecognised port + # hould be directed to the default site + request = HttpRequest() + request.path = '/' + request.META['HTTP_HOST'] = unrecognised_hostname + request.META['SERVER_PORT'] = unrecognised_port + self.assertEqual(Site.find_for_request(request), default_site) + + # requests on an unrecognised port should be directed to the default + # site, even if their hostname (but not port) matches another entry + request = HttpRequest() + request.path = '/' + request.META['HTTP_HOST'] = events_site.hostname + request.META['SERVER_PORT'] = unrecognised_port + self.assertEqual(Site.find_for_request(request), default_site) + + # port in the HTTP_HOST header is ignored + request = HttpRequest() + request.path = '/' + request.META['HTTP_HOST'] = "%s:%s" % (events_site.hostname, events_site.port) + request.META['SERVER_PORT'] = alternate_port_events_site.port + self.assertEqual(Site.find_for_request(request), alternate_port_events_site) + def test_urls(self): default_site = Site.objects.get(is_default_site=True) homepage = Page.objects.get(url_path='/home/') From 3cac8ea3875169b34ee7ed854800d1dbcec4320a Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 27 Jun 2014 16:19:10 +0100 Subject: [PATCH 8/9] Add test that requests on unrecognised ports are routed to the expected site if no ambiguity --- wagtail/wagtailcore/tests/test_page_model.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index 874b4bed3e..a9e9781b10 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -15,8 +15,10 @@ class TestRouting(TestCase): def test_find_site_for_request(self): default_site = Site.objects.get(is_default_site=True) events_page = Page.objects.get(url_path='/home/events/') + about_page = Page.objects.get(url_path='/home/about-us/') events_site = Site.objects.create(hostname='events.example.com', root_page=events_page) alternate_port_events_site = Site.objects.create(hostname='events.example.com', root_page=events_page, port='8765') + about_site = Site.objects.create(hostname='about.example.com', root_page=about_page) unrecognised_port = '8000' unrecognised_hostname = 'unknown.site.com' @@ -61,8 +63,17 @@ class TestRouting(TestCase): request.META['SERVER_PORT'] = unrecognised_port self.assertEqual(Site.find_for_request(request), default_site) + # requests on an unrecognised port should be directed to the site with + # matching hostname if there is no ambiguity + request = HttpRequest() + request.path = '/' + request.META['HTTP_HOST'] = about_site.hostname + request.META['SERVER_PORT'] = unrecognised_port + self.assertEqual(Site.find_for_request(request), about_site) + # requests on an unrecognised port should be directed to the default - # site, even if their hostname (but not port) matches another entry + # site, even if their hostname (but not port) matches more than one + # other entry request = HttpRequest() request.path = '/' request.META['HTTP_HOST'] = events_site.hostname From 1aad1e55c287778ccff4d2da7fad24b175b8e30b Mon Sep 17 00:00:00 2001 From: Nick Smith Date: Fri, 27 Jun 2014 16:20:09 +0100 Subject: [PATCH 9/9] Refactor port/site tests into new test case and individual tests --- wagtail/wagtailcore/tests/test_page_model.py | 79 ++++++++++++-------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index a9e9781b10..41d47c4e0e 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -9,83 +9,96 @@ from wagtail.wagtailcore.models import Page, Site, UserPagePermissionsProxy from wagtail.tests.models import EventPage, EventIndex, SimplePage -class TestRouting(TestCase): +class TestSiteRouting(TestCase): fixtures = ['test.json'] - def test_find_site_for_request(self): - default_site = Site.objects.get(is_default_site=True) + def setUp(self): + self.default_site = Site.objects.get(is_default_site=True) events_page = Page.objects.get(url_path='/home/events/') about_page = Page.objects.get(url_path='/home/about-us/') - events_site = Site.objects.create(hostname='events.example.com', root_page=events_page) - alternate_port_events_site = Site.objects.create(hostname='events.example.com', root_page=events_page, port='8765') - about_site = Site.objects.create(hostname='about.example.com', root_page=about_page) - unrecognised_port = '8000' - unrecognised_hostname = 'unknown.site.com' + self.events_site = Site.objects.create(hostname='events.example.com', root_page=events_page) + self.alternate_port_events_site = Site.objects.create(hostname='events.example.com', root_page=events_page, port='8765') + self.about_site = Site.objects.create(hostname='about.example.com', root_page=about_page) + self.unrecognised_port = '8000' + self.unrecognised_hostname = 'unknown.site.com' + def test_no_host_header_routes_to_default_site(self): # requests without a Host: header should be directed to the default site request = HttpRequest() request.path = '/' - self.assertEqual(Site.find_for_request(request), default_site) + self.assertEqual(Site.find_for_request(request), self.default_site) + def test_valid_headers_route_to_specific_site(self): # requests with a known Host: header should be directed to the specific site request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = events_site.hostname - request.META['SERVER_PORT'] = events_site.port - self.assertEqual(Site.find_for_request(request), events_site) + request.META['HTTP_HOST'] = self.events_site.hostname + request.META['SERVER_PORT'] = self.events_site.port + self.assertEqual(Site.find_for_request(request), self.events_site) + def test_ports_in_request_headers_are_respected(self): # ports in the Host: header should be respected request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = alternate_port_events_site.hostname - request.META['SERVER_PORT'] = alternate_port_events_site.port - self.assertEqual(Site.find_for_request(request), alternate_port_events_site) + request.META['HTTP_HOST'] = self.alternate_port_events_site.hostname + request.META['SERVER_PORT'] = self.alternate_port_events_site.port + self.assertEqual(Site.find_for_request(request), self.alternate_port_events_site) + def test_unrecognised_host_header_routes_to_default_site(self): # requests with an unrecognised Host: header should be directed to the default site request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = unrecognised_hostname + request.META['HTTP_HOST'] = self.unrecognised_hostname request.META['SERVER_PORT'] = '80' - self.assertEqual(Site.find_for_request(request), default_site) + self.assertEqual(Site.find_for_request(request), self.default_site) - # requests on an unrecognised port should be directed to the default site + def test_unrecognised_port_and_default_host_routes_to_default_site(self): + # requests to the default host on an unrecognised port should be directed to the default site request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = default_site.hostname - request.META['SERVER_PORT'] = unrecognised_port - self.assertEqual(Site.find_for_request(request), default_site) + request.META['HTTP_HOST'] = self.default_site.hostname + request.META['SERVER_PORT'] = self.unrecognised_port + self.assertEqual(Site.find_for_request(request), self.default_site) + def test_unrecognised_port_and_unrecognised_host_routes_to_default_site(self): # requests with an unrecognised Host: header _and_ an unrecognised port # hould be directed to the default site request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = unrecognised_hostname - request.META['SERVER_PORT'] = unrecognised_port - self.assertEqual(Site.find_for_request(request), default_site) + request.META['HTTP_HOST'] = self.unrecognised_hostname + request.META['SERVER_PORT'] = self.unrecognised_port + self.assertEqual(Site.find_for_request(request), self.default_site) + def test_unrecognised_port_on_known_hostname_routes_there_if_no_ambiguity(self): # requests on an unrecognised port should be directed to the site with # matching hostname if there is no ambiguity request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = about_site.hostname - request.META['SERVER_PORT'] = unrecognised_port - self.assertEqual(Site.find_for_request(request), about_site) + request.META['HTTP_HOST'] = self.about_site.hostname + request.META['SERVER_PORT'] = self.unrecognised_port + self.assertEqual(Site.find_for_request(request), self.about_site) + def test_unrecognised_port_on_known_hostname_routes_to_default_site_if_ambiguity(self): # requests on an unrecognised port should be directed to the default # site, even if their hostname (but not port) matches more than one # other entry request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = events_site.hostname - request.META['SERVER_PORT'] = unrecognised_port - self.assertEqual(Site.find_for_request(request), default_site) + request.META['HTTP_HOST'] = self.events_site.hostname + request.META['SERVER_PORT'] = self.unrecognised_port + self.assertEqual(Site.find_for_request(request), self.default_site) + def test_port_in_http_host_header_is_ignored(self): # port in the HTTP_HOST header is ignored request = HttpRequest() request.path = '/' - request.META['HTTP_HOST'] = "%s:%s" % (events_site.hostname, events_site.port) - request.META['SERVER_PORT'] = alternate_port_events_site.port - self.assertEqual(Site.find_for_request(request), alternate_port_events_site) + request.META['HTTP_HOST'] = "%s:%s" % (self.events_site.hostname, self.events_site.port) + request.META['SERVER_PORT'] = self.alternate_port_events_site.port + self.assertEqual(Site.find_for_request(request), self.alternate_port_events_site) + + +class TestRouting(TestCase): + fixtures = ['test.json'] def test_urls(self): default_site = Site.objects.get(is_default_site=True)