[PYTHON] [Django] A pattern to add related records after creating a record

There are many cases where you want to create a linked record when registering a record on the site or adding a record using the API. This time, I've put together the code that makes it possible with various Django patterns. Please note that the examples given here are only those that you have implemented, so you can implement them in many other ways.

There is also an implementation in django-rest-framework (DRF).

Django

Use signals

The simplest and most versatile pattern is to use signals. When a specific action occurs in Django, a signal is sent accordingly, and you can perform a specific process.

from django.contrib.auth.models import AbstractBaseUser
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.crypto import get_random_string

class User(AbstractBaseUser):
    pass


class Token(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='token')
    token = models.CharField("token", max_length=40)


@receiver(post_save, sender=User)
def create_token(sender, **kwargs):
    """Generate token when creating user"""
    if kwargs['created']:
        #Get instead of hash because it's an example_random_string
        Token.objects.create(user=kwargs['instance'],
                             token=get_random_string(length=40))

Since it is a signal when the record is saved, it receives the signal post_save. The function that receives the signal uses the decorator @ receiver.

When created, kwargs ['created'] = True. The created instance is stored in kwargs ['instance'], which can be used to create related records.

advantage

The advantage of using signals is that you can also generate related records by adding records from the management site. Since it is executed by any creation method such as management site, shell, Form, API, it can be used in various patterns. Conversely, it also means that records will be created in any situation.

Added to creation method

Django's design recommends working with Model Manager to work with records. Create a method for creating a record in ModelManager and create a related object in that method.

from django.contrib.auth.models import BaseUserManager

class UserManager(BaseUserManager):

    use_in_migrations = True

    def create_user_and_token(self, email, password, **extra_fields):
        if not email:
            raise ValueError('The given username must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        #Creating related objects
        Token.objects.create(user=user,
                             token=get_random_string(length=40))
        return user

class User(AbstractBaseUser):
    objects = UserManager()

advantage

If you give it a descriptive name like create_user_and_token, The good thing is that you can recognize that it is a method that also creates related objects.

You can also pass arguments, even if the structure of the associated object changes Maintainability is high because only this method needs to be changed.

Use form_valid

With generic.CreateView, you can do this by writing a little form_valid.

from django.views.generic import CreateView
from django.contrib import messages

class View(CreateView):
   form_class = UserModelFormClass # modelForm

    def form_valid(self, form):
        self.object = form.save()
        #Creating related objects
        token = Token.objects.create(user=self.object,
                                     token=get_random_string(length=40))
        messages.success(self.request, f"I also created a token: {token.token}")
        return HttpResponseRedirect(self.get_success_url())

advantage

The advantage is that it requires a relatively small amount of description. CreateView is often used to create records, so it's an option. Also, if you want to display a message using the created object, it seems good to describe it here.

Django REST Framework

Use serializers.SerializerMethodField ()

In DRF, you can define a field called SerializerMethodField in serializer. The value defined in this field returns the return value of the method get_ <field_name>.

from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    token = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = (
            'pk',
            'email',
            'name',
            'password',
            'profile',
            'token',
        )

    def get_token(self, instance):
        #instance contains the created instance
        return APIToken.object.create(instance).token

advantage

When creating data with API and returning related values created at the same time, it is good to be able to write simply. Overriding create in ViewSet tends to make the View longer.

Summary

To be honest, I think it's better to use signals or implement creation methods for most use cases, but I was introduced that there are some things that can be written neatly.

Recommended Posts

[Django] A pattern to add related records after creating a record
Steps from installing Python 3 to creating a Django app
Add a dictionary to MeCab
Creating a Shogi Game Record Management App Using Django 1-Environment Construction-
To add a C module to MicroPython ...
Steps to create a Django project
Errors related to memcached in django
[Django 2.2] Add a New badge to new posts with a date using a template filter
[Discord.py] How to add or remove job titles after a reaction [python]
Add a GPIO board to your computer. (1)
How to add a package with PyCharm
Commands for creating a new django project
Creating a login screen with Django allauth
Creating a shell script to write a diary
Add a Python virtual environment to VSCode