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
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.
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.
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()
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.
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())
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
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
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.
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