From f59e570c4effacedb5900a2eb13353ba0659d631 Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Wed, 5 Feb 2025 16:43:25 -0500 Subject: [PATCH] Remove imports for builtin django User model (#2012) --- .gitignore | 3 +++ channels/api.py | 15 +++++++++++---- channels/views.py | 6 +++++- channels/views_test.py | 5 ++++- .../commands/backpopulate_favorites_lists.py | 4 +++- .../commands/populate_featured_lists.py | 4 +++- learning_resources/models.py | 19 ++++++++++++++----- learning_resources/serializers.py | 4 +++- learning_resources/utils.py | 10 ++++++++-- main/factories.py | 6 +++--- main/settings.py | 2 ++ profiles/views.py | 3 ++- profiles/views_test.py | 4 +++- pyproject.toml | 3 +++ 14 files changed, 67 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 4032c66543..b145bc50cd 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,6 @@ storybook-static/ /**/.yarn/cache .swc + +# ignore local ssl certs +certs/ diff --git a/channels/api.py b/channels/api.py index 4ecd51cfd5..8dcfd44d6f 100644 --- a/channels/api.py +++ b/channels/api.py @@ -1,11 +1,18 @@ """API for channels""" -from django.contrib.auth.models import Group, User +from typing import TYPE_CHECKING + +from django.contrib.auth.models import Group from django.db import transaction from channels.constants import CHANNEL_ROLE_CHOICES, CHANNEL_ROLE_MODERATORS from channels.models import Channel, ChannelGroupRole +if TYPE_CHECKING: + from django.contrib.auth import get_user_model + + User = get_user_model() + def create_channel_groups_and_roles( channel: Channel, @@ -31,14 +38,14 @@ def get_role_model(channel: Channel, role: str) -> ChannelGroupRole: return ChannelGroupRole.objects.get(channel=channel, role=role) -def add_user_role(channel: Channel, role: str, user: User): +def add_user_role(channel: Channel, role: str, user: "User"): """ Add a user to a channel role's group """ get_role_model(channel, role).group.user_set.add(user) -def remove_user_role(channel: Channel, role: str, user: User): +def remove_user_role(channel: Channel, role: str, user: "User"): """ Remove a user from a channel role's group """ @@ -51,7 +58,7 @@ def get_group_role_name(channel_id: int, role: str) -> str: return f"channel_{channel_name}_{role}" -def is_moderator(user: User, channel_id: int) -> bool: +def is_moderator(user: "User", channel_id: int) -> bool: """ Determine if the user is a moderator for a channel (or a staff user) """ diff --git a/channels/views.py b/channels/views.py index 2fc2114211..331fbe84e2 100644 --- a/channels/views.py +++ b/channels/views.py @@ -3,7 +3,7 @@ import logging from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.db.models import Prefetch from django.utils.decorators import method_decorator from django_filters.rest_framework import DjangoFilterBackend @@ -169,6 +169,8 @@ def get_queryset(self): """ Build a queryset of relevant users with moderator permissions for this channel """ + User = get_user_model() + channel_group_name = get_group_role_name( self.kwargs["id"], CHANNEL_ROLE_MODERATORS, @@ -190,6 +192,8 @@ class ChannelModeratorDetailView(APIView): def delete(self, request, *args, **kwargs): # noqa: ARG002 """Remove the user from the moderator groups for this website""" + User = get_user_model() + user = User.objects.get(username=self.kwargs["moderator_name"]) remove_user_role( Channel.objects.get(id=self.kwargs["id"]), CHANNEL_ROLE_MODERATORS, user diff --git a/channels/views_test.py b/channels/views_test.py index 02a71ecd4f..705312fcdd 100644 --- a/channels/views_test.py +++ b/channels/views_test.py @@ -5,7 +5,8 @@ from math import ceil import pytest -from django.contrib.auth.models import Group, User +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from django.urls import reverse from channels.api import add_user_role @@ -26,6 +27,8 @@ pytestmark = pytest.mark.django_db +User = get_user_model() + def test_list_channels(user_client): """Test that all channels are returned""" diff --git a/learning_resources/management/commands/backpopulate_favorites_lists.py b/learning_resources/management/commands/backpopulate_favorites_lists.py index 3acf31b138..bc6dc78266 100644 --- a/learning_resources/management/commands/backpopulate_favorites_lists.py +++ b/learning_resources/management/commands/backpopulate_favorites_lists.py @@ -1,6 +1,6 @@ """Management command to create user Favorites lists""" -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.core.management import BaseCommand from learning_resources.constants import FAVORITES_TITLE @@ -24,6 +24,8 @@ def add_arguments(self, parser): def handle(self, *args, **options): # noqa: ARG002 """Create a Favorites userlist for every active user""" + User = get_user_model() + if options["delete"]: self.stdout.write("Deleting all existing Favorites userlists") UserList.objects.filter(title=FAVORITES_TITLE).delete() diff --git a/learning_resources/management/commands/populate_featured_lists.py b/learning_resources/management/commands/populate_featured_lists.py index 675c78f3b3..dae2ddfa4a 100644 --- a/learning_resources/management/commands/populate_featured_lists.py +++ b/learning_resources/management/commands/populate_featured_lists.py @@ -3,7 +3,7 @@ import sys from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.core.management import BaseCommand from channels.models import Channel @@ -18,6 +18,8 @@ ) from main.utils import clear_search_cache, now_in_utc +User = get_user_model() + class Command(BaseCommand): """create dev-only featured lists for offeror channels""" diff --git a/learning_resources/models.py b/learning_resources/models.py index cc4878d522..c3db10b55c 100644 --- a/learning_resources/models.py +++ b/learning_resources/models.py @@ -3,9 +3,9 @@ import uuid from abc import abstractmethod from functools import cached_property -from typing import Optional +from typing import TYPE_CHECKING, Optional -from django.contrib.auth.models import User +from django.conf import settings from django.contrib.postgres.fields import ArrayField from django.db import models from django.db.models import CharField, Count, JSONField, OuterRef, Prefetch, Q @@ -25,6 +25,11 @@ ) from main.models import TimestampedModel, TimestampedModelQuerySet +if TYPE_CHECKING: + from django.contrib.auth import get_user_model + + User = get_user_model() + def default_delivery(): """Return the default delivery as a list""" @@ -328,7 +333,7 @@ def __str__(self): class LearningResourceQuerySet(TimestampedModelQuerySet): """QuerySet for LearningResource""" - def for_serialization(self, *, user: User | None = None): + def for_serialization(self, *, user: Optional["User"] = None): """Return the list of prefetches""" return ( self.prefetch_related( @@ -792,7 +797,9 @@ class LearningPath(LearningResourceDetailModel): on_delete=models.CASCADE, ) author = models.ForeignKey( - User, related_name="learning_paths", on_delete=models.PROTECT + settings.AUTH_USER_MODEL, + related_name="learning_paths", + on_delete=models.PROTECT, ) def __str__(self): @@ -896,7 +903,9 @@ class UserList(TimestampedModel): """ author = models.ForeignKey( - User, on_delete=models.deletion.CASCADE, related_name="user_lists" + settings.AUTH_USER_MODEL, + on_delete=models.deletion.CASCADE, + related_name="user_lists", ) title = models.CharField(max_length=256) description = models.TextField(default="", blank=True) diff --git a/learning_resources/serializers.py b/learning_resources/serializers.py index bc4aa05713..068a36daa6 100644 --- a/learning_resources/serializers.py +++ b/learning_resources/serializers.py @@ -4,7 +4,7 @@ from decimal import Decimal from uuid import uuid4 -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.db import transaction from django.db.models import F, Max from drf_spectacular.utils import extend_schema_field @@ -845,6 +845,8 @@ def get_item_count(self, instance) -> int: def create(self, validated_data): """Create a new user list""" + User = get_user_model() + request = self.context.get("request") if request and hasattr(request, "user") and isinstance(request.user, User): validated_data["author"] = request.user diff --git a/learning_resources/utils.py b/learning_resources/utils.py index 56da4f8e32..899d65e75a 100644 --- a/learning_resources/utils.py +++ b/learning_resources/utils.py @@ -2,13 +2,14 @@ import logging import re +from typing import TYPE_CHECKING import rapidjson import requests import yaml from botocore.exceptions import ClientError from django.conf import settings -from django.contrib.auth.models import Group, User +from django.contrib.auth.models import Group from django.db import transaction from django.db.models import Q from retry import retry @@ -33,6 +34,11 @@ log = logging.getLogger() +if TYPE_CHECKING: + from django.contrib.auth import get_user_model + + User = get_user_model() + def user_list_image_upload_uri(instance, filename): """ @@ -229,7 +235,7 @@ def parse_instructors(staff): return instructors -def update_editor_group(user: User, is_editor: False): +def update_editor_group(user: "User", is_editor: False): """Assign or unassign user to staff list editors group""" group, _ = Group.objects.get_or_create(name=GROUP_STAFF_LISTS_EDITORS) if is_editor: diff --git a/main/factories.py b/main/factories.py index 8f338ed84b..499c599cdb 100644 --- a/main/factories.py +++ b/main/factories.py @@ -3,7 +3,7 @@ """ import ulid -from django.contrib.auth.models import Group, User +from django.conf import settings from factory import LazyFunction, RelatedFactory, SubFactory, Trait from factory.django import DjangoModelFactory from factory.fuzzy import FuzzyText @@ -21,7 +21,7 @@ class UserFactory(DjangoModelFactory): profile = RelatedFactory("profiles.factories.ProfileFactory", "user") class Meta: - model = User + model = settings.AUTH_USER_MODEL skip_postgeneration_save = True class Params: @@ -34,7 +34,7 @@ class GroupFactory(DjangoModelFactory): name = FuzzyText() class Meta: - model = Group + model = "auth.Group" class UserSocialAuthFactory(DjangoModelFactory): diff --git a/main/settings.py b/main/settings.py index c01b64b580..d798222c54 100644 --- a/main/settings.py +++ b/main/settings.py @@ -69,6 +69,8 @@ ALLOWED_HOSTS = ["*"] +AUTH_USER_MODEL = "auth.User" + SECURE_SSL_REDIRECT = get_bool("MITOL_SECURE_SSL_REDIRECT", True) # noqa: FBT003 SITE_ID = 1 diff --git a/profiles/views.py b/profiles/views.py index c157209d67..802584d8e2 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -3,7 +3,6 @@ from cairosvg import svg2png # pylint:disable=no-name-in-module from django.contrib.auth import get_user_model from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect from django.utils.decorators import method_decorator @@ -137,6 +136,8 @@ def name_initials_avatar_view( bgcolor, ): # pylint:disable=unused-argument """View for initial avatar""" + User = get_user_model() + user = User.objects.filter(username=username).first() if not user: return redirect(DEFAULT_PROFILE_IMAGE) diff --git a/profiles/views_test.py b/profiles/views_test.py index 99a7a51043..19bef38c25 100644 --- a/profiles/views_test.py +++ b/profiles/views_test.py @@ -5,7 +5,7 @@ import pytest from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.urls import reverse from rest_framework import status @@ -25,6 +25,8 @@ pytestmark = [pytest.mark.django_db] +User = get_user_model() + def test_list_users(staff_client, staff_user): """ diff --git a/pyproject.toml b/pyproject.toml index f569373f2e..d16a073a9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -214,6 +214,9 @@ convention = "pep257" [tool.ruff.lint.flake8-quotes] inline-quotes = "double" +[tool.ruff.lint.flake8-tidy-imports.banned-api] +"django.contrib.auth.models.User".msg = "use get_user_model() or settings.AUTH_USER_MODEL" + [tool.ruff.lint.per-file-ignores] "*_test.py" = ["ARG001", "E501", "S101", "PLR2004"] "test_*.py" = ["ARG001", "E501", "S101", "PLR2004"]