I have a Room object, which will have some settings. I wanted to make Room and RoomSettings different objects but bound to each other. RoomSettings will be created whenever a Room is created, so I'm using signals. However, while creating the room, I need to pass RoomSettings arguments from Room to RoomSettings through signals, so I can set the settings in creation.
models.py
from django.db import models
from users.models import AppUser as UserModel
from django.core.validators import MaxValueValidator, MinValueValidator
import random, string
from datetime import datetime
def generate_unique_key():
length = 10
while True:
key = ''.join(random.choices(string.ascii_uppercase, k=length))
if Room.objects.filter(key=key).count() == 0:
break
return key
class Room(models.Model):
host = models.ForeignKey(UserModel, on_delete=models.CASCADE, related_name='rooms')
key = models.CharField(max_length=10, default=generate_unique_key, unique=True, editable=False)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
def __str__(self):
return f'{self.key} - {self.host.username}'
def get_chat(self):
return self.chat
class RoomSettings(models.Model):
room = models.OneToOneField(Room, on_delete=models.CASCADE, related_name='settings')
max_users = models.PositiveIntegerField(default=8, validators=[MaxValueValidator(8), MinValueValidator(1)])
is_public = models.BooleanField(default=True)
users_can_send_message = models.BooleanField(default=True)
serializers.py
from rest_framework import serializers
from .models import Room, RoomSettings
from users.serializers import UserSerializer
from chats.serializers import RoomChatSerializer
class SettingSerializer(serializers.ModelSerializer):
class Meta:
model = RoomSettings
exclude = ['room']
class CreateRoomSerializer(serializers.ModelSerializer):
max_users = serializers.IntegerField()
is_public = serializers.BooleanField()
users_can_send_message = serializers.BooleanField()
class Meta:
model = Room
fields = ['max_users', 'is_public', 'users_can_send_message']
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Room, RoomSettings
from chats.models import Chat
@receiver(post_save, sender=Room)
def post_save_create_room(sender, instance, created, **kwargs):
if created:
RoomSettings.objects.create(room=instance, **kwargs)
Chat.objects.create(room=instance)
views.py
class CreateRoomView(CreateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = CreateRoomSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
room = Room.objects.create(host=self.request.user, **serializer.data)
return JsonResponse(RoomSerializer(room).data, status=status.HTTP_201_CREATED)
I tried to pass arguments to the endpoint for CreateRoomView, but got errors. It should create Room and RoomSettings with given data.
I don't really see why you need a RoomSettings
model in the first place, especially since you each time create a RoomSettings
record for a Room
. The purpose of a OneToOneField
is to make the extra model (here RoomSettings
) optional, but here you seem to "juggle" with two models where there is no use-case to split these in two.
Just join these together in one model:
from django.conf import settings
def generate_unique_key():
length = 10
while True:
key = ''.join(random.choices(string.ascii_uppercase, k=length))
if not Room.objects.filter(key=key).exists():
break
return key
class Room(models.Model):
host = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='rooms',
editable=False,
)
key = models.CharField(
max_length=10, default=generate_unique_key, unique=True, editable=False
)
created_at = models.DateTimeField(auto_now_add=True)
max_users = models.PositiveIntegerField(
default=8, validators=[MaxValueValidator(8), MinValueValidator(1)]
)
is_public = models.BooleanField(default=True)
users_can_send_message = models.BooleanField(default=True)
def __str__(self):
return f'{self.key} - {self.host.username}'
def get_chat(self):
return self.chat
This simplifies the serializer to:
class RoomSerializer(serializers.ModelSerializer):
max_users = serializers.IntegerField()
is_public = serializers.BooleanField()
users_can_send_message = serializers.BooleanField()
class Meta:
model = Room
fields = ['max_users', 'is_public', 'users_can_send_message']
and thus creating the object to:
class CreateRoomView(CreateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = RoomSerializer
def perform_create(self, serializer):
serializer.save(host=request.user)
Note: It is normally better to make use of the
settings.AUTH_USER_MODEL
[Django-doc] to refer to the user model, than to use theUser
model [Django-doc] directly. For more information you can see the referencing theUser
model section of the documentation [Django-doc].
Note: It is better to work with
.exists()
[Django-doc] to check if a record exists than to work with.count()
[Django-doc] since counting might have to keep looking for records after finding one.