Source code for django_tinyuser.models

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.conf import settings as django_settings
from django_tinyuser import settings
from django_tinyuser.managers import TinyUserManager
from django_tinyuser.enums import (
    FriendshipStatus,
    FriendshipBlockedStatus
)


[docs] class TinyUser(AbstractBaseUser, PermissionsMixin): """ Custom user model for the TinyUser application. :param AbstractBaseUser: Base class for custom user models. :type AbstractBaseUser: django.contrib.auth.models.AbstractBaseUser :param PermissionsMixin: Mixin class to add permission fields and methods. :type PermissionsMixin: django.contrib.auth.models.PermissionsMixin :return: The created user instance. :rtype: TinyUser """ #: The email field is used as the unique identifier for authentication #: instead of the default username field. #: #: It is required and must be unique. email = models.EmailField( unique=True, db_column='email_address', verbose_name=_('email address') ) #: The username field is a unique identifier for the user, used for display #: purposes and as an additional identifier. #: #: It is required and must be unique. username = models.CharField( max_length=127, unique=True, db_column='username', verbose_name=_('username') ) #: The is_active field indicates whether the user's account is active. #: Inactive accounts may not be able to log in. #: #: It is a boolean field that defaults to True. is_active = models.BooleanField( default=True, db_column='is_active', verbose_name=_('active') ) #: The is_staff field indicates whether the user has staff status, which #: allows access to the admin site. #: #: It is a boolean field that defaults to False. is_staff = models.BooleanField( default=False, db_column='is_staff', verbose_name=_('staff status') ) #: The is_superuser field indicates whether the user has superuser status, #: which grants all permissions. #: #: It is a boolean field that defaults to False. is_superuser = models.BooleanField( default=False, db_column='is_superuser', verbose_name=_('superuser status') ) #: The is_verified field indicates whether the user's email address has #: been verified. This can be used to restrict access to certain features #: until the email is verified. #: #: It is a boolean field that defaults to False. is_verified = models.BooleanField( default=False, db_column='is_verified', verbose_name=_('verified') ) #: The joined_at field stores the date and time when the user account was created. #: It is automatically set to the current date and time when the user is created. joined_at = models.DateTimeField( default=timezone.now, db_column='joined_at', verbose_name=_('joined at') ) objects = TinyUserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] @property def display_name(self): """ Returns the display name of the user. :return: The display name of the user. :rtype: str """ self.profile = getattr(self, 'profile', None) if not self.profile and self.id: try: self.profile = TinyUserProfile.objects.filter(user_id=self.id).first() except TinyUserProfile.DoesNotExist: self.profile = TinyUserProfile.objects.create(user=self, first_name='', last_name='', bio='') if self.profile: if (self.profile.first_name or self.profile.last_name): return f"{self.profile.first_name} {self.profile.last_name}".strip() elif self.profile.first_name: return self.profile.first_name elif self.profile.last_name: return self.profile.last_name return self.username def __str__(self): return self.username class Meta: verbose_name = _('user') verbose_name_plural = _('users') if settings.TINYUSER_EXTERNAL_MANAGED or settings.AUTH_EXTERNAL_MANAGED: managed = False else: managed = True if settings.USE_POSTGRESQL_SCHEMAS: db_table = f"{settings.POSTGRESQL_AUTH_SCHEMA}.tinyuser_user" else: db_table = 'tinyuser_user' indexes = [ models.Index(fields=['email'], name='email_idx'), models.Index(fields=['username'], name='username_idx'), ]
[docs] class TinyUserProfile(models.Model): """Model to store additional profile information for TinyUser.""" #: The user field is a one-to-one relationship with the TinyUser model, linking each profile to a specific user. #: It is required and will be deleted if the associated user is deleted. user = models.OneToOneField( django_settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile') #: The first_name field stores the user's first name. #: It is optional and can be left blank. first_name = models.CharField( max_length=30, blank=True, ) #: The last_name field stores the user's last name. #: It is optional and can be left blank. last_name = models.CharField(max_length=30, blank=True) #: The bio field allows users to provide a short biography or description #: about themselves. It is optional and can be left blank. bio = models.TextField(blank=True) def __str__(self): return f"{self.user.username}'s profile" class Meta: verbose_name = _('user profile') verbose_name_plural = _('user profiles') if settings.AUTH_EXTERNAL_MANAGED: managed = False else: managed = True if settings.USE_POSTGRESQL_SCHEMAS: db_table = f"{settings.POSTGRESQL_AUTH_SCHEMA}.tinyuser_profile" else: db_table = 'tinyuser_profile' indexes = [ models.Index(fields=['user'], name='user_idx'), ]
[docs] class UserFriendGroup(models.Model): """Model to represent groups of friends for TinyUser instances.""" #: The name field is a character field that stores the name of the friend group. #: It is required and has a maximum length of 255 characters. name = models.CharField(max_length=255) description_type = models.TextField(_('description type'), default='text', choices=[ ('text', _('Text')), ('markdown', _('Markdown')), ('bbcode', _('BBCode')) ]) #: The description field is a text field that allows users to provide a description of the friend group. #: It is optional and can be left blank. description = models.TextField(blank=True) user = models.ForeignKey( django_settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='user_friend_groups' ) #: The members field is a many-to-many relationship with the TinyUser model, #: allowing multiple users to be part of the same friend group. members = models.ManyToManyField(django_settings.AUTH_USER_MODEL, related_name='friend_groups')
[docs] def add_member(self, user): """Add a user to the friend group. :param user: The user to be added to the friend group. :type user: TinyUser """ self.members.add(user)
[docs] def remove_member(self, user): """Remove a user from the friend group. :param user: The user to be removed from the friend group. :type user: TinyUser """ self.members.remove(user)
[docs] def is_member(self, user): """Check if a user is a member of the friend group. :param user: The user to check for membership in the friend group. :type user: TinyUser :return: True if the user is a member of the friend group, False otherwise. :rtype: bool """ if self.owner == user: return True return self.members.filter(id=user.id).exists()
def __str__(self): return self.name class Meta: verbose_name = _('friend group') verbose_name_plural = _('friend groups') if settings.AUTH_EXTERNAL_MANAGED: managed = False else: managed = True if settings.USE_POSTGRESQL_SCHEMAS: db_table = f"{settings.POSTGRESQL_AUTH_SCHEMA}.tinyuser_friend_group" else: db_table = 'tinyuser_friend_group' indexes = [ models.Index(fields=['name'], name='friend_group_name_idx'), ] unique_together = [('user', 'name'),]
[docs] class UserFriendship(models.Model): """Model to represent friendships between TinyUser instances.""" #: The from_user field is a foreign key to the TinyUser model, representing the user who initiated the friendship. #: It is required and will be deleted if the associated user is deleted. from_user = models.ForeignKey( django_settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='friendships_initiated' ) #: The to_user field is a foreign key to the TinyUser model, representing the user who is #: the recipient of the friendship. #: #: It is required and will be deleted if the associated user is deleted. to_user = models.ForeignKey( django_settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='friendships_received' ) #: The is_initiator field indicates whether the user is the initiator of the friendship request. #: It is a boolean field that defaults to False. is_initiator = models.BooleanField( default=False, verbose_name=_('friendship initiator'), db_column='is_initiator' ) @property def is_recipient(self): """Return True if the user is the recipient of the friendship, False otherwise.""" return not self.is_initiator #: The created_at field stores the date and time when the friendship was created. #: It is automatically set to the current date and time when the friendship is created. created_at = models.DateTimeField(default=timezone.now) #: The status field indicates the status of the friendship, such as 'pending', 'accepted', 'rejected' or 'blocked'. #: It is a character field with a maximum length of 20 characters, and it defaults to 'pending'. status_data = models.CharField( max_length=20, default=FriendshipStatus.PENDING.value, verbose_name=_('friendship status'), db_column='status' ) @property def status(self): """ Return the current status of the friendship as a FriendshipStatus enum member. :return: The current status of the friendship. :rtype: FriendshipStatus """ return FriendshipStatus.from_string(self.status_data) @status.setter def status(self, value: FriendshipStatus | str): """ Set the status of the friendship using a FriendshipStatus enum member or a valid string. :param value: The new status of the friendship. :type value: FriendshipStatus or str """ if isinstance(value, FriendshipStatus): self.status_data = value.value elif isinstance(value, str): try: self.status_data = FriendshipStatus.from_string(value).value except ValueError: raise ValueError(f"Invalid status value: {value}. Must be a valid FriendshipStatus or string.") else: raise TypeError("Status must be a FriendshipStatus enum member or a valid string.") blocked_status_data = models.CharField( max_length=20, default=FriendshipBlockedStatus.NOT_BLOCKED.value, verbose_name=_('friendship blocked status'), db_column='blocked_status' ) @property def blocked_status(self): """ Return the current blocked status of the friendship as a FriendshipBlockedStatus enum member. :return: The current blocked status of the friendship. :rtype: FriendshipBlockedStatus """ return FriendshipBlockedStatus.from_string(self.blocked_status_data) @blocked_status.setter def blocked_status(self, value: FriendshipBlockedStatus | str): """ Set the blocked status of the friendship using a FriendshipBlockedStatus enum member or a valid string. :param value: The new blocked status of the friendship. :type value: FriendshipBlockedStatus or str """ if isinstance(value, FriendshipBlockedStatus): self.blocked_status_data = value.value elif isinstance(value, str): try: self.blocked_status_data = FriendshipBlockedStatus.from_string(value).value except ValueError: raise ValueError(f"Invalid blocked status value: {value}. Must be a valid FriendshipBlockedStatus or string.") # noqa: E501 else: raise TypeError("Blocked status must be a FriendshipBlockedStatus enum member or a valid string.") @blocked_status.deleter def blocked_status(self): """Delete the blocked status of the friendship by setting it to 'not blocked'.""" self.blocked_status_data = FriendshipBlockedStatus.NOT_BLOCKED.value @property def is_pending(self): """Return True if the friendship is currently pending, False otherwise.""" return self.status == FriendshipStatus.PENDING @property def is_accepted(self): """Return True if the friendship is currently accepted, False otherwise.""" others = self.__class__.objects.filter(from_user=self.to_user, to_user=self.from_user) if others.exists(): return ( self.status == FriendshipStatus.ACCEPTED and others[0].status == FriendshipStatus.ACCEPTED ) return False @property def is_rejected(self): """Return True if the friendship is currently rejected, False otherwise.""" other_status = self.__class__.objects.filter(from_user=self.to_user, to_user=self.from_user)[0].status return self.status == FriendshipStatus.REJECTED or other_status == FriendshipStatus.REJECTED @property def is_blocked(self): """Return True if the friendship is currently blocked, False otherwise.""" other_status = self.__class__.objects.filter(from_user=self.to_user, to_user=self.from_user)[0].status return ( self.blocked_status != FriendshipBlockedStatus.NOT_BLOCKED or other_status != FriendshipBlockedStatus.NOT_BLOCKED )
[docs] def accept(self): """Accept the friendship request by setting the status to 'accepted'.""" if self.is_initiator: raise ValueError(_('Only the recipient of a friendship request can accept it.')) self.status_data = FriendshipStatus.ACCEPTED.value self.other_status = self.__class__.objects.get_or_create( from_user=self.to_user, to_user=self.from_user )[0] if self.other_status.status == FriendshipStatus.PENDING: self.other_status.status_data = FriendshipStatus.ACCEPTED.value self.other_status.save() self.save()
[docs] def reject(self): """Reject the friendship request by setting the status to 'rejected'.""" self.status_data = FriendshipStatus.REJECTED.value self.other_status = self.__class__.objects.get_or_create( from_user=self.to_user, to_user=self.from_user )[0] self.other_status.status_data = FriendshipStatus.REJECTED.value self.other_status.save() self.save()
[docs] def block(self): """Block the user by setting the status to 'blocked'.""" self.blocked_status = FriendshipBlockedStatus.BLOCKED_BY_FROM_USER self.block_initiator = True self.save() friendship2 = self.__class__.objects.filter( from_user=self.to_user, to_user=self.from_user )[0] if friendship2 and friendship2.blocked_status == FriendshipBlockedStatus.NOT_BLOCKED: friendship2.blocked_status = FriendshipBlockedStatus.BLOCKED_BY_TO_USER friendship2.save()
[docs] def unblock(self): """Unblock the user by setting the status back to 'pending'.""" self.blocked_status = FriendshipBlockedStatus.NOT_BLOCKED self.block_initiator = False self.save() friendship2 = self.__class__.objects.filter( from_user=self.to_user, to_user=self.from_user )[0] if friendship2 and friendship2.blocked_status == FriendshipBlockedStatus.BLOCKED_BY_TO_USER: friendship2.blocked_status = FriendshipBlockedStatus.NOT_BLOCKED friendship2.save()
def __str__(self): return _("{from_user} is {status} friends with {to_user}").format( from_user=self.from_user.username, status=self.status.name, to_user=self.to_user.username ) class Meta: verbose_name = _('user friendship') verbose_name_plural = _('user friendships') if settings.AUTH_EXTERNAL_MANAGED: managed = False else: managed = True if settings.USE_POSTGRESQL_SCHEMAS: db_table = f"{settings.POSTGRESQL_AUTH_SCHEMA}.tinyuser_friendship" else: db_table = 'tinyuser_friendship' indexes = [ models.Index(fields=['from_user'], name='from_user_idx'), models.Index(fields=['to_user'], name='to_user_idx'), models.Index(fields=['from_user', 'to_user'], name='friendship_idx'), ] unique_together = [('from_user', 'to_user'),]