Changeset View
Changeset View
Standalone View
Standalone View
bid_main/models.py
| from typing import Optional, Set | |||||
| import itertools | |||||
| import logging | import logging | ||||
| import typing | import re | ||||
| from django import urls | from django import urls | ||||
| from django.db import models, transaction | from django.db import models, transaction | ||||
| from django.db.models import Q | from django.db.models import Q | ||||
| from django.conf import settings | from django.conf import settings | ||||
| from django.core import validators | from django.core import validators | ||||
| from django.core.exceptions import ValidationError | from django.core.exceptions import ValidationError | ||||
| from django.core.mail import send_mail | from django.core.mail import send_mail | ||||
| from django.contrib.auth.models import PermissionsMixin | from django.contrib.auth.models import PermissionsMixin | ||||
| from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager | from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager | ||||
| from django.utils import timezone | from django.utils import timezone | ||||
| from django.utils.deconstruct import deconstructible | from django.utils.deconstruct import deconstructible | ||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||
| import sorl.thumbnail | import sorl.thumbnail | ||||
| import oauth2_provider.models as oa2_models | import oauth2_provider.models as oa2_models | ||||
| from . import fields | from . import fields | ||||
| from . import hashers | from . import hashers | ||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||
| nickname_illegal_chars = re.compile(r"[^\w.+-]") | |||||
| strip_email = re.compile("@.*$") | |||||
| def random_nums(): | |||||
| """Increasingly larger random number generator.""" | |||||
| import random | |||||
| lower, upper = 1, 5 | |||||
| while True: | |||||
| yield random.randint(lower, upper - 1) | |||||
| lower, upper = upper, upper * 3 | |||||
| class RoleManager(models.Manager): | class RoleManager(models.Manager): | ||||
| def badges(self) -> models.QuerySet: | def badges(self) -> models.QuerySet: | ||||
| """Query for those roles that are considered badges.""" | """Query for those roles that are considered badges.""" | ||||
| return ( | return ( | ||||
| self.filter(is_public=True, is_active=True, is_badge=True) | self.filter(is_public=True, is_active=True, is_badge=True) | ||||
| .exclude(badge_img__isnull=True) | .exclude(badge_img__isnull=True) | ||||
| ▲ Show 20 Lines • Show All 304 Lines • ▼ Show 20 Lines | def has_confirmed_email(self): | ||||
| return self.confirmed_email_at is not None | return self.confirmed_email_at is not None | ||||
| @property | @property | ||||
| def email_to_confirm(self): | def email_to_confirm(self): | ||||
| """The email to confirm, either 'email_change_preconfirm' when set, or 'email'.""" | """The email to confirm, either 'email_change_preconfirm' when set, or 'email'.""" | ||||
| return self.email_change_preconfirm or self.email | return self.email_change_preconfirm or self.email | ||||
| @property | @property | ||||
| def role_names(self) -> typing.Set[str]: | def role_names(self) -> Set[str]: | ||||
| return {role.name for role in self.roles.all()} | return {role.name for role in self.roles.all()} | ||||
| @property | @property | ||||
| def must_pp_agree(self) -> bool: | def must_pp_agree(self) -> bool: | ||||
| """Return True when user needs to agree to new privacy policy.""" | """Return True when user needs to agree to new privacy policy.""" | ||||
| return self.privacy_policy_agreed is None or self.privacy_policy_agreed < settings.PPDATE | return self.privacy_policy_agreed is None or self.privacy_policy_agreed < settings.PPDATE | ||||
| def revoke_all_tokens(self, date=None): | def revoke_all_tokens(self, date=None): | ||||
| ▲ Show 20 Lines • Show All 81 Lines • ▼ Show 20 Lines | def anonymize(self, *args, using=None, **kwargs) -> None: | ||||
| full_name='<deleted>', | full_name='<deleted>', | ||||
| nickname=nickname, | nickname=nickname, | ||||
| last_login_ip=None, | last_login_ip=None, | ||||
| current_login_ip=None, | current_login_ip=None, | ||||
| is_active=False, | is_active=False, | ||||
| avatar=None, | avatar=None, | ||||
| ) | ) | ||||
| @classmethod | |||||
| def generate_nickname( | |||||
| cls, email: Optional[str] = '', full_name: Optional[str] = '' | |||||
| ) -> str: | |||||
| """Return a unique nickname based on given full name or email.""" | |||||
| full_name = full_name.replace(" ", "-") | |||||
| email = strip_email.sub("", email) | |||||
| base = full_name or email | |||||
| log.debug("Generating unique nickname for base %r", base) | |||||
| def acceptable_nickname(name: str) -> bool: | |||||
| """Return True if the nickname is unique.""" | |||||
| return not cls.objects.filter(nickname=name).exists() | |||||
| base = nickname_illegal_chars.sub("", base) | |||||
| if acceptable_nickname(base): | |||||
| return base | |||||
| # Try increasingly larger random numbers as a suffix. | |||||
| for num in itertools.islice(random_nums(), 1000): | |||||
| nickname = f"{base}-{num}" | |||||
| if acceptable_nickname(nickname): | |||||
| return nickname | |||||
| raise ValueError( | |||||
| f"Unable to find unique name for base {base!r} after trying 1000 names" | |||||
| ) | |||||
| class SettingValueField(models.CharField): | class SettingValueField(models.CharField): | ||||
| def __init__(self, *args, **kwargs): # noqa: D107 | def __init__(self, *args, **kwargs): # noqa: D107 | ||||
| kwargs["max_length"] = 128 | kwargs["max_length"] = 128 | ||||
| super().__init__(*args, **kwargs) | super().__init__(*args, **kwargs) | ||||
| SETTING_DATA_TYPE_CHOICES = [ | SETTING_DATA_TYPE_CHOICES = [ | ||||
| ▲ Show 20 Lines • Show All 72 Lines • Show Last 20 Lines | |||||