Django – testing the UserManager

Here is a manager for my custom user model, that allows to create instances for regular users and admin/staff users.

My custom user model:

from django.db import models
from django.contrib.auth.models import AbstractUser
from .managers import ContractUserManager


class ContractUser(AbstractUser):

    class ProfileChoices(models.TextChoices):
        CLIENT = "client"
        CONTRACTOR = "contractor"

    profile = models.TextField(
        choices=ProfileChoices.choices, default=ProfileChoices.CLIENT
    )
    email = models.EmailField(max_length=255, unique=True)
    is_active = models.BooleanField(default=False)

    objects = ContractUserManager()

    def __str__(self):
        return self.username

Accounts for the created regular users are inactive, i.e. they can’t log in using the provided data until activated via e-mail.

Creating regular and privileged users is done using a manager that inherits from the UserManager class.

source file: backend/users/managers.py

from django.contrib.auth.models import UserManager
from django.utils.translation import gettext_lazy as _


class ContractUserManager(UserManager):

    def create_user(self, username, email, password, **extra_fields):
        if not email:
            raise ValueError(_("Email field is required"))
        email = self.normalize_email(email)
        user = self.model(username=username, email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, username, email, password, **extra_fields):
        extra_fields.setdefault("is_superuser", True)
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_active", True)
        user = self.create_user(username, email, password, **extra_fields)
        return user

Then I test the custom manager:

from django.test import TestCase
from .models import ContractUser


class ContractUserTestCase(TestCase):

    def setUp(self):
        self.user = ContractUser.objects.create_user(
            username="user", password="123", email="user@company.com"
        )
        self.superuser = ContractUser.objects.create_superuser(
            username="admin",
            password="123",
            email="admin@company.com",
        )

    def test_create(self):
        self.assertEqual(self.user.username, "user")
        self.assertEqual(self.user.email, "user@company.com")
        self.assertEqual(self.user.profile, "client")
        self.assertEqual(str(self.user), "user")
        self.assertFalse(self.user.is_active)

        with self.assertRaisesMessage(ValueError, "Email field is required"):
            ContractUser.objects.create_user(username="user1", email="", password="123")

    def test_create_superuser(self):
        self.assertTrue(self.superuser.is_superuser)
        self.assertTrue(self.superuser.is_staff)
        self.assertTrue(self.superuser.is_active)
        self.assertEqual(self.superuser.username, "admin")

DRF – custom permissions #2

Previous post regarding permissions in DRF – see here.

For the warehouse model, I intend to define permissions thanks to which only the owner of a given warehouse will be able to edit the created model instance. Additionally, a warehouse can only be created by a user with a “customer” profile – who places orders.

The warehouse model:

source file: backend/contracts/models/warehouse.py

class Warehouse(models.Model):
    warehouse_name = models.CharField(max_length=15, unique=True)
    warehouse_info = models.TextField(max_length=100)
    client = models.ForeignKey(ContractUser, on_delete=models.CASCADE)

    def __str__(self):
        return self.warehouse_name

For this model, I will create a WarehouseWritePermission class that inherits from the BasePermission class.

In this class I define two methods: has_object_permission() and has_permission().

The has_permission() method defines permissions when executing the GET and POST methods, while the has_object_permission() method will define permissions when executing the GET method along with a parameter specifying a specific warehouse, as well as when editing the warehouse (PUT method), i.e.

   def has_permission(self, request, view):
        if request.method != "POST":
            return True
        return request.user.profile == "client"

In the above method, only a user with the “client” profile can create a new instance of the warehouse model. Users with the “contractor” profile can only view the list of created warehouses.

    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return obj.client == request.user

In the above method, users can view a specific instance, but only the owner of the warehouse, which is saved in the instance’s client variable, can edit it.

How to add logged in user in serializer in DRF

In the example I discussed, the user appears as a foreign key in the warehouse model, i.e.

source file: backend/contracts/models/warehouse.py

class Warehouse(models.Model):
    warehouse_name = models.CharField(max_length=15, unique=True)
    warehouse_info = models.TextField(max_length=100)
    client = models.ForeignKey(ContractUser, on_delete=models.CASCADE)

    def __str__(self):
        return self.warehouse_name

When creating a new instance of the warehouse model, the user only needs to provide the name of the new warehouse and information about the warehouse (e.g. location or other useful information).

However, to create an instance, it is also necessary to provide a user who is the “owner” of the created warehouse.

I provide the user by adding him to the context in the view, i.e.

source file: backend/api/views/warehouse.py

class WarehouseViewSet(viewsets.ModelViewSet):
    queryset = Warehouse.objects.all()
    serializer_class = WarehouseSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_serializer_context(self):
        context = super().get_serializer_context()
        context.update({"client": self.request.user})
        return context

The added user can be used during serialization, i.e

source file: backend/api/serializers/warehouse.py

class WarehouseSerializer(serializers.ModelSerializer):

    class Meta:
        model = Warehouse
        fields = ["warehouse_name", "warehouse_info"]

    def create(self, validated_data):
        validated_data["client"] = self.context["client"]
        warehouse = Warehouse.objects.create(**validated_data)
        return warehouse

The logged in user will be added “automatically” when the model instance is created.

DRF – model and API testing

In the project – backend providing data for the frontend, which I’m going to create in the REACT framework, I set an application called contracts containing model – Warehouse.

source file: backend/contracts/model/warehouse.py

from django.db import models
from users.models import ContractUser


class Warehouse(models.Model):
    warehouse_name = models.CharField(max_length=15, unique=True)
    warehouse_info = models.TextField(max_length=100)
    client = models.ForeignKey(ContractUser, on_delete=models.CASCADE)

    def __str__(self):
        return self.warehouse_name

Model tests for the Warehouse model:

source file: backend/contracts/tests/models_tests/test_warehouse.py

from django.test import TestCase
from users.models import ContractUser
from ...models import Warehouse


class WarehouseTestCase(TestCase):

    def setUp(self):
        self.user = ContractUser.objects.create(
            username="user", password="123", email="user@company.com"
        )
        self.warehouse = Warehouse.objects.create(
            warehouse_name="TestWarehouse",
            warehouse_info="TestWarehouse info",
            client=self.user,
        )

    def test_create(self):
        self.assertEqual(self.warehouse.warehouse_name, "TestWarehouse")
        self.assertEqual(self.warehouse.warehouse_info, "TestWarehouse info")
        self.assertEqual(self.warehouse.client, self.user)
        self.assertEqual(str(self.warehouse), "TestWarehouse")

In the test above, I check whether the model instance was created correctly.

Then I create CRUD tests for the API created in DRF:

source file: backend/api/tests/test_warehouse.py

from rest_framework.test import APITestCase
from rest_framework import status
from django.urls import reverse
from users.models import ContractUser
from contracts.models import Warehouse


class WarehouseAPITestCase(APITestCase):

    def setUp(self):
        self.user = ContractUser.objects.create(
            username="TestUser", password="123", email="testuser@company.com"
        )
        self.warehouse = Warehouse.objects.create(
            warehouse_name="Warehouse",
            warehouse_info="Warehouse info",
            client=self.user,
        )

    def test_get(self):
        endpoint = reverse("warehouse-list")
        response = self.client.get(endpoint, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_create(self):
        endpoint = reverse("warehouse-list")
        data = {
            "warehouse_name": "Warehouse-new",
            "warehouse_info": "Warehouse-new info",
            "client": self.user.id,
        }
        response = self.client.post(endpoint, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_retrieve(self):
        endpoint = reverse("warehouse-detail", args=[self.warehouse.id])
        response = self.client.get(endpoint, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data["warehouse_name"], "Warehouse")

    def test_update(self):
        data = {
            "warehouse_name": "Warehouse-upd",
            "warehouse_info": "Warehouse1-upd info",
            "client": self.user.id,
        }
        endpoint = reverse("warehouse-detail", args=[self.warehouse.id])
        response = self.client.put(endpoint, data, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data["warehouse_name"], "Warehouse-upd")

    def test_delete(self):
        endpoint = reverse("warehouse-detail", args=[self.warehouse.id])
        response = self.client.delete(endpoint)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

In the above tests, I check the GET, POST, PUT and DELETE request methods.

Custom Permissions in DRF

To create my own permissions for the custom user I created earlier, I create a class that inherits from the BasePermission class.

I’ll create two permission classes:

  • A class that allows access for users who are clients (the role attribute is client). Contractors are not authorized to the resource.
  • A class that allows users who are contractors read-only access.

First of all, in the permissions.py file of the contracts application, I import the BasePermission class and the list of “safe methods”, i.e. allowing the listing of the resource, without the possibility of editing, deleting or adding a new contract.

from rest_framework.permissions import BasePermission, SAFE_METHODS


"""Custom permissions classes"""

The IsClient() class allows access only to clients. Other users will see a message informing about no access to the resource:

class IsClient(BasePermission):
    message = "Only clients can access"

    def has_permission(self, request, view):
        if request.user.role == "client":
            return True
        return False

The IsClientOrReadOnly() class allows users who are clients to add, delete and edit, other users can only view the data. i.e.:

class IsClientOrReadOnly(BasePermission):
    message = "Only clients can modify, contractors can read only"

    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return request.user.role == "client"

I put the created permission classes in views, e.g.

class ContractViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated, IsClientOrReadOnly]
    serializer_class = ContractSerializer

Django Rest Framework – API testing

In this post, I will describe testing the API for the created custom user. (earlier entries – >>part 1<< and >>part 2<<)

Access to the ContactUserCreateRetrieveViewSet view for creating a new user and displaying a specific user is registered in the urls.py file of the users application, i.e.

from rest_framework.routers import DefaultRouter
from .views import ContractUserCreateRetrieveViewSet

router = DefaultRouter()

router.register("", ContractUserCreateRetrieveViewSet, basename="user")

urlpatterns = router.urls

As the basename parameter, I defined it as user, which name I will refer to during the tests.

The users application urls.py file is then imported in the project urls.py file, i.e.

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api-auth/", include("rest_framework.urls")),
    path("api/user/", include("users.urls")),
]

So the complete endpoint for creating users will be /api/user/

Creating a user and displaying a specific user will be handled by specific methods from the views.py file of the users application, i.e. creating – the create() function, displaying the user – the retrieve() function.

from django.shortcuts import get_object_or_404
from rest_framework import viewsets, mixins
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from .models import ContractUser
from .serializers import ContractUserSerializer

class ContractUserCreateRetrieveViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet,
):
    serializer_class = ContractUserSerializer
    queryset = ContractUser.objects.all()

    def retrieve(self, request, pk):
        permission_classes = [IsAuthenticated]
        queryset = ContractUser.objects.filter(id=request.user.id)
        user = get_object_or_404(queryset, pk=pk)
        serializer = ContractUserSerializer(user)
        return Response(serializer.data)

    def create(self, request):
        serializer = ContractUserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The ContractUserCreateRetrieveViewSet class inherits from the basic GenericViewSet class and the mixins classes, which add the required functionalities – creating and displaying a user.

The create() function serializes the given data and, after checking whether they are correct, saves them in the database and returns the Response object and the appropriate status code.

The retrieve() function checks if the user is logged in and filters users so that the returned queryset contains only the user who is currently logged in, and then returns a Response object and the appropriate status code.

In the signals.py file of the users application, I modify the email field provided by the AbstractUser class so that this field is required when creating the ContractUser model, i.e.

from .models import ContractUser
from django.dispatch import receiver


@receiver(pre_save, sender=ContractUser)
def create_inactive_user(sender, instance, **kwargs):
    instance.is_active = False
    instance._meta.get_field("email").blank = False
    instance._meta.get_field("email").null = False

If we want the email field to be unique, we can set the unique parameter to True, i.e.

    instance._meta.get_field("email")._unique = True

Then, if the user enters an email that is already in the database, a validation error will occur. Unfortunately, thanks to this, it will be known that the user with this email address is already present in the database.

To test creating and displaying a user, I create a tests directory in the users app.

I move the tests.py file created in the previous article, which contained model tests, to it and rename it to test_model.py

In the created tests directory, I create a new file called test_api.py, which will contain api tests for the users application, i.e.

from rest_framework.test import APITestCase
from rest_framework import status
from django.urls import reverse
from ..models import ContractUser


class ContractUserApiTestCase(APITestCase):
    """Testing ContractUser API"""

    def test_create_user(self):
        endpoint = reverse("user-list")
        user_data = {
            "username": "user_1",
            "password": "123456789",
            "email": "user_1@company.com",
        }
        response = self.client.post(endpoint, data=user_data, format="json")
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_retrieve_user(self):
        user = ContractUser.objects.create(
            username="user1", password="12345", email="user1@company.com"
        )
        self.client.force_authenticate(user)
        endpoint = reverse("user-detail", args=[user.id])
        response = self.client.get(endpoint, format="json")
        self.assertEqual(response.status_code, status.HTTP_200_OK)

In the test_create_user() method, I create an endpoint using the reverse() function. The reverse() function takes as a parameter the basename given in the urls.py file with the -list suffix. Then I create data for the test user and using the default client present in the APITestCase class, I make a query using the POST method. The assertion checks whether the status code of the received response is 201 when the user was created successfully, or 400 otherwise.

In the test_retrieve_user() method, I create a test user and then log him in using the force_authenticate() function. The reverse() function takes the basename value given in the urls.py file with the -detail suffix as a parameter. As an additional parameter, I include the logged in user’s id. The assertion checks if the response status code is 200 if the query was successful or if the status is 400 otherwise.

Django – model testing

To test the custom ContractUser model created in the previous article, in the tests.py file of the users application, I import the TestCase class from the django.test module and the tested model, i.e.

A new ContractUserTestCase class that inherits from the TestCase class is created.

In the setUp() method, I create two temporary users user1 and user2.

In the test_create_user() method, I run 1 test consisting of several assertions, in particular the text representation of the created instance suggested by the coverage tool, i.e.

class ContractUserTestCase(TestCase):
    def setUp(self):
        self.user1 = ContractUser.objects.create(
            username="user_1",
            password="12345",
            role="contractor"
        )
        self.user2 = ContractUser.objects.create(
            username="user_2",
            password="12345")

    def test_create_user(self):
        self.assertEqual(self.user1.username, "user_1")
        self.assertEqual(self.user2.username, "user_2")
        self.assertEqual(self.user1.role, "contractor")
        self.assertEqual(self.user2.role, "client")
        self.assertEqual(str(self.user1), "user_1")
        self.assertEqual(str(self.user2), "user_2")

I test other models from the contracts application in a similar way.

The Contract model also includes a custom validator that checks that the delivery date is not earlier than the order date.

part of the Contract model from models.py of the contracts application:

class Contract(models.Model):
    class Meta:
        ordering = ("-date_of_order",)

    class StatusChoices(models.TextChoices):
        OPEN = "open"
        ACCEPTED = "accepted"
        CANCELED = "cancelled"

    class DeliveryDateValidator:
        def validate(value):
            if value < timezone.now().date():
                raise validators.ValidationError(
                    "Date of delivery can't be earlier than date of order"
                )
            else:
                return value

    date_of_order = models.DateField(default=timezone.now)

    date_of_delivery = models.DateField(
        validators=(DeliveryDateValidator.validate,)
)

             ..........

To test the custom validator, I create a separate test that checks whether the ValidationError exception will be raised and whether the entered date will be returned when the correct date is given (not older than the order date).

    def test_delivery_date_validator(self):
        with self.assertRaises(ValidationError):
            self.contract.full_clean()
        self.contract.date_of_delivery = datetime.strptime(
            "2200-01-01", "%Y-%m-%d"
        ).date()
        self.assertEqual(
            self.contract.DeliveryDateValidator.validate(
                self.contract.date_of_delivery
            ),
            self.contract.date_of_delivery,
        )

Django Rest Framework – creating users

First, I create a new application called users.

python manage.py startapp users

In the models.py file, I create a custom user that inherits from the AbstractUser class. I’m adding a role checkbox so that the user will be either a principal or a contractor (client or contractor).

from django.contrib.auth.models import AbstractUser
from django.db import models


class ContractUser(AbstractUser):
    class RoleChoices(models.TextChoices):
        CLIENT = "client"
        CONTRACTOR = "contractor"

    role = models.CharField(
        max_length=10,
        choices=RoleChoices.choices,
        default=RoleChoices.CLIENT
    )

    def __str__(self):
        return self.username

The new user model should be registered in the project’s settings.py file, i.e.

AUTH_USER_MODEL = "users.ContractUser"

In the users application, I create a serializers.py file containing the serializer of the new user model, i.e.

from django.contrib.auth.hashers import make_password
from rest_framework import serializers
from .models import ContractUser


class ContractUserSerializer(serializers.ModelSerializer):
    password = serializers.CharField(
        min_length=8,
        write_only=True,
        style={"input_type": "password"}
    )

    class Meta:
        model = ContractUser
        fields = [
             "id",
             "username",
             "password",
             "email",
             "role"
        ]

    def create(self, validated_data):
        validated_data["password"] = make_password(validated_data["password"])
        return super().create(validated_data)

The serializer includes a CharField to enter a password. This field contains the write_only parameter set to True to allow only writing and not reading of this field. The fields field of the Meta internal class contains a list of all available serializable fields. The create() method creates a password based on user input using the make_password() function.

In the urls.py file of the users application, I define an endpoint, i.e.

from rest_framework.routers import DefaultRouter
from .views import ContractUserCreateRetrieveViewSet

router = DefaultRouter()

router.register("", ContractUserCreateRetrieveViewSet, basename="user")

urlpatterns = router.urls

I attach the endpoint for the users application to the main urls.py file of the project:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/user/", include("users.urls")),
]

The /api/user/ endpoint will allow you to create a user and view data for a given user. To do this, I create a ContractUserCreateRetrieveViewSet() class in the views.py file. To limit the number of available methods, I do not use the ModelViewSet class, but inherit from the GenericViewSet class and the appropriate Mixin classes.

from django.shortcuts import get_object_or_404
from rest_framework import viewsets, mixins
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from .models import ContractUser
from .serializers import ContractUserSerializer

class ContractUserCreateRetrieveViewSet(
    mixins.CreateModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet,
):
    serializer_class = ContractUserSerializer
    queryset = ContractUser.objects.all()

    def retrieve(self, request, pk):
        permission_classes = [IsAuthenticated]
        queryset = ContractUser.objects.filter(id=request.user.id)
        user = get_object_or_404(queryset, pk=pk)
        serializer = ContractUserSerializer(user)
        return Response(serializer.data)

    def create(self, request):
        serializer = ContractUserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

To make the newly created user inactive, I used the pre_save signal. For this purpose, I created a new signals.py file in the users application, i.e.

from django.db.models.signals import pre_save
from .models import ContractUser
from django.dispatch import receiver

@receiver(pre_save, sender=ContractUser)
def create_inactive_user(sender, instance, **kwargs):
    instance.is_active = False

The signal should be imported in the ready() function of the UsersConfig() class from the apps.py file:

from django.apps import AppConfig


class UsersConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "users"

    def ready(self):
        import users.signals

Django #4

You can get the source code from GitHub here.

In this post I will present views responsible for car management and repairs.

▣ I will start with the main view showing the cars entered by the user.

The maximum number of entries per page is 10 cars.

After clicking on a particular car, the repairs of a given car are displayed.

Both car and repair entries are sorted by date, i.e. the newest entries are displayed first.

The view class displaying cars inherits from the ListView class and the LoginRequiredMixin class (so that access is only possible for logged in users).

First, I define all the class attributes, i.e. the model used – Car, the template used – cars.html, the name of the object under which the data in the context is available – context_object_name – ‘cars’, and the number of entries on the page – 10 vehicles.

In this class, I override two methods – get_queryset (), which is responsible for filtering data, and get_context_data (), in which I complete the context data with data from the search field.

CarListView () class source code:

class CarsListView(LoginRequiredMixin, ListView):
    model = Car
    template_name = 'cars/cars.html'
    context_object_name = 'cars'
    paginate_by = 10

    def get_queryset(self):
        if self.request.GET.get('q'):
            q = self.request.GET.get('q')
            make_results = self.model.objects.filter(
                user=self.request.user, make=q).order_by('-pk')
            model_results = self.model.objects.filter(
                user=self.request.user, model=q).order_by('-pk')
            if make_results.exists():
                return make_results
            elif model_results.exists():
                return model_results
            else:
                return self.model.objects.none()
        return self.model.objects.filter(user=self.request.user).order_by('-pk')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['q'] = self.request.GET.get('q', '')
        return context

The get_queryset() method lists all of the user’s vehicles. The last added car is visible first (sort ‘-pk’). If the make or model of the vehicle (s) was entered in the search field, only these vehicles will be displayed.

The get_context_data() method adds an entry from the search field to the context, thanks to which the data about car repairs is displayed correctly when dividing cars into individual pages.

▣ Adding a vehicle is done by pressing the Add Car button in the application menu.

The view that handles adding a car is the AddCarView () class, and the template that displays the add car form is car_form.html (the same template also supports updating auto data).

The AddCarView () class inherits the functionality from the CreateView () and LoginRequiredMixin class (only logged in users can create a new vehicle).

I define the following view class attributes: model that the class uses – Car, fields – the form fields to be visible, and success_url – the url that will be loaded after successfully completing the form.

I override the form_valid() method, which adds the user who created the new car (the Car model requires a user attribute to be defined).

The source code for the AddCarView class :

class AddCarView(LoginRequiredMixin, CreateView):
    model = Car
    fields = ['make', 'model', 'vrn', 'year']
    success_url = '/'

    def form_valid(self, form):
        form.instance.user = self.request.user
        return super().form_valid(form)

▣ Removing the car is done using the DeleteCarView () view class, and the template that prompts you to delete the auto is the car_confirm_delete.html file (displays the Delete button that deletes the auto and the Cancel button that returns to the previous page).

DeleteCarView () class inherits functionality from DeleteView, LoginRequiredMixin class (only logged in user can delete auto) and UserPassesTestMixin (user can delete auto created by himself).

I define the model arguments – the used Car model and success_url – the address to which it will be loaded after the successful removal of the car – in this case, the view showing all the user’s cars.

I am creating a test_func () method which checks if the user who wants to delete the car is the person who created the auto (the user can only delete the cars he created).

I override the delete () method, which additionally displays the message about deleting the car.

The DeleteCarView() class source code:

class DeleteCarView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Car
    success_url = '/'

    def test_func(self):
        if self.get_object().user == self.request.user:
            return True
        return False

    def delete(self, request, *args, **kwargs):
        success_message = f'Car {self.get_object()} has been deleted'
        messages.success(self.request, success_message)
        return super().delete(request, *args, **kwargs)

▣ The vehicle data is updated using the UpdateCarView () class, while the template displaying the form for changing this data is car_form.html.

The UpdateCarView () class inherits functionality from the UpdateView class as well as the LoginRequiredMixin classes (only the logged in user can update the data) and UserPassesTestMixin (a function is called to check if the user who wants to modify is the one who created the auto).

I define class attributes: model – specifies the model that is used to change the data – in this case the Car model, fields attribute – specifying which form fields are to be available and success_message – a text message about updating the car data.

Two methods are defined: test_func () and get_success_url ().

The test_func () method checks if the user who wants to modify the car’s data is the one who created the car.

The get_success_url() method displays a message about the data change and returns to the page with the name: car_detail – displaying repairs for a given car. As additional parameters, they are sent using the GET method: row, p and q that define the row and page of the car in the table and the text string from the search box.

Code of the UpdateCarView() class:

class UpdateCarView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Car
    fields = ['make', 'model', 'vrn', 'year']
    success_message = 'Car info has been updated'

    def get_success_url(self, **kwargs):
        row = self.request.GET.get('row')
        p = self.request.GET.get('p')
        q = self.request.GET.get('q')
        options = '?p=' + p + '&row=' + row
        options += '&q=' + q
        messages.success(self.request, self.success_message)
        return reverse_lazy('car_detail') + options

    def test_func(self):
        if self.get_object().user == self.request.user:
            return True
        return False

▣ Adding a repair note is defined in the AddRepairView() class, and the form is defined in the repair_form.html template.

This class has the following arguments: model – specifying the model used – in this case Repair. The next argument: fields – specifies what the form fields are displayed. The last argument is the success_message string displayed after the repair note was successfully added.

The methods in the AddRepairView() class are: get_context_data(), form_valid(), and get_success_url().

The get_context_data() method adds a Car object to the context, making it visible in the template.

The form_valid () method uses the data from the form to create a new model instance. To properly create an instance of the Repair class, it is necessary to provide a foreign key – a Car object that identifies the vehicle to which a given repair note relates.

The get_success_url () method specifies the address of the page to be displayed when a new note is successfully added. In this case, a page named car_detail as defined in cars / urls.py will be displayed.

class AddRepairView(LoginRequiredMixin, CreateView):
    model = Repair
    fields = ['date', 'description']
    success_message = 'New repair has been added'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['car'] = Car.objects.get(id=self.kwargs['pk'])
        return context

    def form_valid(self, form, **kwargs):
        form.instance.car = Car.objects.get(id=self.kwargs['pk'])
        return super().form_valid(form)

    def get_success_url(self, **kwargs):
        row = self.request.GET.get('row')
        p = self.request.GET.get('p')
        q = self.request.GET.get('q')
        options = '?p=' + p + '&row=' + row
        options += '&q=' + q
        return reverse_lazy('car_detail') + options

Django #3

You can get a source code here.

In previous posts, I described user management in the application, i.e. a new user can register – create an account on the site, a registered user can log in, log out or change the password.

Then I’ll describe the main functionality of the app – car and repair records.

For this purpose, in the project urls.py file, I will attach the mapping of addresses related to cars application.

After the changes, the project urls.py file will look like this:

from django.contrib import admin
from django.urls import path, include
from users import views as users_views
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('cars.urls')),
    path('register-user/', users_views.RegisterUser.as_view(
        template_name='users/register-user.html'), name='register_user'),
    path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),
    path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name='logout'),
    path('change-password/', users_views.ChangePassword.as_view(
        template_name='users/change-password.html'), name='change_password'),
]

The urls.py file created in the cars application also takes into account mapping addresses to appropriate views. The urls.py file from the cars application looks like this:

from django.urls import path
from .views import (
    CarsListView,
    AddCarView,
    RepairsListView,
    UpdateCarView,
    DeleteCarView,
    AddRepairView
)
urlpatterns = [
    path('', CarsListView.as_view(), name='cars'),
    path('add-car/', AddCarView.as_view(), name='add_car'),
    path('car/', RepairsListView.as_view(), name='car_detail'),
    path('car/<int:pk>/update/', UpdateCarView.as_view(), name='update_car'),
    path('car/<int:pk>/delete/', DeleteCarView.as_view(), name='delete_car'),
    path('car/<int:pk>/new-repair/', AddRepairView.as_view(), name='add_repair'),
]

The main view defined in the CarListView class displays all the user’s cars.

Remember to add the application in the project’s settings.py file in the INSTALLED_APPS list, e.g. for cars it will be an element: ‘cars.apps.CarsConfig’.

I used the pre-installed user model in the users application. In the cars application, I created models – objects mapping database tables.

These models will define: vehicle – Car model and repair – Repair model. The models.py file looks like this:

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.core.validators import MaxValueValidator, MinValueValidator


class Car(models.Model):
    make = models.CharField(max_length=10)
    model = models.CharField(max_length=10)
    vrn = models.CharField(max_length=10)
    year = models.IntegerField(default=timezone.now().year,
                               validators=[MinValueValidator(timezone.now().year - 100),
                                           MaxValueValidator(timezone.now().year)])
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return f'{self.make} {self.model}'


class Repair(models.Model):
    date = models.DateField(default=timezone.now)
    description = models.TextField()
    car = models.ForeignKey(Car, on_delete=models.CASCADE)

    def __str__(self):
        lead = self.description[:10] + '...'
        return lead

The Car model contains make, model, vrn, year, and user attributes that describe the vehicle make, model, vehicle registration number, and year of manufacture, as well as a foreign key containing the user object that created the vehicle entry.

The year field has validators that make it impossible to enter a car with a production year 100 years older than the current date and newer than the current year.

The on_delete option of the user attribute has the default value CASCADE, ie deleting a user will also delete all his cars.

The Repair model includes the attributes: date – repair date, description – repair details and a foreign key car – which car the repair concerns.

The __str__() method prints the header that is the beginning of the repair description (leading 10 characters).

To be able to edit entries of both models from the administration panel, you need to add both models to the admin.py file of the cars application, i.e.

from django.contrib import admin
from .models import Car, Repair

admin.site.register(Car)
admin.site.register(Repair)

In the next post, I will describe the views that define individual functionalities, i.e. displaying cars belonging to the user, adding repairs, and searching for cars using the search box on the website.

>>part 4<<

Django #2

The login and logout views are predefined as LoginView and LogoutView classes, so you only need to put the appropriate entries in the urls.py file (as described in part 1) and create template files, i.e.

login.html template:

{% extends 'cars/base.html' %}
{% load crispy_forms_tags %}


{% block content %}
  <div class="row justify-content-center">
    <div class="col-4">
      <form method="POST" class="form-control-sm">
       {% csrf_token %}
       <fieldset class="form-group">
         <legend class="border-bottom text-primary">
            Log In
          </legend>
          {{ form|crispy }}
        </fieldset>
        <button class="btn btn-secondary" type="submit">Login</button>
        <p class="text-muted text-small mt-5 ml-2">Need an Account? <a href="{% url 'register_user' %}">Sign up</a></p>
        </form>
      </div>
    </div>
{% endblock %}
login.html template

logout.html template:

{% extends 'cars/base.html' %}

{% block content %}
 <div class="row justify-content-center mb-5">
    <div class="col-4">
	   <h4 class="border-bottom text-primary mt-2">You're logged out now</h4>	
  <div class="text-muted text-small ml-2 mt-5">Back to: <a href="{% url 'login' %}">login page</a></div>
  </div>
 </div>
{% endblock %}

It still remains to create a view responsible for changing the password. The view class will inherit from the CreateView class as well as the LoginRequiredMixin class. The LoginRequiredMixin class adds the functionality that only the logged in user can change the password.

ChangePassword () view class code:

class ChangePassword(LoginRequiredMixin, CreateView):
    form_class = PasswordChangeForm

    def get(self, request, *args, **kwargs):
        form = PasswordChangeForm(request.user)
        return render(request, 'users/change-password.html', {'form': form})

    def post(self, request, *args, **kwargs):
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)
            messages.success(request, 'Password changed!')
            return redirect('cars')
        return render(request, 'users/change-password.html', {'form': form})

In the class above, I override the get() and post() methods.

When the page is displayed using the get() method, an empty password change form is displayed for the user.

When the page is displayed using the post () method, the submitted data is validated and, if correct, the changed password is saved, a password change message is displayed and redirected to a page named cars.

It remains to add the template file, ie change-password.html.

{% extends 'cars/base.html' %}
{% load crispy_forms_tags %}


{% block content %}
  <div class="row justify-content-center">
   <div class="col-4">
	  <form method="POST" class="form-control-sm">
      {% csrf_token %}
      <fieldset class="form-group">
        <legend class="border-bottom text-primary">
            New Password Settings
        </legend>
        {{ form|crispy }}
      </fieldset>
      <button class="btn btn-secondary" type="submit">Save</button>
    </form>
   </div>
  </div>
{% endblock %}
change-password.html template

>>part 3<<

Django #1

Below is a listing of the required packages for the project:

asgiref==3.2.5
Django==3.0.4
django-crispy-forms==1.9.0
pytz==2019.3
sqlparse==0.3.1

I recommend creating a separate virtual environment for the project (how to create a virtual environment I described here).

All packages except django-crispy-forms are installed when the django package is installed.

In the directory containing the venv subdirectory, create a project with the command: django-admin startproject project_name

Startproject command creates the project’s main directory and a manage.py file for managing the project.

You can then create a project administrator, i.e. a user with maximum administrative privileges, so that you can use the web admin panel. However, first you need to run a migrate: python manage.py migrate thanks to which the appropriate tables are created in the database, e.g. admin, auth etc.

After migration, you can create an administrative account with the command: python manage.py createsuperuser

The project directory contains the following files and directories:

├── project_name
│ ├── asgi.py
│ ├── init.py
│ ├── pycache
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
└── manage.py

By default, the data is placed in the sqlite3 database (db.sqlite3 file), which is a good solution when creating a project, and finally you can use a different database (Django includes support for PostgreSQL, MySQL and many others). Changing the default database only requires modifying the data from the DATABASES section of the project’s settings.py file.

In order to separate the individual components of the project, I made two components, i.e. users – responsible for user management and cars for vehicle management. In Django, these are called apps. To create them, type: python manage.py startapp users and python manage.py startapp cars

I will start with the users application, in which new users will be able to create accounts, log in, log out, and change their access password.

To define URL mapping to specific view classes, I modify the project urls.py file, i.e.

from django.contrib import admin
from django.urls import path
from users import views as users_views
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('register-user/', users_views.RegisterUser.as_view(
        template_name='users/register-user.html'), name='register_user'),
    path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),
    path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name='logout'),
    path('change-password/', users_views.ChangePassword.as_view(
        template_name='users/change-password.html'), name='change_password'),
]

For example, after entering the address ending with: register-user /, the class RegisterUser () is called from the views.py file and the assignment is given a name that we can use in templates.

For a class to be used as a view, its as_view () method must be called. As an argument I have given the template_name argument, in which I give the name of the template that displays the given page. You might as well pass this argument as a class attribute in a view, but for consistency with the rest of the views, I have included it here.

The code of the RegisterUser () class from the views.py file of the users application looks as follows (at the beginning I put the required imports):

from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm, PasswordChangeForm
from django.contrib.auth import login, authenticate
from django.views.generic import CreateView
from django.contrib import messages

class RegisterUser(CreateView):
    form_class = UserCreationForm

    def get(self, request, *args, **kwargs):
        return render(request, 'users/register-user.html', {'form': UserCreationForm()})

    def post(self, request, *args, **kwargs):
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password1')
            user = authenticate(username=username, password=password)
            login(request, user)
            messages.success(request, f'Welcome, {username}!')
            return redirect('cars')
        return render(request, 'users/register-user.html', {'form': form})

The RegisterUser () class inherits from the CreateView class, and the form is defined by the UserCreationForm class.

When the page is displayed using the GET method, an empty form appears, while if some data is already provided in the form, it is passed using the POST method.

To handle GET and POST, I override the get() and post() methods of RegisterUser(), respectively. In the post() method, the form is validated and if the form is correct, then the data from the form is saved, ie the user is created using the save() method. By default, this is a user with no administrative privileges. He is logged in using the data provided during registration. A new message with the tag Success will be displayed after redirecting to an address named cars. The message is displayed only once.

It remains to create the register-user.html template for RegisterUser(). For this purpose, I create a template directory in the users application according to the convention – in the templates directory, create another directory with the name consistent with the name of the application, i.e.

project_name/users/templates/users/register-user.html

The code of register-user.html is part of a larger whole, or more precisely, the base.html file from the cars application, which has not been created yet.

Applications can use their templates, so the look defined in cars in base.html is preserved in register-user.html

The template also enables the use of crispy_forms tags to make the form look better.

Each form must contain csrf_token, thanks to which the website is automatically immune to some forms of attacks (details: here).

Code of register-user.html file:

{% extends 'cars/base.html' %}
{% load crispy_forms_tags %}

{% block content %}
  <div class="row justify-content-center">
    <div class="col-4">
      <form method="POST" class="form-control-sm">
        {% csrf_token %}
        <fieldset class="form-group">
          <legend class="border-bottom text-primary">
            Registration
          </legend>
            {{ form|crispy }}
        </fieldset>
          <button class="btn btn-secondary" type="submit">Register</button>
      </form>
    </div>
  </div>
{% endblock %}

>>part 2<<

Django – web framework

In this series, I will describe the basics of the Django framework using the example of an application for recording repairs related to your car.

The application is similar to the one described in this posts, but the use of a web framework forced additional functionalities, e.g. user accounts support.

You can download the project code here.