[DRF]: extra field in serializer with related id - python

I've been trying hard but I can't find a solution for this, maybe you can help me:
I have 2 models: consumption and message.
The messages have a field "consumption" so I can know the consumption related.
Now I'm using DRF to get all the consumptions but I need all the messages related with them
This is my serializers.py file:
class ConsumptionSapSerializer(serializers.HyperlinkedModelSerializer):
client = ClientSerializer(many=False, read_only=True, allow_null=True)
call = CallSerializer(many=False, read_only=True, allow_null=True)
course = CourseSerializer(many=False, read_only=True, allow_null=True)
provider = ProviderSerializer(many=False, read_only=True, allow_null=True)
# messages = this is where I failed
class Meta:
model = Consumption
fields = [
"id",
"client",
"course",
"provider",
"user_code",
"user_name",
"access_date",
"call",
"speciality_code",
"expedient",
"action_number",
"group_number",
"start_date",
"end_date",
"billable",
"added_time",
"next_month",
"status",
"incidented",
"messages"
]
class MessageSapSerializer(serializers.ModelSerializer):
user = UserSerializer(allow_null=True)
class Meta:
model = Message
fields = [
"user",
"date",
"content",
"read",
"deleted"
]
I have read here Django REST Framework: adding additional field to ModelSerializer that I can make a method but I don't have the consumption ID to get all the messages related.
Any clue?

There is related_name that you can set in Message.consumption in models.py in order to call consumption.messages.
models.py
class Consumption(models.Model):
# some fields here
class Message(models.Model):
consumption = models.ForeignKey(Consumption, ...., related_name="messages")
P.S. Since you didn't show your models.py, I am just assuming Message.consumption is a ForeignKey.
More explanation on related_name here.

Related

How to get child model field value from parent model serializer in Django Rest Framework?

I am trying to access child model value from parent model serializer.
Parent Models
class Course(models.Model):
name = models.CharField(max_length=100)
Child Models
class CourseStaff(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE,
related_name='course_staff_course')
staff = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='course_staff_user')
enable = models.BooleanField()
Serializer Class
class TeacherCourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = '__all__'
Expected Result
[
{
id: 1,
name: "",
staff_id: 5, #<---- It will come from the child model.
is_enabled: true #<---- It will come from the child model.
}
]
I tried with this
Serializer
class TeacherCourseSerializer(serializers.ModelSerializer):
enable = serializers.CharField(source='course_staff_course.enable')
class Meta:
model = Course
fields = '__all__'
But getting error
Got AttributeError when attempting to get a value for field `enable` on serializer `TeacherCourseSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Course` instance.
Original exception text was: 'RelatedManager' object has no attribute 'enable'.
How can I achieve this?
Thanks
Course to course staff is one to many relationship. The related manager will not return a single object instead multiple objects. Instead you can try different approach if course is going to have only one staff use OneToOneField and continue in same way.
Other way is to create a nested serializer for staff and the use in the course serializer.
class CourseStaffSerializer(serializers.ModelSerializer):
class Meta:
model = CourseStaff
fields = '__all__'
class TeacherCourseSerializer(serializers.ModelSerializer):
teachers = CourseStaffSerializer(many=True, read_only=True)
class Meta:
model = Course
fields = '__all__'

Django rest framework unique together error message not displayed anymore

I worked with django rest framework a lot and everything worked fine until today. The same function suddenly returns something different. I have a unique together on my client model with name and company. I recently created a mixin called AutoCompany which automatically sets the company on the client.
My client model:
class Client(AutoCompany):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=155)
description = models.TextField(blank=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=["name", "company"], name="Name and company"),
]
AutoCompany
class AutoCompany(models.Model):
company = models.ForeignKey("company.Company", models.DO_NOTHING)
def save(self, *args, **kwargs):
company = apps.get_model("company.Company")
try:
self.company
except company.DoesNotExist:
self.company = get_request().user.company
self.after_company_set()
return super().save(*args, **kwargs)
def after_company_set(self):
pass
class Meta:
abstract = True
So previously when I created a client with the same name for the same company I got a 400 response with this error:
The fields name, company must make a unique set.
My serializer looks like this:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = models.Client
fields = ["id", "name", "description"]
But now I just get a 500 response with a python error. It seems like the error is not caught anymore. Now I just get:
django.db.utils.IntegrityError: (1062, "Duplicate entry 'test-cafd0ed10f9f4865a1d56abb67daa831' for key 'Name and company'")
Does anybody know why this change occurred?
DRF doesn't validate UniqueConstraints the way it does unique_together.
The reason for this is because unique constraints can be more complex than just "this collection of fields must have unique values". Consider the following:
UniqueConstraint(
fields=["author", "slug"],
condition=Q(status="published"),
name="author_slug_published_uniq",
)
This constraint only validates that the row is unique if the status is published, i.e. there can be multiple rows with the same slug and author if they are all drafts. Because of this complexity, there isn't a validator for this. However, for this simple case, you can add DRF's UniqueTogether validator manually:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
fields = ["id", "name", "description", "company"]
validators = [
UniqueTogetherValidator(
queryset=Client..objects.all(),
fields=["name", "company"],
)
]

How to create new model with foreignkey in django

class Schedule(models.Model):
name = models.CharField(max_length=50)
class ScheduleDefinition(models.Model):
schedule = models.ForeignKey(Schedule, on_delete=models.DO_NOTHING)
config = JSONField(default=dict, blank=True)
These are my models. I am trying to create a new ScheduleDefinition(The Schedule already exists and I know the ID I want to use for my foreign_key). I have a predefined Schedule id that I want to use, but it is not working..
Posting this body:
{
"schedule_id": 1,
"config": {
"CCC": "ccc"
}
}
Error I get:
null value in column "schedule_id" violates not-null constraint
What am I doing wrong? When I create new ScheduleDefinition models, the Schedule model will already be created previously. I am never going to be creating new Schedule's when I create new ScheduleDefinition's.
Serializer:
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ['id', 'name']
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(read_only=True, many=False)
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'config']
View:
from rest_framework import generics
from .models import Schedule, ScheduleDefinition
from .serializers import ScheduleSerializer, ScheduleDefinitionSerializer
class ScheduleList(generics.ListAPIView):
queryset = Schedule.objects.all()
serializer_class = ScheduleSerializer
class ScheduleDefinitionList(generics.ListCreateAPIView):
queryset = ScheduleDefinition.objects.all()
serializer_class = ScheduleDefinitionSerializer
class ScheduleDefinitionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = ScheduleDefinition.objects.all()
serializer_class = ScheduleDefinitionSerializer
View error:
File "/app/server/schedules/serializers.py", line 13, in ScheduleDefinitionSerializer
schedule_id = serializers.PrimaryKeyRelatedField(source="schedule")
File "/usr/local/lib/python3.7/dist-packages/rest_framework/relations.py", line 247, in __init__
super().__init__(**kwargs)
File "/usr/local/lib/python3.7/dist-packages/rest_framework/relations.py", line 108, in __init__
'Relational field must provide a `queryset` argument, '
AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.
You've specified Schedule as required (not null), but you aren't actually posting any information to it. Currently your serializer is expecting information in the form:
{
"schedule": {
"name": "Foo"
},
"config": {...}
}
schedule_id is being discarded when you post. Furthermore, you've specified that schedule is a read only field, meaning even if you posted a schedule, it would still be discarded.
If you'd like to post to foreign keys, you'll either need to specially handle it by manually writing the create/update logic for a writable nested serializer (which can be a bit of a hassle), or use a different (writable) foreignkey field serializer and serialize your other read-only data another way.
For example, the following setup should work (untested) with the data you're currently trying to POST:
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = serializers.PrimaryKeyRelatedField(
queryset=Schedule.objects.all()
)
schedule_name = serializers.CharField(read_only=True, source="schedule.name")
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'schedule_name', 'config']
With this your post should work, and you'll still have read-only access to the corresponding schedule's name via the schedule_name field in your list/detail views.
EDIT
My earlier version of the code would not have been compatible with the original desired POST data. The following should work without altering the POST
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(many=False, read_only=True)
schedule_id = serializers.PrimaryKeyRelatedField(
source="schedule",
queryset=Schedule.objects.all()
)
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'schedule_id', 'config']

Joining Models in Python DRF Serializer without introducing Foreign key

So I am relatively new to Django, and DRF. I recently started a project to learn more about the platform.
I also found best practice guide line which I am following at the moment. Here is the Django Style Guide
The guild line says that every App should completely decouple itself and use UUID instead of foreign key,
And every business rule should go into services.py (Which I like btw.)
Here comes the problem. I am not sure how I can construct two models together to produce a nested JSON Output.
Ex: I have Post() model and Comments() model. and Comment model uses uuid of Post model as a referential id.
Now in my API.py, I am not sure how I can join these two together in the Serializer.py
FYI the code below is only for demo purposes, may not be executable
Model
class Post(models.Model):
user_id = models.UUIDField(default=uuid.uuid4)
title = models.CharField(max_length=100)
description = models.CharField(max_length=100)
pictures = models.CharField(max_length=100)
lat = models.CharField(max_length=16)
long = models.CharField(max_length=16)
vote = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
post_id = models.UUIDField(default=uuid.uuid4)
parent_id = models.ForeignKey("self", on_delete=models.CASCADE)
text = models.CharField(max_length=1000)
up_vote = models.IntegerField()
down_vote = models.IntegerField()
user_id = models.UUIDField(default=uuid.uuid4)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now_add=True)
Service
def get_report(id) -> Dict:
logger.info('Get an Report by Id')
post= Post.objects.get(id=id)
return {
'post'= post
}
def get_comments(id) -> Dict:
logger.info('Get an Report by Id')
comments = Comment.objects.filter(post_id=id)
return {
'comments' = comments
}
API
class ReportGetApi(APIView):
class OutputSerializer(serializers.ModelSerializer):
comments = CommentsSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('id', 'title', 'description', 'pictures', 'lat', 'long', 'vote', 'comments')
class CommentsSerializer(serializers.ModelSerializer):
class Meta:
model = Comments
fields = ('post_id', 'parent_id', 'text', 'up_vote', 'down_vote', 'user_id', 'created_at', 'modified_at')
def get(self, request):
post = PostService.get_post() #Only one item
comments = PostService.get_comments() #Many Items
serializer = self.OutputSerializer(post, many=True)
return Response(serializer.data)
You actually don't need a services file to do what you're asking. DRF can handle what you want directly with serializers and using a RetrieveAPIView.
So using your models you have above you could have something like the following:
serializers.py
from rest_framework import serializers
from . import models
class CommentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
class Meta:
model = models.Comment
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
comments = serializers.SerializerMethodField()
class Meta:
model = models.Post
fields = '__all__'
def get_comments(self, obj):
comments = models.Comment.objects.filter(post_id=obj.id)
return CommentSerializer(comments, many=True).data
views.py
from rest_framework import generics
from . import serializers, models
class ReportApi(generics.RetrieveAPIView):
serializer_class = serializers.PostSerializer
queryset = models.Post.objects.all()
You would need to specify the URL to pass in the primary key of the Post object you want to retrieve like so:
urls.py
from django.urls import path
from api import views
app_name = 'api'
urlpatterns = [
path('get_post_report/<pk>/', views.ReportApi.as_view()),
]
Then you would access the view using something like http://example.com/api/get_post_report/12345678/.
Note: You must configure the urls.py within your project's
urls file to use 'api/' for including your app's urls for the
'/api/' part of the url above to be a part of the url.
If you don't know how to set up urls refer to the Django Tutorial
This will then give you something like the following:
response.json
{
"id": 1,
"comments": [
{
"id": 1,
"post_id": "00000000-0000-0000-0000-000000000001",
"text": "Comment Text",
"up_vote": 0,
"down_vote": 0,
"user_id": "00000000-0000-0000-0000-000000000001",
"created_at": "2020-05-29T13:14:07.103072Z",
"modified_at": "2020-05-29T13:14:07.103124Z",
"parent_id": null
}
],
"user_id": "00000000-0000-0000-0000-000000000001",
"title": "My Post",
"description": "Post Description",
"pictures": "No Pictures",
"lat": "12",
"long": "12",
"vote": "12",
"created_at": "2020-05-29T13:14:07.102316Z",
"modified_at": "2020-05-29T13:14:07.102356Z"
}
Lastly
I looked at the guide you are referring to and did not see a reference to decouple models with UUID's. While you can do that (and in some cases may be necessary) I would think hard on whether you truly need that much decoupling.
There are reasons to use foreign keys over UUID's such as accessing the related models easier and faster. Decoupling using UUID's means you will need to write more boilerplate code every time you need to access a related model.
It comes down to deciding whether you need decoupling or a better developer experience with (subjectively) cleaner code. Please don't just follow a guide and assume it's law. More experience will help with this.
For example, if you used a foreign key relationship your PostSerializer could look like the following:
example_serializers.py
class PostSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
comments = CommentSerializer(many=True)
class Meta:
model = models.Post
fields = '__all__'
See how we got rid of the get_comments(self, obj) method? That was just one bit of boilerplate that we got rid of by making the design decision to use foreign key relationships. Now just imagine a codebase of millions of lines of code and many serializers. Remember, the more code you write the more testing/debugging you need to do as well.
Again, just my opinion, but be sure you actually need to decouple your models before you do it.
Also, I strongly recommend you follow the DRF tutorial. It reviews everything you need to accomplish what I just posted here.
Hope this helps!
You could create one serializer called OutputSerializer and include the comments as its own field? if you change comments = OutputCommentSerializer to comments = CommentsSerializer(many=True, read_only=True) I believe that will depict what you want...(example below). You already defined CommentSerializer, thus you can use it.
class ReportGetApi(APIView):
class OutputSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('id', 'title', 'description', 'pictures', 'lat', 'long', 'vote', 'comments')
class CommentsSerializer(serializers.ModelSerializer):
class Meta:
model = Comments
fields = ('post_id', 'parent_id', 'text', 'up_vote', 'down_vote', 'user_id', 'created_at', 'modified_at')
def get(self, request):
post = PostService.get_post() #Only one item
comments = PostService.get_comments() #Many Items
serializer = self.OutputSerializer(post, many=True)
return Response(serializer.data)

How do I create a view different than the model with Django-rest-framework

So, I have this 2 classes of servers and datacenters;
class Datacenter(models.Model):
name = models.CharField(max_length=50)
status = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class Servers(models.Model):
datacenter = models.ForeignKey(Datacenter)
hostname = models.CharField(max_length=50)
def __unicode__(self):
return self.hostname
And want to create a view that returns the details of the datacenter plus all the servers that are related, so right now when I do;
http://127.0.0.1:8000/datacenter/1/
I'm getting something like;
{
"id": 1,
"name": "TestDC"
}
But what I'm actually looking to get is something like this;
{
"id": 1,
"name": "TestDC",
"Servers": [
{
"id": 1,
"hostname": "Server1",
},
{
"id": 2,
"hostname": "Server2",
}
]
}
Right now my view is this;
class DatacenterViewSet(viewsets.ModelViewSet):
queryset = datacenter.objects.all()
serializer_class = datacenterSerializer
and my serialiazer;
class DatacenterSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Datacenter
fields = ('id','name')
I also would like to have that server list in an other method like;
http://127.0.0.1:8000/datacenter/1/Servers
Any suggestions?
Nested Servers:
If you want (almost) exactly the output you gave as a sample, then it would be this:
class ServersSerializer(serializers.ModelSerializer):
class Meta:
model = Servers
fields = ('id', 'hostname')
class DatacenterSerializer(serializers.ModelSerializer):
servers = ServersSerializer(source='servers_set')
class Meta:
model = Datacenter
fields = ('id', 'name')
If you want to show all fields for both models, then just drop the 'fields' line.
This could also work without the source keyword argument, but would require the related name to match the 'servers' property name (you could do this by adding related_name='servers' to the datacenter field on the Servers model).
The docs for DRF are pretty good, the bits you care about are serializer relations
Deep URL:
To achieve the nested URL structure, you could simply make an url pattern that matches the above like so:
url(r'^datacenter/(?P<datacenter_id>\d+)/Servers$', 'views.dc_servers',name="dc_servers")
which would call your view with the ID of the Datacenter as the kwarg datacenter_id. You would then use that ID to filter the queryset of your view by datacenter_id.
You'll have to look into how to write that view yourself, here are the views docs to get you started.
A couple of general Django tips: Models should usually have singular names rather than plural and adding a related_name argument is usually a good thing (explicit over implicit).
To show the Servers you can do this on the serializer:
class DatacenterSerializer(serializers.HyperlinkedModelSerializer):
servers = serializers.HyperlinkedRelatedField(
many=True
)
class Meta:
model = Datacenter
fields = ('id','name', 'servers')
If you want to show several fields of the servers, you should create a ServerSerializer with the fields that you want to show and then replace the line:
servers = serializers.HyperlinkedRelatedField(
many=True
)
With:
servers = ServerSerializer(many=True)
For further information have a look at the doc: http://www.django-rest-framework.org/api-guide/relations/
Thanks you both for your answers, finally all I had to do was add the related_name in the model, now it looks like this;
class Datacenter(models.Model):
name = models.CharField(max_length=50)
status = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class Servers(models.Model):
datacenter = models.ForeignKey(Datacenter,related_name="servers")
hostname = models.CharField(max_length=50)
def __unicode__(self):
return self.hostname
Regarding the deep URL, I was checking the documentation and it should be possible to accomplish using a SimpleRouter, but couldn't find any example to see how to implement it; {prefix}/{lookup}/{methodname}/
http://www.django-rest-framework.org/api-guide/routers/

Categories

Resources