diff --git a/settings/common.py b/settings/common.py index fdc7d140..3834fcb1 100644 --- a/settings/common.py +++ b/settings/common.py @@ -116,7 +116,7 @@ TEMPLATE_LOADERS = [ MIDDLEWARE_CLASSES = [ "taiga.base.middleware.cors.CoorsMiddleware", - "taiga.base.domains.middleware.DomainsMiddleware", + "taiga.domains.middleware.DomainsMiddleware", # Common middlewares "django.middleware.common.CommonMiddleware", @@ -154,10 +154,10 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", "taiga.base.users", - "taiga.base.domains", "taiga.base.notifications", "taiga.base.searches", "taiga.base", + "taiga.domains", "taiga.projects", "taiga.projects.mixins.blocked", "taiga.projects.milestones", diff --git a/taiga/base/auth/api.py b/taiga/base/auth/api.py index 3fc55d82..b543346b 100644 --- a/taiga/base/auth/api.py +++ b/taiga/base/auth/api.py @@ -11,8 +11,8 @@ from rest_framework.permissions import AllowAny from rest_framework import status, viewsets from taiga.base.decorators import list_route -from taiga.base.domains.models import DomainMember -from taiga.base.domains import get_active_domain +from taiga.domains.models import DomainMember +from taiga.domains import get_active_domain from taiga.base.users.models import User, Role from taiga.base.users.serializers import UserSerializer from taiga.base import exceptions as exc diff --git a/taiga/base/auth/tests/tests_auth.py b/taiga/base/auth/tests/tests_auth.py index 7072eebe..4adb3f6b 100644 --- a/taiga/base/auth/tests/tests_auth.py +++ b/taiga/base/auth/tests/tests_auth.py @@ -18,7 +18,7 @@ from taiga.base import auth from taiga.base.users.tests import create_user, create_domain from taiga.projects.tests import create_project -from taiga.base.domains.models import Domain, DomainMember +from taiga.domains.models import Domain, DomainMember from taiga.projects.models import Membership diff --git a/taiga/base/domains/admin.py b/taiga/base/domains/admin.py deleted file mode 100644 index d90935f8..00000000 --- a/taiga/base/domains/admin.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.contrib import admin - -from .models import Domain, DomainMember - -class DomainMemberInline(admin.TabularInline): - model = DomainMember - -class DomainAdmin(admin.ModelAdmin): - list_display = ('domain', 'name') - search_fields = ('domain', 'name') - inlines = [ DomainMemberInline, ] - -admin.site.register(Domain, DomainAdmin) diff --git a/taiga/base/domains/middleware.py b/taiga/base/domains/middleware.py deleted file mode 100644 index 36931857..00000000 --- a/taiga/base/domains/middleware.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- - -import json - -from django import http -from taiga.base import domains -from taiga.base.exceptions import format_exception - - -class DomainsMiddleware(object): - def process_request(self, request): - domain = request.META.get("HTTP_X_HOST", None) - if domain is not None: - try: - domain = domains.get_domain_for_domain_name(domain) - except domains.DomainNotFound as e: - detail = format_exception(e) - return http.HttpResponseBadRequest(json.dumps(detail)) - else: - domain = domains.get_default_domain() - - request.domain = domain - domains.activate(domain) - - def process_response(self, request, response): - domains.deactivate() - - if hasattr(request, "domain"): - response["X-Site-Host"] = request.domain.domain - - return response diff --git a/taiga/domains/__init__.py b/taiga/domains/__init__.py new file mode 100644 index 00000000..75b3c015 --- /dev/null +++ b/taiga/domains/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2014 Andrey Antukh +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# y ou may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .base import get_default_domain +from .base import get_domain_for_domain_name +from .base import activate +from .base import deactivate +from .base import get_active_domain +from .base import DomainNotFound + +__all__ = ["get_default_domain", + "get_domain_for_domain_name", + "activate", + "deactivate", + "get_active_domain", + "DomainNotFound"] diff --git a/taiga/domains/admin.py b/taiga/domains/admin.py new file mode 100644 index 00000000..32da3519 --- /dev/null +++ b/taiga/domains/admin.py @@ -0,0 +1,27 @@ +# Copyright 2014 Andrey Antukh +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# y ou may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.contrib import admin + +from .models import Domain, DomainMember + +class DomainMemberInline(admin.TabularInline): + model = DomainMember + +class DomainAdmin(admin.ModelAdmin): + list_display = ('domain', 'name') + search_fields = ('domain', 'name') + inlines = [ DomainMemberInline, ] + +admin.site.register(Domain, DomainAdmin) diff --git a/taiga/base/domains/api.py b/taiga/domains/api.py similarity index 69% rename from taiga/base/domains/api.py rename to taiga/domains/api.py index d9eb06d2..174e6dc4 100644 --- a/taiga/base/domains/api.py +++ b/taiga/domains/api.py @@ -1,4 +1,16 @@ -# -*- coding: utf-8 -*- +# Copyright 2014 Andrey Antukh +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# y ou may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from rest_framework import viewsets from rest_framework.response import Response @@ -7,8 +19,8 @@ from rest_framework.permissions import AllowAny, IsAuthenticated from django.http import Http404 from taiga.base.api import ModelCrudViewSet, UpdateModelMixin -from taiga.base.domains import get_active_domain +from .base import get_active_domain from .serializers import DomainSerializer, DomainMemberSerializer from .permissions import DomainMembersPermission, DomainPermission from .models import DomainMember, Domain diff --git a/taiga/base/domains/__init__.py b/taiga/domains/base.py similarity index 67% rename from taiga/base/domains/__init__.py rename to taiga/domains/base.py index 6f64e702..2b2d4243 100644 --- a/taiga/base/domains/__init__.py +++ b/taiga/domains/base.py @@ -1,49 +1,42 @@ # -*- coding: utf-8 -*- import logging -from threading import local +import functools +import threading from django.db.models import get_model from django.core.exceptions import ImproperlyConfigured from django.utils.translation import ugettext_lazy as _ -from .. import exceptions as exc - - -_local = local() +from taiga.base import exceptions as exc log = logging.getLogger("taiga.domains") +_local = threading.local() + class DomainNotFound(exc.BaseException): pass +@functools.lru_cache(maxsize=1) def get_default_domain(): from django.conf import settings try: - sid = settings.SITE_ID + sid = settings.DOMAIN_ID except AttributeError: raise ImproperlyConfigured("You're using the \"domains framework\" without having " "set the DOMAIN_ID setting. Create a domain in your database " "and set the DOMAIN_ID setting to fix this error.") model_cls = get_model("domains", "Domain") - cached = getattr(_local, "default_domain", None) - if cached is None: - try: - cached = _local.default_domain = model_cls.objects.get(pk=sid) - except model_cls.DoesNotExist: - raise ImproperlyConfigured("default domain not found on database.") + try: + return model_cls.objects.get(pk=sid) + except model_cls.DoesNotExist: + raise ImproperlyConfigured("default domain not found on database.") - return cached - - -def get_domain_for_domain_name(domain): +@functools.lru_cache(maxsize=100, typed=True) +def get_domain_for_domain_name(domain:str, follow_alias:bool=True): log.debug("Trying activate domain for domain name: {}".format(domain)) - cache = getattr(_local, "cache", {}) - - if domain in cache: - return cache[domain] model_cls = get_model("domains", "Domain") @@ -52,11 +45,12 @@ def get_domain_for_domain_name(domain): except model_cls.DoesNotExist: log.warning("Domain does not exist for domain: {}".format(domain)) raise DomainNotFound(_("domain not found")) - else: - cache[domain] = domain - return domain + # Use `alias_of_id` instead of simple `alias_of` for performace reasons. + if domain.alias_of_id is None or not follow_alias: + return domain + return domain.alias_of def activate(domain): log.debug("Activating domain: {}".format(domain)) @@ -75,9 +69,7 @@ def get_active_domain(): return get_default_domain() return active_domain -def clear_domain_cache(**kwargs): - if hasattr(_local, "default_domain"): - del _local.default_domain - if hasattr(_local, "cache"): - del _local.cache +def clear_domain_cache(**kwargs): + get_default_domain.cache_clear() + get_domain_for_domain_name.cache_clear() diff --git a/taiga/base/domains/fixtures/initial_domains.json b/taiga/domains/fixtures/initial_domains.json similarity index 100% rename from taiga/base/domains/fixtures/initial_domains.json rename to taiga/domains/fixtures/initial_domains.json diff --git a/taiga/domains/middleware.py b/taiga/domains/middleware.py new file mode 100644 index 00000000..29dfacb4 --- /dev/null +++ b/taiga/domains/middleware.py @@ -0,0 +1,54 @@ +# Copyright 2014 Andrey Antukh +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# y ou may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json + +from django import http +from taiga.base.exceptions import format_exception + +from .base import get_domain_for_domain_name +from .base import activate as activate_domain +from .base import deactivate as deactivate_domain +from .base import get_default_domain +from .base import DomainNotFound + + +class DomainsMiddleware(object): + """ + Domain middlewate: process request and try resolve domain + from HTTP_X_HOST header. If no header is specified, one + default is used. + """ + + def process_request(self, request): + domain = request.META.get("HTTP_X_HOST", None) + if domain is not None: + try: + domain = get_domain_for_domain_name(domain, follow_alias=True) + except DomainNotFound as e: + detail = format_exception(e) + return http.HttpResponseBadRequest(json.dumps(detail)) + else: + domain = get_default_domain() + + request.domain = domain + activate_domain(domain) + + def process_response(self, request, response): + deactivate_domain() + + if hasattr(request, "domain"): + response["X-Site-Host"] = request.domain.domain + + return response diff --git a/taiga/base/domains/migrations/0001_initial.py b/taiga/domains/migrations/0001_initial.py similarity index 100% rename from taiga/base/domains/migrations/0001_initial.py rename to taiga/domains/migrations/0001_initial.py diff --git a/taiga/base/domains/migrations/0002_auto__add_field_domainmember_domain__chg_field_domainmember_site.py b/taiga/domains/migrations/0002_auto__add_field_domainmember_domain__chg_field_domainmember_site.py similarity index 100% rename from taiga/base/domains/migrations/0002_auto__add_field_domainmember_domain__chg_field_domainmember_site.py rename to taiga/domains/migrations/0002_auto__add_field_domainmember_domain__chg_field_domainmember_site.py diff --git a/taiga/base/domains/migrations/0003_change_sites_for_domains.py b/taiga/domains/migrations/0003_change_sites_for_domains.py similarity index 100% rename from taiga/base/domains/migrations/0003_change_sites_for_domains.py rename to taiga/domains/migrations/0003_change_sites_for_domains.py diff --git a/taiga/base/domains/migrations/0004_auto__del_field_domainmember_site__del_unique_domainmember_site_user__.py b/taiga/domains/migrations/0004_auto__del_field_domainmember_site__del_unique_domainmember_site_user__.py similarity index 100% rename from taiga/base/domains/migrations/0004_auto__del_field_domainmember_site__del_unique_domainmember_site_user__.py rename to taiga/domains/migrations/0004_auto__del_field_domainmember_site__del_unique_domainmember_site_user__.py diff --git a/taiga/base/domains/migrations/0005_auto__add_field_domain_default_language.py b/taiga/domains/migrations/0005_auto__add_field_domain_default_language.py similarity index 100% rename from taiga/base/domains/migrations/0005_auto__add_field_domain_default_language.py rename to taiga/domains/migrations/0005_auto__add_field_domain_default_language.py diff --git a/taiga/domains/migrations/0006_auto__add_field_domain_alias_of.py b/taiga/domains/migrations/0006_auto__add_field_domain_alias_of.py new file mode 100644 index 00000000..b78cb4c2 --- /dev/null +++ b/taiga/domains/migrations/0006_auto__add_field_domain_alias_of.py @@ -0,0 +1,89 @@ +# -*- 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): + # Adding field 'Domain.alias_of' + db.add_column('domains_domain', 'alias_of', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['domains.Domain'], default=None, blank=True, null=True, related_name='+'), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Domain.alias_of' + db.delete_column('domains_domain', 'alias_of_id') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + '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': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'object_name': 'Permission', 'unique_together': "(('content_type', 'codename'),)", 'ordering': "('content_type__app_label', 'content_type__model', 'codename')"}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'object_name': 'ContentType', 'unique_together': "(('app_label', 'model'),)", 'ordering': "('name',)", 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + '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'}) + }, + 'domains.domain': { + 'Meta': {'object_name': 'Domain', 'ordering': "('domain',)"}, + 'alias_of': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'default': 'None', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}), + 'default_language': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '20'}), + 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'public_register': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'scheme': ('django.db.models.fields.CharField', [], {'default': 'None', 'null': 'True', 'max_length': '60'}) + }, + 'domains.domainmember': { + 'Meta': {'object_name': 'DomainMember', 'unique_together': "(('domain', 'user'),)", 'ordering': "['email']"}, + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'null': 'True', 'related_name': "'members'"}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'null': 'True', 'related_name': "'+'"}) + }, + 'users.user': { + 'Meta': {'object_name': 'User', 'ordering': "['username']"}, + 'color': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "'#28261c'", 'max_length': '9'}), + 'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_language': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '20'}), + 'default_timezone': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': "''", 'max_length': '20'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '75'}), + 'first_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '30'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True', 'symmetrical': 'False', 'related_name': "'user_set'"}), + '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', [], {'blank': 'True', 'max_length': '30'}), + 'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'notify_level': ('django.db.models.fields.CharField', [], {'default': "'all_owned_projects'", 'max_length': '32'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'photo': ('django.db.models.fields.files.FileField', [], {'blank': 'True', 'null': 'True', 'max_length': '500'}), + 'token': ('django.db.models.fields.CharField', [], {'blank': 'True', 'default': 'None', 'null': 'True', 'max_length': '200'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True', 'symmetrical': 'False', 'related_name': "'user_set'"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + } + } + + complete_apps = ['domains'] \ No newline at end of file diff --git a/taiga/base/domains/migrations/__init__.py b/taiga/domains/migrations/__init__.py similarity index 100% rename from taiga/base/domains/migrations/__init__.py rename to taiga/domains/migrations/__init__.py diff --git a/taiga/base/domains/models.py b/taiga/domains/models.py similarity index 77% rename from taiga/base/domains/models.py rename to taiga/domains/models.py index 1f5ab093..211eed1c 100644 --- a/taiga/base/domains/models.py +++ b/taiga/domains/models.py @@ -1,4 +1,16 @@ -# -*- coding: utf-8 -*- +# Copyright 2014 Andrey Antukh +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# y ou may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import string @@ -8,7 +20,7 @@ from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError -from . import clear_domain_cache +from .base import clear_domain_cache def _simple_domain_name_validator(value): @@ -38,6 +50,9 @@ class Domain(models.Model): default_language = models.CharField(max_length=20, null=False, blank=True, default="", verbose_name=_("default language")) + alias_of = models.ForeignKey("self", null=True, default=None, blank=True, + verbose_name=_("Mark as alias of"), related_name="+") + class Meta: verbose_name = _('domain') verbose_name_plural = _('domain') diff --git a/taiga/base/domains/permissions.py b/taiga/domains/permissions.py similarity index 53% rename from taiga/base/domains/permissions.py rename to taiga/domains/permissions.py index d6a0d752..48a309c0 100644 --- a/taiga/base/domains/permissions.py +++ b/taiga/domains/permissions.py @@ -1,7 +1,21 @@ +# Copyright 2014 Andrey Antukh +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# y ou may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from rest_framework import permissions -from taiga.base.domains.models import DomainMember -from taiga.base.domains import get_active_domain +from .models import DomainMember +from .base import get_active_domain class DomainPermission(permissions.BasePermission): diff --git a/taiga/base/domains/serializers.py b/taiga/domains/serializers.py similarity index 52% rename from taiga/base/domains/serializers.py rename to taiga/domains/serializers.py index 5976f3da..51ff718c 100644 --- a/taiga/base/domains/serializers.py +++ b/taiga/domains/serializers.py @@ -1,4 +1,16 @@ -# -*- coding: utf-8 -*- +# Copyright 2014 Andrey Antukh +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# y ou may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from rest_framework import serializers from taiga.base.users.serializers import UserSerializer diff --git a/taiga/domains/tests.py b/taiga/domains/tests.py new file mode 100644 index 00000000..a20d8724 --- /dev/null +++ b/taiga/domains/tests.py @@ -0,0 +1,118 @@ +# Copyright 2014 Andrey Antukh +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# y ou may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django import test +from django.test.utils import override_settings +from django.core.exceptions import ImproperlyConfigured +from django.db.models import get_model +from django.http import HttpResponse + +from . import base +from .models import Domain +from .middleware import DomainsMiddleware + + +class DomainCoreTests(test.TestCase): + fixtures = ["initial_domains.json"] + + def setUp(self): + base.clear_domain_cache() + + @override_settings(DOMAIN_ID=1) + def test_get_default_domain(self): + default_domain = base.get_default_domain() + self.assertEqual(default_domain.domain, "localhost") + + @override_settings(DOMAIN_ID=2) + def test_get_wrong_default_domain(self): + with self.assertRaises(ImproperlyConfigured): + default_domain = base.get_default_domain() + + def test_get_domain_by_name(self): + domain = base.get_domain_for_domain_name("localhost") + self.assertEqual(domain.id, 1) + self.assertEqual(domain.domain, "localhost") + + def test_get_domain_by_name_aliased(self): + main_domain = base.get_default_domain() + aliased_domain = Domain.objects.create(domain="beta.localhost", scheme="http", + alias_of=main_domain) + + resolved_domain = base.get_domain_for_domain_name("beta.localhost", follow_alias=False) + self.assertEqual(resolved_domain.domain, "beta.localhost") + + resolved_domain = base.get_domain_for_domain_name("beta.localhost", follow_alias=True) + self.assertEqual(resolved_domain.domain, "localhost") + + def test_lru_cache_for_get_default_domain(self): + with self.assertNumQueries(1): + base.get_default_domain() + base.get_default_domain() + + def test_lru_cache_for_get_domain_for_domain_name(self): + with self.assertNumQueries(2): + base.get_domain_for_domain_name("localhost", follow_alias=True) + base.get_domain_for_domain_name("localhost", follow_alias=True) + base.get_domain_for_domain_name("localhost", follow_alias=False) + base.get_domain_for_domain_name("localhost", follow_alias=False) + + def test_activate_deactivate_domain(self): + main_domain = base.get_default_domain() + aliased_domain = Domain.objects.create(domain="beta.localhost", scheme="http", + alias_of=main_domain) + + self.assertEqual(base.get_active_domain(), main_domain) + + base.activate(aliased_domain) + self.assertEqual(base.get_active_domain(), aliased_domain) + + base.deactivate() + self.assertEqual(base.get_active_domain(), main_domain) + + +from django.test.client import RequestFactory + +class DomainMiddlewareTests(test.TestCase): + fixtures = ["initial_domains.json"] + + def setUp(self): + self.main_domain = base.get_default_domain() + self.aliased_domain = Domain.objects.create(domain="beta.localhost", scheme="http", + alias_of=self.main_domain) + self.factory = RequestFactory() + + def test_process_request(self): + request = self.factory.get("/", HTTP_X_HOST="beta.localhost") + middleware = DomainsMiddleware() + ret = middleware.process_request(request) + + self.assertEqual(request.domain, self.main_domain) + self.assertEqual(ret, None) + + def test_process_request_with_wrong_domain(self): + request = self.factory.get("/", HTTP_X_HOST="beta2.localhost") + middleware = DomainsMiddleware() + ret = middleware.process_request(request) + + self.assertFalse(hasattr(request, "domain")) + self.assertNotEqual(ret, None) + self.assertIsInstance(ret, HttpResponse) + + def test_process_request_without_host_header(self): + request = self.factory.get("/") + middleware = DomainsMiddleware() + ret = middleware.process_request(request) + + self.assertEqual(request.domain, self.main_domain) + self.assertEqual(ret, None) diff --git a/taiga/front/__init__.py b/taiga/front/__init__.py index 46ea7231..1222c35b 100644 --- a/taiga/front/__init__.py +++ b/taiga/front/__init__.py @@ -3,8 +3,7 @@ from django.conf import settings from django_jinja import library -from taiga.base import domains - +from taiga import domains URLS = { "home": "/", diff --git a/taiga/projects/api.py b/taiga/projects/api.py index 640a554d..0904ff25 100644 --- a/taiga/projects/api.py +++ b/taiga/projects/api.py @@ -13,12 +13,12 @@ from rest_framework import status from djmail.template_mail import MagicMailBuilder +from taiga.domains import get_active_domain from taiga.base import filters from taiga.base import exceptions as exc from taiga.base.decorators import list_route, detail_route from taiga.base.permissions import has_project_perm from taiga.base.api import ModelCrudViewSet, ModelListViewSet, RetrieveModelMixin -from taiga.base.domains import get_active_domain from taiga.base.users.models import Role from taiga.base.notifications.api import NotificationSenderMixin from taiga.projects.aggregates.tags import get_all_tags diff --git a/taiga/projects/models.py b/taiga/projects/models.py index 3f270a85..3c0ceca7 100644 --- a/taiga/projects/models.py +++ b/taiga/projects/models.py @@ -19,11 +19,12 @@ from django.utils import timezone from picklefield.fields import PickledObjectField import reversion +from taiga.domains.models import DomainMember +from taiga.projects.userstories.models import UserStory from taiga.base.utils.slug import slugify_uniquely from taiga.base.utils.dicts import dict_sum -from taiga.base.domains.models import DomainMember from taiga.base.users.models import Role -from taiga.projects.userstories.models import UserStory + from . import choices diff --git a/taiga/projects/permissions.py b/taiga/projects/permissions.py index 6005b278..6a753bf3 100644 --- a/taiga/projects/permissions.py +++ b/taiga/projects/permissions.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from taiga.base.permissions import BasePermission -from taiga.base.domains import get_active_domain +from taiga.domains import get_active_domain class ProjectPermission(BasePermission): diff --git a/taiga/projects/tests/__init__.py b/taiga/projects/tests/__init__.py index ed8c9719..3a003f3f 100644 --- a/taiga/projects/tests/__init__.py +++ b/taiga/projects/tests/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.db.models.loading import get_model -from taiga.base.domains import get_active_domain +from taiga.domains import get_active_domain def create_project(id, owner, save=True): diff --git a/taiga/routers.py b/taiga/routers.py index 453eb21c..f26e60c5 100644 --- a/taiga/routers.py +++ b/taiga/routers.py @@ -4,12 +4,12 @@ from taiga.base import routers from taiga.base.auth.api import AuthViewSet from taiga.base.users.api import UsersViewSet, PermissionsViewSet from taiga.base.searches.api import SearchViewSet -from taiga.base.domains.api import DomainViewSet, DomainMembersViewSet from taiga.base.resolver.api import ResolverViewSet from taiga.projects.api import (ProjectViewSet, MembershipViewSet, InvitationViewSet, UserStoryStatusViewSet, PointsViewSet, TaskStatusViewSet, IssueStatusViewSet, IssueTypeViewSet, PriorityViewSet, SeverityViewSet, ProjectAdminViewSet, RolesViewSet) #, QuestionStatusViewSet) +from taiga.domains.api import DomainViewSet, DomainMembersViewSet from taiga.projects.milestones.api import MilestoneViewSet from taiga.projects.userstories.api import UserStoryViewSet, UserStoryAttachmentViewSet from taiga.projects.tasks.api import TaskViewSet, TaskAttachmentViewSet