DRF Serializer foreign key reverse lookup - django

I'm trying to send the following HTTP Post API request to create a new EventInterest object. How can I accomplish this in a smallest payload instead of sending the entire object? I'm attempting an extra layer of security-through-obfuscation and instead of using the default integer pk, how can I use uuid for Event and username for User? .... Or do the extra SQL lookups negate the benefits of simplifying the payload and I should just use pk?
models.py
class Event(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(max_length=500)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True, blank=True)
class EventInterest(models.Model):
event = models.ForeignKey(Event)
sender = models.ForeignKey(settings.AUTH_USER_MODEL) # from User
api.py
class EventInterestViewSet(mixins.CreateModelMixin, GenericViewSet):
queryset = models.EventInterest.objects.all()
serializer_class = serializers.EventInterestSerializer
lookup_field = 'uuid'
serializer.py
class EventInterestSerializer(serializers.ModelSerializer):
# event = serializers.SlugRelatedField(read_only=True, slug_field='uuid')
# recipient = serializers.SlugRelatedField(read_only=True, slug_field='username')
# sender = serializers.SlugRelatedField(read_only=True, slug_field='username')
class Meta:
model = models.EventInterest
fields = (
'event', #works with pk, want uuid
'sender', # works with pk, want username
)
HTTP Post:
{
"event": "da9290c6-f6f8-4d27-bfe0-d388ed911fe8",
"sender":"eX8gkxJNDREv" //this is the username field
}

You need to make your UUIDField as primary key. Just like this:
class Event(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
....

As you defined uuid in Event model, you have to define uuid in User model too. In order to do that, you have to extend the default user model. Then you have to override the create() method of EventInterestSerializer to do a lookup on respective UUID field instead of pk
models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True)
class Event(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(max_length=500)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True)
class EventInterest(models.Model):
event = models.ForeignKey(Event)
sender = models.ForeignKey(User)
serializer.py
class EventInterestSerializer(serializers.ModelSerializer):
class Meta:
model = EventInterest
fields = ('event', 'sender',)
def create(self, validated_data):
try:
return EventInterest.objects.get(event__uuid=validated_data['event'],
sender__uuid=validated_data['sender'])
except EventInterest.DoesNotExist:
raise serializers.ValidationError("No matching data found")

Related

How to combine two different models on the basis of user and send a single response- Django Rest Framework

I have two different models in my project. The StudentDetail model has an one-to-one connection with the student-user and the EnrollmentList has a foreign key connection with the student user. I want to combine information from both the models for that specific student-user and send them as as a single response rather than sending different responses. Below are the models and their serializers
StudentDetail/models.py
class StudentDetail(models.Model):
id = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4)
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
name = models.CharField(max_length=100, null=True, blank=True)
StudentDetailSerializer
class StudentDetailSerializer(serializers.ModelSerializer):
class Meta:
model = StudentDetail
fields = "__all__"
Enrollment/models.py
class EnrollmentList(models.Model):
id = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4)
student = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='student')
EnrollSerializer
class AllReqs(serializers.ModelSerializer):
class Meta:
model = EnrollmentList
fields = ['id','student_name', 'student_parent_name', 'standard', 'applying_for_standard', "board", 'home_tuition', 'address']
Now suppose a request is made, I want to combine the information from the StudentDetail and EnrollmentList for that specific student user to get a serialized data which may look like below example and send this as a single response
{
"student_name": name, #from StudentDetail
"home_tuition": home_tuition #from EnrollmentList
}
Please suggest to me the correct way to do it
Define your serializer like that:
class AllReqs(serializers.ModelSerializer):
student_name = serializers.CharField(source='student.user.name')
class Meta:
model = EnrollmentList
fields = ['id','student_name', 'home_tuition']
additional StudentDetail serializer:
class StudentDetailSerializer(serializers.ModelSerializer):
student_name = serializers.CharField(source='user.name')
home_tuition = serializers.SerializerMethodField()
class Meta:
model = StudentDetail
fields = ['id','student_name', 'home_tuition']
def get_home_tuition(self, obj):
for enrollment in obj.user.students.all():
return enrollment.home_tuition
I kindly ask the questioner to refer to the Django-Rest-Framework documentation. https://www.django-rest-framework.org/

Django: how to include missing pk field into serializer when updating nested object?

I have a serializer in my Django app that is meant for updating a nested object. Updating works, but I'm facing another problem: I can't delete objects that are not in validated_data['events] because I don't have the id to be compared with my instance id's.
For reference, these are my Models:
class Plan(models.Model):
planId = models.CharField(primary_key=True, max_length=100, unique=True)
name = models.CharField(max_length=200)
class PlanEvent(models.Model):
plan = models.ForeignKey(Plan, on_delete=models.CASCADE)
id = models.CharField(primary_key=True, max_length=100, unique=True, blank=False, null=False)
done = models.BooleanField()
title = models.CharField(max_length=100, blank=True)
This is my PlanEventUpdateSerializer:
class PlanEventUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = PlanEvent
fields = ('done', 'title')
Is there some way to include the id, so I could compare the id's like this in my update method:
class PlanUpdateSerializer(serializers.ModelSerializer):
events = PlanEventUpdateSerializer(many=True)
class Meta:
model = Plan
fields = ('name',)
....
def update(self, instance, validated_data):
events_validated_data = validated_data.pop('events')
events = (instance.events.all())
events = list(events)
event_ids = [item['id'] for item in events_validated_data]
for event in events:
if event.id not in event_ids:
event.delete()
I found a solution. I defined the id as a optional field in the serializer and then I was able to include it in the fields. Sending POST and PUT requests works now and I'm also able to delete objects when updating:
class PlanEventUpdateSerializer(serializers.ModelSerializer):
id = serializers.CharField(source='pk', required=False)
class Meta:
model = PlanEvent
fields = ('id', 'done', 'title')

How to return data from two different tables in django?

I have two models in my models.py. I need to return a json response which includes data from two tables.
How should my view and serializer look like?
class Device(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
device_name = models.CharField(max_length=200, null=True, blank=True )
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.device_name)
class StatusActivity(models.Model):
OFFLINE = 1
ONLINE = 2
STATUS = (
(OFFLINE, ('Offline')),
(ONLINE, ('Online')),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
device_id = models.ForeignKey(Device, related_name='StatusActivity', on_delete=models.CASCADE)
changed_to = models.PositiveSmallIntegerField(choices=STATUS)
modified_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.device_id)
Expected Response:
{
"device_id":"",
"device_name":"",
"changed_to":"",
"modified_at":"",
}
UPDATE:
I set my views.py and serializer.py as below. I am checking
Serializer.py
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = '__all__'
class StatusActivitySerializer(serializers.ModelSerializer):
class Meta:
model = StatusActivity
fields = '__all__'
class ListSerializer(serializers.Serializer):
# devices = DeviceSerializer(many=True)
# activities = StatusActivitySerializer(many=True)
class Meta:
model = [Device, StatusActivity]
fields = ['device_id', 'device_name', 'changed_to', 'modified_at']
Views.py
class DeviceListView(generics.ListAPIView):
queryset = Device.objects.all()
serializer_class = ListSerializer
class StatusActivityListView(generics.ListAPIView):
queryset = StatusActivity.objects.all()
serializer_class = StatusActivitySerializer
Actually you don't need to have two separated views for this, because you can easily serialize relations from one serializer class.
Take a look at this useful answer: How do I include related model fields using Django Rest Framework?
For your case you can write something like this:
class StatusActivitySerializer(serializers.ModelSerializer):
device_name = serializers.CharField(source='device_id.device_name')
class Meta:
model = StatusActivity
fields = ('changed_to', 'modified_at', 'device_id', 'device_name')
Something that worth to note:
it's a good idea for ForeignKey field use device name instead of
device_id;
related_name arg should have a name for reverse access. Keep it
meaningful, e.g. status_activities is a good choice.
filter the status activity. you can refer the foreignkey.
eg:
items = []
for statusact in StatusActivity.objects.all():
items.append({
"device_id":statusact.device_id.id,
"device_name":statusact.device_id.device_name,
"changed_to":statusact.changed_to,
"modified_at":statusact.modified_at,
})

How to use a model field for multiple models in Django Rest Framework

I have a model BstUserActionLog with a foreign key to Django model User. I have another model for user profile information, BstUserProfile. When I do serialize BstUserActionLog with ModelSerializer I do have Django User info serialized as it is supposed to be. But I also need to add BstUserProfile serialized using the same user_id used for User model.
How can I serialize BstUserActionLog with model User and BstUserProfile are both serialized?
From my models.py:
class BstUserActionLog(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
bst_action_type = models.ForeignKey(BstActionType)
action_date = models.DateTimeField(auto_now_add=True)
bst_book = models.ForeignKey(BstBook)
new_value_id = models.IntegerField(blank=True, null=True)
old_value_id = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'bst_user_action_log'
class BstUserProfile(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User, unique=True)
website = models.CharField(max_length=200)
picture = models.CharField(max_length=100)
is_avatar_uploaded = models.BooleanField(default=False)
is_cover_uploaded = models.BooleanField(default=False)
class Meta:
managed = False
db_table = 'bst_user_profile'
app_label = 'bst'
From my serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id','username',)
class BstUserActionLogSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = BstUserActionLog
fields = ('id', 'user', 'bst_action_type', 'action_date', 'bst_book', 'new_value_id', 'old_value_id')
depth = 3
The key to my solution is SerializerMethodField. With this a new field can be added which is calculated with a method. This method signature contains the object to be serialized. After that a regular method serializer is used to return the serialized object.
From my serializers.py
class BstUserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = BstUserProfile
fields = ('is_avatar_uploaded', 'is_cover_uploaded')
class BstUserActionLogSerializer(serializers.ModelSerializer):
user = UserSerializer()
user_profile = serializers.SerializerMethodField()
def get_user_profile(self, obj):
try:
user_profile = BstUserProfile.objects.get(user_id=obj.user_id)
return BstUserProfileSerializer(user_profile).data
except Exception as e:
return {}
class Meta:
model = BstUserActionLog
fields = ('id', 'user', 'user_profile', 'bst_action_type', 'action_date', 'bst_book', 'new_value_id', 'old_value_id')
depth = 3

DjangoRestFramework - how to serialize a specific ForiegnKey field rather than PK value

This is my model:
class Post(models.Model):
owner = models.ForeignKey(User, related_name="%(app_label)s%(class)s_set")
usersVoted = models.ManyToManyField(User, blank=True)
post = models.CharField(max_length=400)
and this is my serializer:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('owner', 'usersVoted', 'post')
read_only_fields = ('owner', 'usersVoted')
def create(self, validated_data):
drop = Drop(
owner = User.objects.get(username='TestUser'),
post = validated_data['post'],
)
Owner has a ForeignKey to the default Django User model. Currently when I serialize a post, owner is the pk value of the user. How do I make owner the username of the user instead?
You can do like:
class PostSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source="owner.username")
class Meta:
model = Post
fields = ('owner', 'usersVoted', 'post')
read_only_fields = ('owner', 'usersVoted')
def create(self, validated_data):
drop = Drop(
owner = User.objects.get(username='TestUser'),
post = validated_data['post'],
)
You should use the SlugRelatedField provided that the username is unique. This will make the fields read/write which is very handy.
Documentation is available at http://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield