Content description:
In this post I'll describe how to create a ModelViewSet based on the created Warehouse model. Then I'll describe how to create a serializer for the given model. I'll describe how to create custom permissions for the created ViewSet. Finally I'll make tests for the created API.
First, I make simple Warehouse model with foreign key - a client (the warehouse owner).
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
In the views.py file, I place a ViewSet for the model. This class inherits from ModelViewSet. As attributes, I provide the serializer name and a list of required permissions. The class contains methods: get_queryset(), get_serializer_context(), and create(). The get_queryset() method filters warehouses and returns only those owned by the user. The get_serializer_context() method adds the logged-in user to the context. The create() method creates an instance of the model based on the data provided by the user.
from rest_framework import viewsets, permissions, status
from rest_framework.response import Response
from contracts.models import Warehouse
from ..serializers import WarehouseSerializer
from ..permissions.warehouse import WarehouseWritePermission
class WarehouseViewSet(viewsets.ModelViewSet):
serializer_class = WarehouseSerializer
permission_classes = [permissions.IsAuthenticated, WarehouseWritePermission]
def get_queryset(self):
user = self.request.user
return Warehouse.objects.filter(client=user)
def get_serializer_context(self):
context = super().get_serializer_context()
context.update({"client": self.request.user})
return context
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
The created serializer inherits from the ModelSerializer class. The fields attribute specifies all the fields returned by the serializer, with the client field being read-only (by default, the owner of the created warehouse is the logged-in user). The methods of the serializer class are to_representation(), validate(), and create(). The to_representation() method allows you to specify the client name instead of its ID number. The validate() method checks the supplied data when creating the instance. The create method is run when creating the serializer instance and adds the client field from the context in addition to the data supplied by the user.
from rest_framework import serializers
from contracts.models import Warehouse
class WarehouseSerializer(serializers.ModelSerializer):
class Meta:
model = Warehouse
fields = ["id", "warehouse_name", "warehouse_info", "client"]
extra_kwargs = {
"client": {"read_only": True},
}
def to_representation(self, instance):
warehouse = super().to_representation(instance)
warehouse['client'] = instance.client.username
return warehouse
def validate(self, attrs):
return super().validate(attrs)
def create(self, validated_data):
validated_data["client"] = self.context["client"]
return super().create(validated_data)
Creating warehouses is only available to users with the "client" profile. This is enabled by a permission class that inherits from the BasePermission class.
from rest_framework.permissions import BasePermission
class WarehouseWritePermission(BasePermission):
message = "No sufficient permissions"
def has_permission(self, request, view):
return request.user.profile == "client"
I register the given view in the router placed in the urls.py file.
from rest_framework import routers, urls
from .views import (
WarehouseViewSet,
ContractViewSet,
BookingViewSet,
)
router = routers.DefaultRouter()
router.register("warehouses", WarehouseViewSet, basename="warehouse")
router.register("contracts", ContractViewSet, basename="contract")
router.register("bookings", BookingViewSet, basename="booking")
urlpatterns = router.urls
Then I test all access methods for the logged in user.
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,
)
self.client.force_authenticate(user=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)