diff --git a/AUTHORS.rst b/AUTHORS.rst index b4a52014..2b829ce1 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -28,6 +28,7 @@ answer newbie questions, and generally made taiga that much better: - Joe Letts - Julien Palard - luyikei +- Motius GmbH - Ricky Posner - Yamila Moreno - Yaser Alraddadi diff --git a/taiga/auth/services.py b/taiga/auth/services.py index 49f9ceaa..5015a02e 100644 --- a/taiga/auth/services.py +++ b/taiga/auth/services.py @@ -25,6 +25,7 @@ not uses clasess and uses simple functions. """ from django.apps import apps +from django.contrib.auth import get_user_model from django.db import transaction as tx from django.db import IntegrityError from django.utils.translation import ugettext as _ @@ -69,7 +70,7 @@ def is_user_already_registered(*, username:str, email:str) -> (bool, str): and in case he does whats the duplicated attribute """ - user_model = apps.get_model("users", "User") + user_model = get_user_model() if user_model.objects.filter(username=username): return (True, _("Username is already in use.")) @@ -110,7 +111,7 @@ def public_register(username:str, password:str, email:str, full_name:str): if is_registered: raise exc.WrongArguments(reason) - user_model = apps.get_model("users", "User") + user_model = get_user_model() user = user_model(username=username, email=email, full_name=full_name) @@ -159,7 +160,7 @@ def private_register_for_new_user(token:str, username:str, email:str, if is_registered: raise exc.WrongArguments(reason) - user_model = apps.get_model("users", "User") + user_model = get_user_model() user = user_model(username=username, email=email, full_name=full_name) diff --git a/taiga/auth/tokens.py b/taiga/auth/tokens.py index 4d809e38..58fe2938 100644 --- a/taiga/auth/tokens.py +++ b/taiga/auth/tokens.py @@ -14,7 +14,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - +from django.contrib.auth import get_user_model from taiga.base import exceptions as exc from django.apps import apps @@ -47,7 +47,7 @@ def get_user_for_token(token, scope, max_age=None): except signing.BadSignature: raise exc.NotAuthenticated(_("Invalid token")) - model_cls = apps.get_model("users", "User") + model_cls = get_user_model() try: user = model_cls.objects.get(pk=data["user_%s_id" % (scope)]) diff --git a/taiga/base/management/commands/test_emails.py b/taiga/base/management/commands/test_emails.py index a3e99688..53a63a87 100644 --- a/taiga/base/management/commands/test_emails.py +++ b/taiga/base/management/commands/test_emails.py @@ -20,6 +20,7 @@ import datetime from optparse import make_option from django.apps import apps +from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand from django.utils import timezone @@ -28,7 +29,6 @@ from taiga.base.mails import mail_builder from taiga.projects.models import Project, Membership from taiga.projects.history.models import HistoryEntry from taiga.projects.history.services import get_history_queryset_by_model_instance -from taiga.users.models import User class Command(BaseCommand): @@ -50,7 +50,7 @@ class Command(BaseCommand): # Register email context = {"lang": locale, - "user": User.objects.all().order_by("?").first(), + "user": get_user_model().objects.all().order_by("?").first(), "cancel_token": "cancel-token"} email = mail_builder.registered_user(test_email, context) @@ -58,7 +58,7 @@ class Command(BaseCommand): # Membership invitation membership = Membership.objects.order_by("?").filter(user__isnull=True).first() - membership.invited_by = User.objects.all().order_by("?").first() + membership.invited_by = get_user_model().objects.all().order_by("?").first() membership.invitation_extra_text = "Text example, Text example,\nText example,\n\nText example" context = {"lang": locale, "membership": membership} @@ -88,19 +88,19 @@ class Command(BaseCommand): email.send() # Password recovery - context = {"lang": locale, "user": User.objects.all().order_by("?").first()} + context = {"lang": locale, "user": get_user_model().objects.all().order_by("?").first()} email = mail_builder.password_recovery(test_email, context) email.send() # Change email - context = {"lang": locale, "user": User.objects.all().order_by("?").first()} + context = {"lang": locale, "user": get_user_model().objects.all().order_by("?").first()} email = mail_builder.change_email(test_email, context) email.send() # Export/Import emails context = { "lang": locale, - "user": User.objects.all().order_by("?").first(), + "user": get_user_model().objects.all().order_by("?").first(), "project": Project.objects.all().order_by("?").first(), "error_subject": "Error generating project dump", "error_message": "Error generating project dump", @@ -109,7 +109,7 @@ class Command(BaseCommand): email.send() context = { "lang": locale, - "user": User.objects.all().order_by("?").first(), + "user": get_user_model().objects.all().order_by("?").first(), "error_subject": "Error importing project dump", "error_message": "Error importing project dump", } @@ -120,7 +120,7 @@ class Command(BaseCommand): context = { "lang": locale, "url": "http://dummyurl.com", - "user": User.objects.all().order_by("?").first(), + "user": get_user_model().objects.all().order_by("?").first(), "project": Project.objects.all().order_by("?").first(), "deletion_date": deletion_date, } @@ -129,7 +129,7 @@ class Command(BaseCommand): context = { "lang": locale, - "user": User.objects.all().order_by("?").first(), + "user": get_user_model().objects.all().order_by("?").first(), "project": Project.objects.all().order_by("?").first(), } email = mail_builder.load_dump(test_email, context) @@ -157,9 +157,9 @@ class Command(BaseCommand): context = { "lang": locale, "project": Project.objects.all().order_by("?").first(), - "changer": User.objects.all().order_by("?").first(), + "changer": get_user_model().objects.all().order_by("?").first(), "history_entries": HistoryEntry.objects.all().order_by("?")[0:5], - "user": User.objects.all().order_by("?").first(), + "user": get_user_model().objects.all().order_by("?").first(), } for notification_email in notification_emails: diff --git a/taiga/export_import/serializers.py b/taiga/export_import/serializers.py index 49d574ca..55b2031d 100644 --- a/taiga/export_import/serializers.py +++ b/taiga/export_import/serializers.py @@ -21,6 +21,7 @@ import os from collections import OrderedDict from django.apps import apps +from django.contrib.auth import get_user_model from django.core.files.base import ContentFile from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError @@ -263,7 +264,7 @@ class WatcheableObjectModelSerializer(serializers.ModelSerializer): adding_watcher_emails = list(new_watcher_emails.difference(old_watcher_emails)) removing_watcher_emails = list(old_watcher_emails.difference(new_watcher_emails)) - User = apps.get_model("users", "User") + User = get_user_model() adding_users = User.objects.filter(email__in=adding_watcher_emails) removing_users = User.objects.filter(email__in=removing_watcher_emails) diff --git a/taiga/front/sitemaps/users.py b/taiga/front/sitemaps/users.py index 675839a0..5e956dd7 100644 --- a/taiga/front/sitemaps/users.py +++ b/taiga/front/sitemaps/users.py @@ -16,6 +16,7 @@ # along with this program. If not, see . from django.apps import apps +from django.contrib.auth import get_user_model from taiga.front.templatetags.functions import resolve @@ -24,7 +25,7 @@ from .base import Sitemap class UsersSitemap(Sitemap): def items(self): - user_model = apps.get_model("users", "User") + user_model = get_user_model() # Only active users and not system users queryset = user_model.objects.filter(is_active=True, diff --git a/taiga/hooks/bitbucket/services.py b/taiga/hooks/bitbucket/services.py index a1b12d9b..9b2b8d21 100644 --- a/taiga/hooks/bitbucket/services.py +++ b/taiga/hooks/bitbucket/services.py @@ -17,10 +17,10 @@ import uuid +from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from django.conf import settings -from taiga.users.models import User from taiga.base.utils.urls import get_absolute_url @@ -43,4 +43,4 @@ def get_or_generate_config(project): def get_bitbucket_user(user_id): - return User.objects.get(is_system=True, username__startswith="bitbucket") + return get_user_model().objects.get(is_system=True, username__startswith="bitbucket") diff --git a/taiga/hooks/github/services.py b/taiga/hooks/github/services.py index b4114449..830406bf 100644 --- a/taiga/hooks/github/services.py +++ b/taiga/hooks/github/services.py @@ -17,9 +17,9 @@ import uuid +from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse -from taiga.users.models import User from taiga.users.models import AuthData from taiga.base.utils.urls import get_absolute_url @@ -49,6 +49,6 @@ def get_github_user(github_id): pass if user is None: - user = User.objects.get(is_system=True, username__startswith="github") + user = get_user_model().objects.get(is_system=True, username__startswith="github") return user diff --git a/taiga/hooks/gitlab/services.py b/taiga/hooks/gitlab/services.py index 2b2af849..d80fe62c 100644 --- a/taiga/hooks/gitlab/services.py +++ b/taiga/hooks/gitlab/services.py @@ -17,10 +17,10 @@ import uuid +from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from django.conf import settings -from taiga.users.models import User from taiga.base.utils.urls import get_absolute_url @@ -47,11 +47,11 @@ def get_gitlab_user(user_email): if user_email: try: - user = User.objects.get(email=user_email) - except User.DoesNotExist: + user = get_user_model().objects.get(email=user_email) + except get_user_model().DoesNotExist: pass if user is None: - user = User.objects.get(is_system=True, username__startswith="gitlab") + user = get_user_model().objects.get(is_system=True, username__startswith="gitlab") return user diff --git a/taiga/mdrender/extensions/mentions.py b/taiga/mdrender/extensions/mentions.py index 2dcea03a..683250d2 100644 --- a/taiga/mdrender/extensions/mentions.py +++ b/taiga/mdrender/extensions/mentions.py @@ -22,13 +22,12 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from django.contrib.auth import get_user_model from markdown.extensions import Extension from markdown.inlinepatterns import Pattern from markdown.util import etree, AtomicString -from taiga.users.models import User - class MentionsExtension(Extension): def extendMarkdown(self, md, md_globals): @@ -43,8 +42,8 @@ class MentionsPattern(Pattern): username = m.group(3) try: - user = User.objects.get(username=username) - except User.DoesNotExist: + user = get_user_model().objects.get(username=username) + except get_user_model().DoesNotExist: return "@{}".format(username) url = "/profile/{}".format(username) diff --git a/taiga/projects/history/freeze_impl.py b/taiga/projects/history/freeze_impl.py index 07de4bfc..34f4139d 100644 --- a/taiga/projects/history/freeze_impl.py +++ b/taiga/projects/history/freeze_impl.py @@ -19,10 +19,10 @@ from contextlib import suppress from functools import partial from django.apps import apps +from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist -from taiga.base.utils.urls import get_absolute_url from taiga.base.utils.iterators import as_tuple from taiga.base.utils.iterators import as_dict from taiga.mdrender.service import render as mdrender @@ -49,7 +49,7 @@ def _get_generic_values(ids:tuple, *, typename=None, attr:str="name") -> tuple: @as_dict def _get_users_values(ids:set) -> dict: - user_model = apps.get_model("users", "User") + user_model = get_user_model() ids = filter(lambda x: x is not None, ids) qs = user_model.objects.filter(pk__in=tuple(ids)) diff --git a/taiga/projects/history/models.py b/taiga/projects/history/models.py index 24526235..d440900c 100644 --- a/taiga/projects/history/models.py +++ b/taiga/projects/history/models.py @@ -14,12 +14,10 @@ import uuid -from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.db import models -from django.apps import apps +from django.contrib.auth import get_user_model from django.utils.functional import cached_property -from django.conf import settings from django_pgjson.fields import JsonField from taiga.mdrender.service import get_diff_of_htmls @@ -96,7 +94,7 @@ class HistoryEntry(models.Model): @cached_property def owner(self): pk = self.user["pk"] - model = apps.get_model("users", "User") + model = get_user_model() try: return model.objects.get(pk=pk) except model.DoesNotExist: diff --git a/taiga/projects/issues/api.py b/taiga/projects/issues/api.py index 0a93250b..c0ade2f4 100644 --- a/taiga/projects/issues/api.py +++ b/taiga/projects/issues/api.py @@ -16,25 +16,21 @@ # along with this program. If not, see . from django.utils.translation import ugettext as _ -from django.db.models import Q from django.http import HttpResponse from taiga.base import filters from taiga.base import exceptions as exc from taiga.base import response -from taiga.base.decorators import detail_route, list_route +from taiga.base.decorators import list_route from taiga.base.api import ModelCrudViewSet, ModelListViewSet from taiga.base.api.mixins import BlockedByProjectMixin from taiga.base.api.utils import get_object_or_404 -from taiga.users.models import User - from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin from taiga.projects.occ import OCCResourceMixin from taiga.projects.history.mixins import HistoryResourceMixin from taiga.projects.models import Project, IssueStatus, Severity, Priority, IssueType -from taiga.projects.milestones.models import Milestone from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin from . import models diff --git a/taiga/projects/likes/serializers.py b/taiga/projects/likes/serializers.py index ce321f24..7d28f8f8 100644 --- a/taiga/projects/likes/serializers.py +++ b/taiga/projects/likes/serializers.py @@ -16,16 +16,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from taiga.base.api import serializers -from taiga.base.fields import TagsField +from django.contrib.auth import get_user_model -from taiga.users.models import User -from taiga.users.services import get_photo_or_gravatar_url +from taiga.base.api import serializers class FanSerializer(serializers.ModelSerializer): full_name = serializers.CharField(source='get_full_name', required=False) class Meta: - model = User + model = get_user_model() fields = ('id', 'username', 'full_name') diff --git a/taiga/projects/notifications/mixins.py b/taiga/projects/notifications/mixins.py index 2751b2bd..fb3cada7 100644 --- a/taiga/projects/notifications/mixins.py +++ b/taiga/projects/notifications/mixins.py @@ -14,15 +14,11 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - from functools import partial from operator import is_not -from django.apps import apps -from django.contrib.contenttypes.models import ContentType +from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist -from django.db import models -from django.utils.translation import ugettext_lazy as _ from taiga.base import response from taiga.base.decorators import detail_route @@ -34,8 +30,6 @@ from taiga.projects.notifications.utils import (attach_watchers_to_queryset, attach_is_watcher_to_queryset, attach_total_watchers_to_queryset) -from taiga.users.models import User -from . import models from . serializers import WatcherSerializer @@ -224,9 +218,9 @@ class EditableWatchedResourceModelSerializer(WatchedResourceModelSerializer): adding_watcher_ids = list(new_watcher_ids.difference(old_watcher_ids)) removing_watcher_ids = list(old_watcher_ids.difference(new_watcher_ids)) - User = apps.get_model("users", "User") - adding_users = User.objects.filter(id__in=adding_watcher_ids) - removing_users = User.objects.filter(id__in=removing_watcher_ids) + User = get_user_model() + adding_users = get_user_model().objects.filter(id__in=adding_watcher_ids) + removing_users = get_user_model().objects.filter(id__in=removing_watcher_ids) for user in adding_users: services.add_watcher(obj, user) @@ -273,7 +267,7 @@ class WatchersViewSetMixin: try: self.object = resource.get_watchers().get(pk=pk) - except ObjectDoesNotExist: # or User.DoesNotExist + except ObjectDoesNotExist: # or User.DoesNotExist return response.NotFound() serializer = self.get_serializer(self.object) diff --git a/taiga/projects/notifications/models.py b/taiga/projects/notifications/models.py index 8eb0db27..9c36fe75 100644 --- a/taiga/projects/notifications/models.py +++ b/taiga/projects/notifications/models.py @@ -33,7 +33,7 @@ class NotifyPolicy(models.Model): project user notifications preference. """ project = models.ForeignKey("projects.Project", related_name="notify_policies") - user = models.ForeignKey("users.User", related_name="notify_policies") + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="notify_policies") notify_level = models.SmallIntegerField(choices=NOTIFY_LEVEL_CHOICES) created_at = models.DateTimeField(default=timezone.now) @@ -57,7 +57,7 @@ class HistoryChangeNotification(models.Model): or updated when an object requires notifications. """ key = models.CharField(max_length=255, unique=False, editable=False) - owner = models.ForeignKey("users.User", null=False, blank=False, + owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False, verbose_name=_("owner"), related_name="+") created_datetime = models.DateTimeField(null=False, blank=False, auto_now_add=True, verbose_name=_("created date time")) @@ -66,7 +66,7 @@ class HistoryChangeNotification(models.Model): history_entries = models.ManyToManyField("history.HistoryEntry", verbose_name=_("history entries"), related_name="+") - notify_users = models.ManyToManyField("users.User", + notify_users = models.ManyToManyField(settings.AUTH_USER_MODEL, verbose_name=_("notify users"), related_name="+") project = models.ForeignKey("projects.Project", null=False, blank=False, diff --git a/taiga/projects/notifications/serializers.py b/taiga/projects/notifications/serializers.py index 8eab93a0..ed93dce7 100644 --- a/taiga/projects/notifications/serializers.py +++ b/taiga/projects/notifications/serializers.py @@ -15,13 +15,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import json - from taiga.base.api import serializers -from taiga.users.models import User +from taiga.users.models import get_user_model_safe from . import models -from . import choices class NotifyPolicySerializer(serializers.ModelSerializer): @@ -39,5 +36,5 @@ class WatcherSerializer(serializers.ModelSerializer): full_name = serializers.CharField(source='get_full_name', required=False) class Meta: - model = User + model = get_user_model_safe() fields = ('id', 'username', 'full_name') diff --git a/taiga/projects/notifications/services.py b/taiga/projects/notifications/services.py index 9730f0c8..0a3cb8e7 100644 --- a/taiga/projects/notifications/services.py +++ b/taiga/projects/notifications/services.py @@ -20,7 +20,6 @@ import datetime from functools import partial from django.apps import apps -from django.db.transaction import atomic from django.db import IntegrityError, transaction from django.db.models import Q from django.contrib.contenttypes.models import ContentType @@ -37,7 +36,6 @@ from taiga.projects.history.services import (make_key_from_model_object, get_last_snapshot_for_key, get_model_from_key) from taiga.permissions.service import user_has_perm -from taiga.users.models import User from .models import HistoryChangeNotification, Watched @@ -214,7 +212,7 @@ def send_notifications(obj, *, history): return None key = make_key_from_model_object(obj) - owner = User.objects.get(pk=history.user["pk"]) + owner = get_user_model().objects.get(pk=history.user["pk"]) notification, created = (HistoryChangeNotification.objects.select_for_update() .get_or_create(key=key, owner=owner, diff --git a/taiga/projects/services/stats.py b/taiga/projects/services/stats.py index 6e12ff06..0972dc6b 100644 --- a/taiga/projects/services/stats.py +++ b/taiga/projects/services/stats.py @@ -22,8 +22,6 @@ import datetime import copy import collections -from taiga.projects.history.models import HistoryEntry -from taiga.projects.userstories.models import RolePoints def _count_status_object(status_obj, counting_storage): if status_obj.id in counting_storage: @@ -228,6 +226,7 @@ def _get_milestones_stats_for_backlog(project, milestones): def get_stats_for_project(project): # Let's fetch all the estimations related to a project with all the necesary # related data + RolePoints = apps.get_model('userstories', 'RolePoints') role_points = RolePoints.objects.filter( user_story__project = project, ).prefetch_related( @@ -378,6 +377,7 @@ def _get_wiki_changes_per_member_stats(project): # Wiki changes wiki_changes = {} wiki_page_keys = ["wiki.wikipage:%s"%id for id in project.wiki_pages.values_list("id", flat=True)] + HistoryEntry = apps.get_model('history', 'HistoryEntry') history_entries = HistoryEntry.objects.filter(key__in=wiki_page_keys).values('user') for entry in history_entries: editions = wiki_changes.get(entry["user"]["pk"], 0) diff --git a/taiga/projects/votes/serializers.py b/taiga/projects/votes/serializers.py index af9ffdfa..78fc94c4 100644 --- a/taiga/projects/votes/serializers.py +++ b/taiga/projects/votes/serializers.py @@ -16,16 +16,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from taiga.base.api import serializers -from taiga.base.fields import TagsField +from django.contrib.auth import get_user_model -from taiga.users.models import User -from taiga.users.services import get_photo_or_gravatar_url +from taiga.base.api import serializers class VoterSerializer(serializers.ModelSerializer): full_name = serializers.CharField(source='get_full_name', required=False) class Meta: - model = User + model = get_user_model() fields = ('id', 'username', 'full_name') diff --git a/taiga/stats/services.py b/taiga/stats/services.py index 26934be3..45ae60e5 100644 --- a/taiga/stats/services.py +++ b/taiga/stats/services.py @@ -14,6 +14,7 @@ from django.apps import apps +from django.contrib.auth import get_user_model from django.db.models import Count from django.db.models import Q from django.utils import timezone @@ -27,7 +28,7 @@ from collections import OrderedDict ########################################################################### def get_users_public_stats(): - model = apps.get_model("users", "User") + model = get_user_model() queryset = model.objects.filter(is_active=True, is_system=False) stats = OrderedDict() diff --git a/taiga/timeline/api.py b/taiga/timeline/api.py index e68ba949..82dea784 100644 --- a/taiga/timeline/api.py +++ b/taiga/timeline/api.py @@ -14,7 +14,8 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - +from django.conf import settings +from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.apps import apps @@ -49,7 +50,7 @@ class TimelineViewSet(ReadOnlyListViewSet): page = self.paginate_queryset(queryset) if page is not None: user_ids = list(set([obj.data.get("user", {}).get("id", None) for obj in page.object_list])) - User = apps.get_model("users", "User") + User = get_user_model() users = {u.id: u for u in User.objects.filter(id__in=user_ids)} for obj in page.object_list: @@ -99,7 +100,7 @@ class TimelineViewSet(ReadOnlyListViewSet): class ProfileTimeline(TimelineViewSet): - content_type = "users.user" + content_type = settings.AUTH_USER_MODEL.lower() permission_classes = (permissions.UserTimelinePermission,) def get_timeline(self, user): @@ -107,7 +108,7 @@ class ProfileTimeline(TimelineViewSet): class UserTimeline(TimelineViewSet): - content_type = "users.user" + content_type = settings.AUTH_USER_MODEL.lower() permission_classes = (permissions.UserTimelinePermission,) def get_timeline(self, user): diff --git a/taiga/timeline/apps.py b/taiga/timeline/apps.py index 3cdfbc8c..c9df776d 100644 --- a/taiga/timeline/apps.py +++ b/taiga/timeline/apps.py @@ -17,6 +17,7 @@ from django.apps import AppConfig from django.apps import apps +from django.contrib.auth import get_user_model from django.db.models import signals @@ -35,4 +36,4 @@ class TimelineAppConfig(AppConfig): signals.post_delete.connect(handlers.delete_membership_push_to_timeline, sender=apps.get_model("projects", "Membership")) signals.post_save.connect(handlers.create_user_push_to_timeline, - sender=apps.get_model("users", "User")) + sender=get_user_model()) diff --git a/taiga/timeline/management/commands/_rebuild_timeline_for_user_creation.py b/taiga/timeline/management/commands/_rebuild_timeline_for_user_creation.py index fdf8ef49..726664f9 100644 --- a/taiga/timeline/management/commands/_rebuild_timeline_for_user_creation.py +++ b/taiga/timeline/management/commands/_rebuild_timeline_for_user_creation.py @@ -18,24 +18,22 @@ # Examples: # python manage.py rebuild_timeline_for_user_creation --settings=settings.local_timeline -from django.conf import settings +from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ObjectDoesNotExist from django.core.management.base import BaseCommand from django.db.models import Model -from django.db import reset_queries from django.test.utils import override_settings from taiga.timeline.service import (_get_impl_key_from_model, _timeline_impl_map, extract_user_info) from taiga.timeline.models import Timeline from taiga.timeline.signals import _push_to_timelines -from taiga.users.models import User from unittest.mock import patch import gc + class BulkCreator(object): def __init__(self): self.timeline_objects = [] @@ -75,7 +73,7 @@ def custom_add_to_object_timeline(obj:object, instance:object, event_type:str, c def generate_timeline(): with patch('taiga.timeline.service._add_to_object_timeline', new=custom_add_to_object_timeline): # Users api wasn't a HistoryResourceMixin so we can't interate on the HistoryEntries in this case - users = User.objects.order_by("date_joined") + users = get_user_model().objects.order_by("date_joined") for user in users.iterator(): print("User:", user.date_joined) extra_data = { @@ -87,6 +85,7 @@ def generate_timeline(): bulk_creator.flush() + class Command(BaseCommand): help = 'Regenerate project timeline' diff --git a/taiga/timeline/management/commands/rebuild_timeline.py b/taiga/timeline/management/commands/rebuild_timeline.py index 5bf7d340..6214d129 100644 --- a/taiga/timeline/management/commands/rebuild_timeline.py +++ b/taiga/timeline/management/commands/rebuild_timeline.py @@ -20,24 +20,17 @@ # python manage.py rebuild_timeline --settings=settings.local_timeline --purge # python manage.py rebuild_timeline --settings=settings.local_timeline --initial_date 2014-10-02 -from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.core.management.base import BaseCommand from django.db.models import Model -from django.db import reset_queries from django.test.utils import override_settings - from taiga.projects.models import Project -from taiga.projects.history import services as history_services -from taiga.projects.history.choices import HistoryType from taiga.projects.history.models import HistoryEntry from taiga.timeline.models import Timeline -from taiga.timeline.service import (_add_to_object_timeline, _get_impl_key_from_model, - _timeline_impl_map, extract_user_info) +from taiga.timeline.service import _get_impl_key_from_model,_timeline_impl_map, extract_user_info from taiga.timeline.signals import on_new_history_entry, _push_to_timelines -from taiga.users.models import User from unittest.mock import patch from optparse import make_option diff --git a/taiga/timeline/serializers.py b/taiga/timeline/serializers.py index 882f62bc..d4a1563c 100644 --- a/taiga/timeline/serializers.py +++ b/taiga/timeline/serializers.py @@ -16,6 +16,7 @@ # along with this program. If not, see . from django.apps import apps +from django.contrib.auth import get_user_model from django.forms import widgets from taiga.base.api import serializers @@ -37,7 +38,7 @@ class TimelineSerializer(serializers.ModelSerializer): if hasattr(obj, "_prefetched_user"): user = obj._prefetched_user else: - User = apps.get_model("users", "User") + User = get_user_model() userData = obj.data.get("user", None) try: user = User.objects.get(id=userData["id"]) diff --git a/taiga/timeline/signals.py b/taiga/timeline/signals.py index f307fa4a..887688fc 100644 --- a/taiga/timeline/signals.py +++ b/taiga/timeline/signals.py @@ -16,14 +16,12 @@ # along with this program. If not, see . from django.conf import settings +from django.contrib.auth import get_user_model from django.utils import timezone from django.utils.translation import ugettext as _ from taiga.projects.history import services as history_services -from taiga.projects.models import Project -from taiga.users.models import User from taiga.projects.history.choices import HistoryType -from taiga.projects.notifications import services as notifications_services from taiga.timeline.service import (push_to_timeline, build_user_namespace, build_project_namespace, @@ -93,7 +91,7 @@ def on_new_history_entry(sender, instance, created, **kwargs): elif instance.type == HistoryType.delete: event_type = "delete" - user = User.objects.get(id=instance.user["pk"]) + user = get_user_model().objects.get(id=instance.user["pk"]) values_diff = instance.values_diff _clean_description_fields(values_diff) diff --git a/taiga/users/models.py b/taiga/users/models.py index 054167cc..bff7a025 100644 --- a/taiga/users/models.py +++ b/taiga/users/models.py @@ -15,19 +15,22 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from importlib import import_module + import random import re -import uuid from django.apps import apps +from django.apps.config import MODELS_MODULE_NAME from django.conf import settings +from django.contrib.auth.models import UserManager, AbstractBaseUser from django.contrib.contenttypes.models import ContentType +from django.core import validators +from django.core.exceptions import AppRegistryNotReady from django.db import models from django.dispatch import receiver -from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth.models import UserManager, AbstractBaseUser -from django.core import validators from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ from django_pgjson.fields import JsonField from djorm_pgarray.fields import TextArrayField @@ -39,7 +42,42 @@ from taiga.permissions.permissions import MEMBERS_PERMISSIONS from taiga.projects.choices import BLOCKED_BY_OWNER_LEAVING from taiga.projects.notifications.choices import NotifyLevel -from easy_thumbnails.files import get_thumbnailer + +def get_user_model_safe(): + """ + Fetches the user model using the app registry. + This doesn't require that an app with the given app label exists, + which makes it safe to call when the registry is being populated. + All other methods to access models might raise an exception about the + registry not being ready yet. + Raises LookupError if model isn't found. + + Based on: https://github.com/django-oscar/django-oscar/blob/1.0/oscar/core/loading.py#L310-L340 + Ongoing Django issue: https://code.djangoproject.com/ticket/22872 + """ + user_app, user_model = settings.AUTH_USER_MODEL.split('.') + + try: + return apps.get_model(user_app, user_model) + except AppRegistryNotReady: + if apps.apps_ready and not apps.models_ready: + # If this function is called while `apps.populate()` is + # loading models, ensure that the module that defines the + # target model has been imported and try looking the model up + # in the app registry. This effectively emulates + # `from path.to.app.models import Model` where we use + # `Model = get_model('app', 'Model')` instead. + app_config = apps.get_app_config(user_app) + # `app_config.import_models()` cannot be used here because it + # would interfere with `apps.populate()`. + import_module('%s.%s' % (app_config.name, MODELS_MODULE_NAME)) + # In order to account for case-insensitivity of model_name, + # look up the model through a private API of the app registry. + return apps.get_registered_model(user_app, user_model) + else: + # This must be a different case (e.g. the model really doesn't + # exist). We just re-raise the exception. + raise def generate_random_hex_color(): @@ -281,7 +319,7 @@ class Role(models.Model): class AuthData(models.Model): - user = models.ForeignKey("users.User", related_name="auth_data") + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="auth_data") key = models.SlugField(max_length=50) value = models.CharField(max_length=300) extra = JsonField() diff --git a/taiga/users/services.py b/taiga/users/services.py index e13355ad..c55b4cdb 100644 --- a/taiga/users/services.py +++ b/taiga/users/services.py @@ -20,6 +20,7 @@ This model contains a domain logic for users application. """ from django.apps import apps +from django.contrib.auth import get_user_model from django.db.models import Q from django.db import connection from django.conf import settings @@ -40,7 +41,7 @@ from .gravatar import get_gravatar_url def get_user_by_username_or_email(username_or_email): - user_model = apps.get_model("users", "User") + user_model = get_user_model() qs = user_model.objects.filter(Q(username__iexact=username_or_email) | Q(email__iexact=username_or_email)) diff --git a/taiga/webhooks/apps.py b/taiga/webhooks/apps.py index 90890262..a10cd1d2 100644 --- a/taiga/webhooks/apps.py +++ b/taiga/webhooks/apps.py @@ -27,8 +27,10 @@ def connect_webhooks_signals(): dispatch_uid="webhooks") + + def disconnect_webhooks_signals(): - signals.post_save.disconnect(sender=HistoryEntry, dispatch_uid="webhooks") + signals.post_save.disconnect(sender=apps.get_model("history", "HistoryEntry"), dispatch_uid="webhooks") class WebhooksAppConfig(AppConfig): diff --git a/tests/factories.py b/tests/factories.py index 71de1044..252ce47a 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -165,7 +165,7 @@ class WikiAttachmentFactory(Factory): class UserFactory(Factory): class Meta: - model = "users.User" + model = settings.AUTH_USER_MODEL strategy = factory.CREATE_STRATEGY username = factory.Sequence(lambda n: "user{}".format(n)) diff --git a/tests/integration/test_importer_api.py b/tests/integration/test_importer_api.py index 9d4e8d0b..2d392fbd 100644 --- a/tests/integration/test_importer_api.py +++ b/tests/integration/test_importer_api.py @@ -132,7 +132,6 @@ def test_valid_project_with_enough_public_projects_slots(client): client.login(user) response = client.json.post(url, json.dumps(data)) - print(response.content) assert response.status_code == 201 assert Project.objects.filter(slug="public-project-with-slots").count() == 1 diff --git a/tests/integration/test_watch_tasks.py b/tests/integration/test_watch_tasks.py index 8c716560..38ddd40b 100644 --- a/tests/integration/test_watch_tasks.py +++ b/tests/integration/test_watch_tasks.py @@ -109,7 +109,6 @@ def test_get_task_is_watcher(client): assert response.data['is_watcher'] == False response = client.post(url_watch) - print(response.data) assert response.status_code == 200 response = client.get(url_detail) diff --git a/tests/unit/test_slug.py b/tests/unit/test_slug.py index 0bebf51f..9cb4ef5f 100644 --- a/tests/unit/test_slug.py +++ b/tests/unit/test_slug.py @@ -16,9 +16,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from taiga.projects.models import Project -from taiga.users.models import User +from django.contrib.auth import get_user_model +from taiga.projects.models import Project from taiga.base.utils.slug import slugify import pytest @@ -38,7 +38,7 @@ def test_slugify_3(): def test_project_slug_with_special_chars(): - user = User.objects.create(username="test") + user = get_user_model().objects.create(username="test") project = Project.objects.create(name="漢字", description="漢字", owner=user) project.save() @@ -46,7 +46,7 @@ def test_project_slug_with_special_chars(): def test_project_with_existing_name_slug_with_special_chars(): - user = User.objects.create(username="test") + user = get_user_model().objects.create(username="test") Project.objects.create(name="漢字", description="漢字", owner=user) project = Project.objects.create(name="漢字", description="漢字", owner=user) diff --git a/tests/unit/test_timeline.py b/tests/unit/test_timeline.py index afebbc09..e34906d6 100644 --- a/tests/unit/test_timeline.py +++ b/tests/unit/test_timeline.py @@ -18,19 +18,18 @@ from unittest.mock import patch, call -from django.core.exceptions import ValidationError +from django.contrib.auth import get_user_model from taiga.timeline import service from taiga.timeline.models import Timeline from taiga.projects.models import Project -from taiga.users.models import User import pytest def test_push_to_timeline_many_objects(): with patch("taiga.timeline.service._add_to_object_timeline") as mock: - users = [User(), User(), User()] + users = [get_user_model(), get_user_model(), get_user_model()] project = Project() service.push_to_timeline(users, project, "test", project.created_date) assert mock.call_count == 3 @@ -45,7 +44,7 @@ def test_push_to_timeline_many_objects(): def test_add_to_objects_timeline(): with patch("taiga.timeline.service._add_to_object_timeline") as mock: - users = [User(), User(), User()] + users = [get_user_model(), get_user_model(), get_user_model()] project = Project() service._add_to_objects_timeline(users, project, "test", project.created_date) assert mock.call_count == 3