Skip to content

Accounts API Reference

accounts.models

Models for the accounts application.

This module defines the custom user model and its associated manager. The system uses email as the primary identifier for authentication.

User

Bases: AbstractUser

Custom User model that uses email as the unique identifier.

Attributes:

Name Type Description
email EmailField

The unique email of the user (primary identifier).

first_name CharField

The user's first name.

last_name CharField

The user's last name.

currency CharField

The preferred currency code (e.g., 'EGP', 'USD').

status CharField

Current user status (e.g., 'Onboarding', 'Active').

language CharField

Preferred interface language.

is_superuser BooleanField

Designates that this user has all permissions.

is_staff BooleanField

Designates whether the user can log into this admin site.

is_active BooleanField

Designates whether this user should be treated as active.

Source code in accounts/models.py
class User(AbstractUser):
    """
    Custom User model that uses email as the unique identifier.

    Attributes:
        email (EmailField): The unique email of the user (primary identifier).
        first_name (CharField): The user's first name.
        last_name (CharField): The user's last name.
        currency (CharField): The preferred currency code (e.g., 'EGP', 'USD').
        status (CharField): Current user status (e.g., 'Onboarding', 'Active').
        language (CharField): Preferred interface language.
        is_superuser (BooleanField): Designates that this user has all permissions.
        is_staff (BooleanField): Designates whether the user can log into this admin site.
        is_active (BooleanField): Designates whether this user should be treated as active.
    """

    username = None
    email = models.EmailField(unique=True, max_length=100)
    first_name = models.CharField(max_length=25)
    last_name = models.CharField(max_length=25)

    currency = models.CharField(max_length=5, default='EGP', blank=True)
    status = models.CharField(max_length=20, default='Onboarding')
    language = models.CharField(max_length=20, default='English')

    is_superuser = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name']

    def __str__(self):
        """Return the string representation of the user (their email)."""
        return self.email

    class Meta:
        """Metadata options for the User model."""
        ordering = ['email']

Meta

Metadata options for the User model.

Source code in accounts/models.py
class Meta:
    """Metadata options for the User model."""
    ordering = ['email']

__str__()

Return the string representation of the user (their email).

Source code in accounts/models.py
def __str__(self):
    """Return the string representation of the user (their email)."""
    return self.email

UserManager

Bases: BaseUserManager

Custom manager for the User model where email is the unique identifier.

Provides methods to create regular users and superusers using email instead of a username.

Source code in accounts/models.py
class UserManager(BaseUserManager):
    """
    Custom manager for the User model where email is the unique identifier.

    Provides methods to create regular users and superusers using email
    instead of a username.
    """

    def create_user(self, email, password=None, **extra_fields):
        """
        Create and return a regular user with an email and password.

        Args:
            email (str): The unique email address of the user.
            password (str, optional): The raw password for the user.
            **extra_fields: Additional fields to be saved in the User model.

        Returns:
            User: The newly created user instance.

        Raises:
            ValueError: If the email field is not provided.
        """
        if not email:
            raise ValueError('The Email field must be set')

        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        """
        Create and return a superuser with elevated permissions.

        Args:
            email (str): The unique email address of the superuser.
            password (str, optional): The raw password for the superuser.
            **extra_fields: Additional fields to be saved in the User model.

        Returns:
            User: The newly created superuser instance.

        Raises:
            ValueError: If is_staff or is_superuser is not set to True.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self.create_user(email, password, **extra_fields)

create_superuser(email, password=None, **extra_fields)

Create and return a superuser with elevated permissions.

Parameters:

Name Type Description Default
email str

The unique email address of the superuser.

required
password str

The raw password for the superuser.

None
**extra_fields

Additional fields to be saved in the User model.

{}

Returns:

Name Type Description
User

The newly created superuser instance.

Raises:

Type Description
ValueError

If is_staff or is_superuser is not set to True.

Source code in accounts/models.py
def create_superuser(self, email, password=None, **extra_fields):
    """
    Create and return a superuser with elevated permissions.

    Args:
        email (str): The unique email address of the superuser.
        password (str, optional): The raw password for the superuser.
        **extra_fields: Additional fields to be saved in the User model.

    Returns:
        User: The newly created superuser instance.

    Raises:
        ValueError: If is_staff or is_superuser is not set to True.
    """
    extra_fields.setdefault('is_staff', True)
    extra_fields.setdefault('is_superuser', True)
    extra_fields.setdefault('is_active', True)

    if extra_fields.get('is_staff') is not True:
        raise ValueError('Superuser must have is_staff=True.')
    if extra_fields.get('is_superuser') is not True:
        raise ValueError('Superuser must have is_superuser=True.')

    return self.create_user(email, password, **extra_fields)

create_user(email, password=None, **extra_fields)

Create and return a regular user with an email and password.

Parameters:

Name Type Description Default
email str

The unique email address of the user.

required
password str

The raw password for the user.

None
**extra_fields

Additional fields to be saved in the User model.

{}

Returns:

Name Type Description
User

The newly created user instance.

Raises:

Type Description
ValueError

If the email field is not provided.

Source code in accounts/models.py
def create_user(self, email, password=None, **extra_fields):
    """
    Create and return a regular user with an email and password.

    Args:
        email (str): The unique email address of the user.
        password (str, optional): The raw password for the user.
        **extra_fields: Additional fields to be saved in the User model.

    Returns:
        User: The newly created user instance.

    Raises:
        ValueError: If the email field is not provided.
    """
    if not email:
        raise ValueError('The Email field must be set')

    email = self.normalize_email(email)
    user = self.model(email=email, **extra_fields)
    user.set_password(password)
    user.save(using=self._db)
    return user

accounts.serializers

Serializers for the accounts application.

Handles data validation and transformation for user registration, profile retrieval, and partial updates.

UserSerializer

Bases: ModelSerializer

Serializer for user registration and profile retrieval.

Includes basic profile information and ensures the password is write-only.

Source code in accounts/serializers.py
class UserSerializer(serializers.ModelSerializer):
    """
    Serializer for user registration and profile retrieval.

    Includes basic profile information and ensures the password is write-only.
    """

    class Meta:
        """Metadata for the UserSerializer."""
        model = User
        fields = ['id', 'email', 'first_name', 'last_name', 'currency', 'language', 'password']
        extra_kwargs = {
            'password': {'write_only': True},
        }

    def create(self, validated_data):
        """
        Create a new user using the custom manager.

        This method ensures that the password is hashed correctly by calling
        User.objects.create_user instead of the default ModelSerializer.create.

        Args:
            validated_data (dict): The data validated by the serializer.

        Returns:
            User: The newly created user instance.
        """
        return User.objects.create_user(**validated_data)

Meta

Metadata for the UserSerializer.

Source code in accounts/serializers.py
class Meta:
    """Metadata for the UserSerializer."""
    model = User
    fields = ['id', 'email', 'first_name', 'last_name', 'currency', 'language', 'password']
    extra_kwargs = {
        'password': {'write_only': True},
    }

create(validated_data)

Create a new user using the custom manager.

This method ensures that the password is hashed correctly by calling User.objects.create_user instead of the default ModelSerializer.create.

Parameters:

Name Type Description Default
validated_data dict

The data validated by the serializer.

required

Returns:

Name Type Description
User

The newly created user instance.

Source code in accounts/serializers.py
def create(self, validated_data):
    """
    Create a new user using the custom manager.

    This method ensures that the password is hashed correctly by calling
    User.objects.create_user instead of the default ModelSerializer.create.

    Args:
        validated_data (dict): The data validated by the serializer.

    Returns:
        User: The newly created user instance.
    """
    return User.objects.create_user(**validated_data)

UserUpdateSerializer

Bases: ModelSerializer

Serializer for partial profile updates.

Allows users to update specific fields like names, currency, and language without touching core authentication data like email or password.

Source code in accounts/serializers.py
class UserUpdateSerializer(serializers.ModelSerializer):
    """
    Serializer for partial profile updates.

    Allows users to update specific fields like names, currency, and language
    without touching core authentication data like email or password.
    """

    class Meta:
        """Metadata for the UserUpdateSerializer."""
        model = User
        fields = ['first_name', 'last_name', 'currency', 'language']

Meta

Metadata for the UserUpdateSerializer.

Source code in accounts/serializers.py
class Meta:
    """Metadata for the UserUpdateSerializer."""
    model = User
    fields = ['first_name', 'last_name', 'currency', 'language']

accounts.views

Views for handling user authentication and profile management.

This module provides the AuthViewSet which consolidates registration, login, logout, and profile operations into a single endpoint set.

AuthViewSet

Bases: CreateModelMixin, GenericViewSet

ViewSet for authentication operations: register, login, logout, and profile management.

This viewset handles: - User registration (POST /auth/) - Login (POST /auth/login/) - Logout (GET/POST /auth/logout/) - Current User Profile (GET /auth/me/) - Profile Update (PATCH /auth/update_profile/)

Source code in accounts/views.py
class AuthViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
    """
    ViewSet for authentication operations: register, login, logout, and profile management.

    This viewset handles:
    - User registration (POST /auth/)
    - Login (POST /auth/login/)
    - Logout (GET/POST /auth/logout/)
    - Current User Profile (GET /auth/me/)
    - Profile Update (PATCH /auth/update_profile/)
    """

    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.AllowAny]

    def perform_create(self, serializer):
        """
        Register a new user using the custom user manager.

        Args:
            serializer (UserSerializer): The validated serializer instance.
        """
        User.objects.create_user(**serializer.validated_data)

    @extend_schema(
        summary="User Login",
        description="Authenticates a user by email and password and establishes a session.",
        request=inline_serializer('LoginRequest', {'email': serializers.EmailField(), 'password': serializers.CharField()}),
        responses={200: inline_serializer('LoginResponse', {'message': serializers.CharField(), 'user': UserSerializer()})}
    )
    @action(detail=False, methods=['post'])
    def login(self, request):
        """
        Authenticate a user by email and password and start a session.

        Expected Data:
            email (str): The user's email.
            password (str): The user's password.

        Returns:
            Response: Success message and user data if valid, or error message.
        """
        email = request.data.get('email')
        password = request.data.get('password')

        user = authenticate(request, username=email, password=password)
        if user is not None:
            login(request, user)
            return Response(
                {'message': 'Login successful', 'user': UserSerializer(user).data},
                status=status.HTTP_200_OK,
            )
        return Response({'message': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)

    @extend_schema(
        summary="User Logout",
        description="Terminates the current authenticated user's session.",
        request=None, 
        responses={200: inline_serializer('LogoutResponse', {'message': serializers.CharField()})}
    )
    @action(detail=False, methods=['get', 'post'], permission_classes=[permissions.IsAuthenticated])
    def logout(self, request):
        """
        End the current user session.

        Requires Authentication.

        Returns:
            Response: Successfully logged out message.
        """
        logout(request)
        return Response({'message': 'Successfully logged out'}, status=status.HTTP_200_OK)

    @extend_schema(
        summary="Get Current Profile",
        description="Returns the profile data of the currently authenticated user.",
        responses=UserSerializer
    )
    @action(detail=False, methods=['get'], permission_classes=[permissions.IsAuthenticated])
    def me(self, request):
        """
        Return the profile data of the currently authenticated user.

        Requires Authentication.

        Returns:
            Response: Authenticated user's profile data.
        """
        serializer = self.get_serializer(request.user)
        return Response(serializer.data)

    @extend_schema(
        summary="Update Profile",
        description="Allows partial updates to the authenticated user's profile (name, currency, language).",
        request=UserUpdateSerializer, 
        responses=UserSerializer
    )
    @action(detail=False, methods=['patch'], permission_classes=[permissions.IsAuthenticated])
    def update_profile(self, request):
        """
        Partially update the authenticated user's profile.

        Allows changing first_name, last_name, currency, and language.

        Returns:
            Response: Updated profile data or validation errors.
        """
        serializer = UserUpdateSerializer(request.user, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(UserSerializer(request.user).data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

login(request)

Authenticate a user by email and password and start a session.

Expected Data

email (str): The user's email. password (str): The user's password.

Returns:

Name Type Description
Response

Success message and user data if valid, or error message.

Source code in accounts/views.py
@extend_schema(
    summary="User Login",
    description="Authenticates a user by email and password and establishes a session.",
    request=inline_serializer('LoginRequest', {'email': serializers.EmailField(), 'password': serializers.CharField()}),
    responses={200: inline_serializer('LoginResponse', {'message': serializers.CharField(), 'user': UserSerializer()})}
)
@action(detail=False, methods=['post'])
def login(self, request):
    """
    Authenticate a user by email and password and start a session.

    Expected Data:
        email (str): The user's email.
        password (str): The user's password.

    Returns:
        Response: Success message and user data if valid, or error message.
    """
    email = request.data.get('email')
    password = request.data.get('password')

    user = authenticate(request, username=email, password=password)
    if user is not None:
        login(request, user)
        return Response(
            {'message': 'Login successful', 'user': UserSerializer(user).data},
            status=status.HTTP_200_OK,
        )
    return Response({'message': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)

logout(request)

End the current user session.

Requires Authentication.

Returns:

Name Type Description
Response

Successfully logged out message.

Source code in accounts/views.py
@extend_schema(
    summary="User Logout",
    description="Terminates the current authenticated user's session.",
    request=None, 
    responses={200: inline_serializer('LogoutResponse', {'message': serializers.CharField()})}
)
@action(detail=False, methods=['get', 'post'], permission_classes=[permissions.IsAuthenticated])
def logout(self, request):
    """
    End the current user session.

    Requires Authentication.

    Returns:
        Response: Successfully logged out message.
    """
    logout(request)
    return Response({'message': 'Successfully logged out'}, status=status.HTTP_200_OK)

me(request)

Return the profile data of the currently authenticated user.

Requires Authentication.

Returns:

Name Type Description
Response

Authenticated user's profile data.

Source code in accounts/views.py
@extend_schema(
    summary="Get Current Profile",
    description="Returns the profile data of the currently authenticated user.",
    responses=UserSerializer
)
@action(detail=False, methods=['get'], permission_classes=[permissions.IsAuthenticated])
def me(self, request):
    """
    Return the profile data of the currently authenticated user.

    Requires Authentication.

    Returns:
        Response: Authenticated user's profile data.
    """
    serializer = self.get_serializer(request.user)
    return Response(serializer.data)

perform_create(serializer)

Register a new user using the custom user manager.

Parameters:

Name Type Description Default
serializer UserSerializer

The validated serializer instance.

required
Source code in accounts/views.py
def perform_create(self, serializer):
    """
    Register a new user using the custom user manager.

    Args:
        serializer (UserSerializer): The validated serializer instance.
    """
    User.objects.create_user(**serializer.validated_data)

update_profile(request)

Partially update the authenticated user's profile.

Allows changing first_name, last_name, currency, and language.

Returns:

Name Type Description
Response

Updated profile data or validation errors.

Source code in accounts/views.py
@extend_schema(
    summary="Update Profile",
    description="Allows partial updates to the authenticated user's profile (name, currency, language).",
    request=UserUpdateSerializer, 
    responses=UserSerializer
)
@action(detail=False, methods=['patch'], permission_classes=[permissions.IsAuthenticated])
def update_profile(self, request):
    """
    Partially update the authenticated user's profile.

    Allows changing first_name, last_name, currency, and language.

    Returns:
        Response: Updated profile data or validation errors.
    """
    serializer = UserUpdateSerializer(request.user, data=request.data, partial=True)
    if serializer.is_valid():
        serializer.save()
        return Response(UserSerializer(request.user).data, status=status.HTTP_200_OK)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)