From 7020dcb055c8b2e7ad77472d2dce6da479797b02 Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 19 Apr 2018 10:21:04 +0100 Subject: [PATCH 1/9] Updates to support Django 2.0 --- multisite/migrations/0001_initial.py | 2 +- multisite/models.py | 4 +++- multisite/template/loaders/filesystem.py | 11 ++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/multisite/migrations/0001_initial.py b/multisite/migrations/0001_initial.py index d2d0f85..bc2d16e 100644 --- a/multisite/migrations/0001_initial.py +++ b/multisite/migrations/0001_initial.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): ('domain', models.CharField(help_text='Either "domain" or "domain:port"', unique=True, max_length=100, verbose_name='domain name')), ('is_canonical', models.NullBooleanField(default=None, validators=[multisite.models.validate_true_or_none], editable=False, help_text='Does this domain name match the one in site?', verbose_name='is canonical?')), ('redirect_to_canonical', models.BooleanField(default=True, help_text='Should this domain name redirect to the one in site?', verbose_name='redirect to canonical?')), - ('site', models.ForeignKey(related_name='aliases', to='sites.Site')), + ('site', models.ForeignKey(related_name='aliases', to='sites.Site', on_delete=models.CASCADE)), ], options={ 'verbose_name_plural': 'aliases', diff --git a/multisite/models.py b/multisite/models.py index 15cd88b..9b724dd 100644 --- a/multisite/models.py +++ b/multisite/models.py @@ -169,7 +169,9 @@ class Alias(models.Model): unique=True, help_text=_('Either "domain" or "domain:port"'), ) - site = models.ForeignKey(Site, related_name='aliases') + site = models.ForeignKey( + Site, related_name='aliases', on_delete=models.CASCADE + ) is_canonical = models.NullBooleanField( _('is canonical?'), default=None, editable=False, diff --git a/multisite/template/loaders/filesystem.py b/multisite/template/loaders/filesystem.py index cc0d957..b061754 100644 --- a/multisite/template/loaders/filesystem.py +++ b/multisite/template/loaders/filesystem.py @@ -6,15 +6,20 @@ from django.conf import settings from django.contrib.sites.models import Site from django.template.loaders.filesystem import Loader as FilesystemLoader +from django import VERSION as django_version class Loader(FilesystemLoader): - def get_template_sources(self, template_name, template_dirs=None): + def get_template_sources(self, *args, **kwargs): + template_name = args[0] domain = Site.objects.get_current().domain default_dir = getattr(settings, 'MULTISITE_DEFAULT_TEMPLATE_DIR', 'default') for tname in (os.path.join(domain, template_name), os.path.join(default_dir, template_name)): - for item in super(Loader, self).get_template_sources(tname, - template_dirs): + if django_version < (2, 0, 0): + args = [tname, None] + else: + args = [tname] + for item in super(Loader, self).get_template_sources(*args, **kwargs): yield item From 5f478202213c9b5dc9fd2f95d832e088fee22d3d Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 19 Apr 2018 10:21:32 +0100 Subject: [PATCH 2/9] Update setup.py and tox to install/test with Django 2.0 --- setup.py | 11 +++++++++-- tox.ini | 9 +++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index cf51ac5..9100255 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,17 @@ +import sys + from setuptools import find_packages, setup import os _dir_ = os.path.dirname(__file__) +if sys.version_info < (3, 4): + install_requires = ['Django>=1.7,<2.0', 'tldextract>=1.2'] +else: + install_requires = ['Django>=1.7,<2.1', 'tldextract>=1.2'] + + def long_description(): """Returns the value of README.rst""" with open(os.path.join(_dir_, 'README.rst')) as f: @@ -24,8 +32,7 @@ def long_description(): packages=find_packages(), include_package_data=True, package_data={'multisite': files}, - install_requires=['Django>=1.7,<2.0', - 'tldextract>=1.2'], + install_requires=install_requires, setup_requires=['pytest-runner'], tests_require=['coverage', 'mock', 'pytest', 'pytest-cov', 'pytest-django', 'pytest-pythonpath', 'tox'], diff --git a/tox.ini b/tox.ini index bebd745..d647352 100644 --- a/tox.ini +++ b/tox.ini @@ -8,9 +8,9 @@ setenv= PYTHONPATH = {toxinidir}:{env:PYTHONPATH:} usedevelop = True envlist = - py36-django{1.11} - py35-django{1.11,1.10,1.9,1.8} - py34-django{1.11,1.10,1.9,1.8,1.7} + py36-django{2.0, 1.11} + py35-django{2.0, 1.11,1.10,1.9,1.8} + py34-django{2.0, 1.11,1.10,1.9,1.8,1.7} py27-django{1.11,1.10,1.9,1.8,1.7} [testenv] @@ -23,7 +23,8 @@ deps = pytest-pythonpath py27: mock - django1.11: Django==1.11 + django2.0: Django>=2.0,<2.1 + django1.11: Django>=1.11,<2.0 django1.10: Django>=1.10,<1.11 django1.9: Django>=1.9,<1.10 django1.8: Django>=1.8,<1.9 From ccdd848c62d7f0bccd0b82283df1c56292b84790 Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 19 Apr 2018 10:32:41 +0100 Subject: [PATCH 3/9] Remove old deprecated PathAssistedCurrentSiteManager It's incompatible with the versions of django that we now support --- multisite/managers.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/multisite/managers.py b/multisite/managers.py index 939991f..c4fee11 100644 --- a/multisite/managers.py +++ b/multisite/managers.py @@ -2,8 +2,6 @@ from __future__ import unicode_literals from __future__ import absolute_import -from warnings import warn - from django.db import models from django.contrib.sites import managers from django.db.models.fields import FieldDoesNotExist @@ -87,21 +85,3 @@ def _get_related_model(self, model, fieldname): return model._meta.get_field(fieldname).remote_field.model except AttributeError: return model._meta.get_field(fieldname).rel.to - - -class PathAssistedCurrentSiteManager(models.CurrentSiteManager): - """ - Deprecated: Use multisite.managers.SpanningCurrentSiteManager instead. - """ - def __init__(self, field_path): - warn(('Use multisite.managers.SpanningCurrentSiteManager instead of ' - 'multisite.managers.PathAssistedCurrentSiteManager'), - DeprecationWarning, stacklevel=2) - super(PathAssistedCurrentSiteManager, self).__init__() - self.__field_path = field_path - - def get_queryset(self): - from django.contrib.sites.models import Site - return super(models.CurrentSiteManager, self).get_queryset().filter( - **{self.__field_path: Site.objects.get_current()} - ) From cabfab94788bf602509239f53c6877185f6fd58a Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 19 Apr 2018 10:38:40 +0100 Subject: [PATCH 4/9] Remove template.loaders.cached Undocumented, and broken since Django 1.6, which is no loger supported --- multisite/template/loaders/cached.py | 49 ---------------------------- 1 file changed, 49 deletions(-) delete mode 100644 multisite/template/loaders/cached.py diff --git a/multisite/template/loaders/cached.py b/multisite/template/loaders/cached.py deleted file mode 100644 index 5d8ff6d..0000000 --- a/multisite/template/loaders/cached.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from __future__ import absolute_import - -from collections import defaultdict -import hashlib - -from django.contrib.sites.models import Site -from django.template.base import TemplateDoesNotExist -from django.template.loader import get_template_from_string -from django.template.loaders.cached import Loader as CachedLoader - - -class Loader(CachedLoader): - """ - This is an adaptation of Django's cached template loader. It differs in - that the cache is domain-based, so you can actually run more than one - site with one process. - - The load_template() method has been adapted from Django 1.6. - """ - - def __init__(self, *args, **kwargs): - super(Loader, self).__init__(*args, **kwargs) - self.template_cache = defaultdict(dict) - - def load_template(self, template_name, template_dirs=None): - domain = Site.objects.get_current().domain - key = template_name - if template_dirs: - # If template directories were specified, use a hash to differentiate - key = '-'.join([template_name, hashlib.sha1('|'.join(template_dirs)).hexdigest()]) - - try: - template = self.template_cache[domain][key] - except KeyError: - template, origin = self.find_template(template_name, template_dirs) - if not hasattr(template, 'render'): - try: - template = get_template_from_string(template, origin, template_name) - except TemplateDoesNotExist: - # If compiling the template we found raises TemplateDoesNotExist, - # back off to returning the source and display name for the template - # we were asked to load. This allows for correct identification (later) - # of the actual template that does not exist. - return template, origin - self.template_cache[domain][key] = template - return template, None - From 70d5b00f713e2119dd00631817002802b382fb1c Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 19 Apr 2018 11:09:03 +0100 Subject: [PATCH 5/9] Pin pytest-django version for Django 1.7 --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d647352..2547b8f 100644 --- a/tox.ini +++ b/tox.ini @@ -19,13 +19,14 @@ deps = coverage pytest pytest-cov - pytest-django pytest-pythonpath py27: mock + django2.0,django1.11,django1.10,django1.9,django1.8: pytest-django django2.0: Django>=2.0,<2.1 django1.11: Django>=1.11,<2.0 django1.10: Django>=1.10,<1.11 django1.9: Django>=1.9,<1.10 django1.8: Django>=1.8,<1.9 + django1.7: pytest-django<3.2.0 django1.7: Django>=1.7,<1.8 From 8037ec528fb8e3e4a20114a59357fde66526017d Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 19 Apr 2018 11:54:48 +0100 Subject: [PATCH 6/9] Remove code for unsupported Django versions (<1.7) --- multisite/hosts.py | 13 +---- multisite/models.py | 15 +----- multisite/south_migrations/0001_initial.py | 49 ------------------- ...__add_field_alias_redirect_to_canonical.py | 34 ------------- multisite/south_migrations/__init__.py | 0 5 files changed, 4 insertions(+), 107 deletions(-) delete mode 100644 multisite/south_migrations/0001_initial.py delete mode 100644 multisite/south_migrations/0002_auto__add_field_alias_redirect_to_canonical.py delete mode 100644 multisite/south_migrations/__init__.py diff --git a/multisite/hosts.py b/multisite/hosts.py index 18e1e9d..90525a9 100644 --- a/multisite/hosts.py +++ b/multisite/hosts.py @@ -1,21 +1,12 @@ from __future__ import unicode_literals from __future__ import absolute_import -from django.utils.functional import SimpleLazyObject - -from django import VERSION as django_version +from django.utils.functional import empty, SimpleLazyObject __ALL__ = ('ALLOWED_HOSTS', 'AllowedHosts') - -# In Django 1.3, LazyObject compares _wrapped against None, while in Django -# 1.4 and above, LazyObjects compares _wrapped against an instance of -# `object` stored in `empty`. -_wrapped_default = None -if django_version >= (1, 4, 0): - from django.utils.functional import empty - _wrapped_default = empty +_wrapped_default = empty class IterableLazyObject(SimpleLazyObject): diff --git a/multisite/models.py b/multisite/models.py index 9b724dd..48b493d 100644 --- a/multisite/models.py +++ b/multisite/models.py @@ -10,11 +10,7 @@ from django.db import connections, models, router from django.db.models import Q from django.db.models.signals import pre_save, post_save -try: - from django.db.models.signals import post_migrate -except ImportError: - # Django < 1.7 compatibility - from django.db.models.signals import post_syncdb as post_migrate +from django.db.models.signals import post_migrate from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ @@ -320,14 +316,7 @@ def site_created_hook(cls, sender, instance, raw, created, @classmethod def db_table_created_hook(cls, *args, **kwargs): """Syncs canonical Alias objects for all existing Site objects.""" - if kwargs.get('created_models'): - # For post_syncdb support in Django < 1.7: - # As before, only sync_all if Alias was in - # the list of created models - if cls in kwargs['created_models']: - Alias.canonical.sync_all() - else: - Alias.canonical.sync_all() + Alias.canonical.sync_all() # Hooks to handle Site objects being created or changed diff --git a/multisite/south_migrations/0001_initial.py b/multisite/south_migrations/0001_initial.py deleted file mode 100644 index 4a6c6d1..0000000 --- a/multisite/south_migrations/0001_initial.py +++ /dev/null @@ -1,49 +0,0 @@ -# encoding: utf-8 -from south.db import db -from south.v2 import SchemaMigration - - -class Migration(SchemaMigration): - def forwards(self, orm): - """Create Alias table.""" - - # Adding model 'Alias' - db.create_table('multisite_alias', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('site', self.gf('django.db.models.fields.related.ForeignKey')(related_name='aliases', to=orm['sites.Site'])), - ('domain', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)), - ('is_canonical', self.gf('django.db.models.fields.NullBooleanField')(default=None, null=True, blank=True)), - )) - db.send_create_signal('multisite', ['Alias']) - - # Adding unique constraint on 'Alias', - # fields ['is_canonical', 'site'] - db.create_unique('multisite_alias', ['is_canonical', 'site_id']) - - def backwards(self, orm): - """Drop Alias table.""" - - # Removing unique constraint on 'Alias', - # fields ['is_canonical', 'site'] - db.delete_unique('multisite_alias', ['is_canonical', 'site_id']) - - # Deleting model 'Alias' - db.delete_table('multisite_alias') - - models = { - 'multisite.alias': { - 'Meta': {'unique_together': "[('is_canonical', 'site')]", 'object_name': 'Alias'}, - 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_canonical': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'aliases'", 'to': "orm['sites.Site']"}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['multisite'] diff --git a/multisite/south_migrations/0002_auto__add_field_alias_redirect_to_canonical.py b/multisite/south_migrations/0002_auto__add_field_alias_redirect_to_canonical.py deleted file mode 100644 index 6854175..0000000 --- a/multisite/south_migrations/0002_auto__add_field_alias_redirect_to_canonical.py +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: utf-8 -from south.db import db -from south.v2 import SchemaMigration - - -class Migration(SchemaMigration): - def forwards(self, orm): - """Adding field 'Alias.redirect_to_canonical""" - db.add_column('multisite_alias', 'redirect_to_canonical', - self.gf('django.db.models.fields.BooleanField')(default=True), - keep_default=False) - - def backwards(self, orm): - """Deleting field 'Alias.redirect_to_canonical'""" - db.delete_column('multisite_alias', 'redirect_to_canonical') - - models = { - 'multisite.alias': { - 'Meta': {'unique_together': "[('is_canonical', 'site')]", 'object_name': 'Alias'}, - 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_canonical': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), - 'redirect_to_canonical': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'aliases'", 'to': "orm['sites.Site']"}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['multisite'] diff --git a/multisite/south_migrations/__init__.py b/multisite/south_migrations/__init__.py deleted file mode 100644 index e69de29..0000000 From 3596326a4b095f87da56a2892bb11abef8426269 Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 19 Apr 2018 12:24:40 +0100 Subject: [PATCH 7/9] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1a55763..70df889 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ parts MANIFEST multisite/*.egg-info .tox/ +.pytest_cache/ From 0d496bf1115ceca12c308e162f7e17b51f0b7cdc Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Thu, 19 Apr 2018 12:39:42 +0100 Subject: [PATCH 8/9] Update README to document development mode see https://github.com/ecometrica/django-multisite/pull/5 --- README.rst | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 6640113..ffa08aa 100644 --- a/README.rst +++ b/README.rst @@ -62,16 +62,16 @@ Add to your settings.py TEMPLATES loaders in the OPTIONS section:: ... ] -Or for Django 1.7 and earlier, add to settings.py TEMPLATES_LOADERS:: +Or for Django <= 1.7, add to settings.py TEMPLATES_LOADERS:: TEMPLATE_LOADERS = ( 'multisite.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ) -Edit settings.py MIDDLEWARE_CLASSES:: +Edit settings.py MIDDLEWARE (MIDDLEWARE_CLASSES for Django < 1.10):: - MIDDLEWARE_CLASSES = ( + MIDDLEWARE = ( ... 'multisite.middleware.DynamicSiteMiddleware', ... @@ -115,6 +115,26 @@ include wildcards.:: # will match any host ending '.example.com' +Development Environments +------------------------ +Multisite returns a valid Alias when in "development mode" (defaulting to the +alias associated with the default SiteID. + +Development mode is either: + - Running tests, i.e. manage.py test + - Running locally in settings.DEBUG = True, where the hostname is a + top-level name, i.e. localhost + +In order to have multisite use aliases in local environments, add entries to +your local etc/hosts file to match aliases in your applications. E.g. :: + + 127.0.0.1 example.com + 127.0.0.1 examplealias.com + +And access your application at example.com:8000 or examplealias.com:8000 instead of +the usual localhost:8000. + + Domain fallbacks ---------------- @@ -156,9 +176,9 @@ Cross-domain cookies In order to support `cross-domain cookies`_, for purposes like single-sign-on, prepend the following to the top of -settings.py MIDDLEWARE_CLASSES:: +settings.py MIDDLEWARE (MIDDLEWARE_CLASSES for Django < 1.10):: - MIDDLEWARE_CLASSES = ( + MIDDLEWARE = ( 'multisite.middleware.CookieDomainMiddleware', ... ) From dda2c3fcbb9b6926a626a0db0534248fbf5d76bf Mon Sep 17 00:00:00 2001 From: Becky Smith Date: Fri, 20 Apr 2018 15:36:55 +0100 Subject: [PATCH 9/9] Bump to version 1.5.0 --- CHANGELOG.rst | 8 ++++++++ multisite/__init__.py | 3 +-- multisite/__version__.py | 1 + setup.py | 11 ++++++++--- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 multisite/__version__.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5218b99..b7e45cb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,14 @@ Release Notes ============= +1.5.0 +----- +* Support Django 2.0 (PR #47 and #60) +* Remove code for Django < 1.7 +* Remove obsolete PathAssistedCurrentSiteManager +* Remove obsolete template.loaders.cached +* Update README to better describe local development setup + 1.4.1 ----- * Specify Django <2.0 in setup.py diff --git a/multisite/__init__.py b/multisite/__init__.py index 9f61133..56c7d5e 100644 --- a/multisite/__init__.py +++ b/multisite/__init__.py @@ -1,3 +1,2 @@ from .threadlocals import SiteDomain, SiteID - -VERSION = "1.4.0" +from .__version__ import __version__ diff --git a/multisite/__version__.py b/multisite/__version__.py new file mode 100644 index 0000000..77f1c8e --- /dev/null +++ b/multisite/__version__.py @@ -0,0 +1 @@ +__version__ = '1.5.0' diff --git a/setup.py b/setup.py index 9100255..ce287bd 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ +import os import sys from setuptools import find_packages, setup -import os _dir_ = os.path.dirname(__file__) @@ -17,11 +17,16 @@ def long_description(): with open(os.path.join(_dir_, 'README.rst')) as f: return f.read() +here = os.path.abspath(_dir_) +version = {} +with open(os.path.join(here, 'multisite', '__version__.py')) as f: + exec(f.read(), version) + files = ["multisite/test_templates/*"] setup(name='django-multisite', - version='1.4.1', + version=version['__version__'], description='Serve multiple sites from a single Django application', long_description=long_description(), author='Leonid S Shestera', @@ -52,4 +57,4 @@ def long_description(): 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries', 'Topic :: Utilities'], -) + )