How to make dynamic field serializer in Django RestFramework - django

I want to add Dynamic Field Array serializer in my drf project:
My get response looks something like this:
{
"title": "some",
"created_at": "2022-03-06T15:59:52.684469Z",
"fields": [
{
"id": 1,
"title": "Some title?",
"parent_field": 1
},
{
"id": 2,
"title": "Yet another fields",
"parent_field": 1
}
]
}
This is the item detail serializer, and fields is another model serializer. I achieved this by using this code:
class AnotherFieldSerializer(serializers.ModelSerializer):
class Meta:
model = AnotherModel
fields = "__all__"
class FirstSerializer(serializers.ModelSerializer):
fields = serializers.SerializerMethodField()
class Meta:
model = FirstModel
fields = "__all__"
def get_fields(self, obj):
serializer_context = {'request': self.context.get('request')}
children = obj.fields
if not children.exists():
return None
serializered_children = FirstSerializer(
children,
many=True,
context=serializer_context
)
return serializered_children.data
This works only for GET requests I want this to also work with POST and PUT requests. So, imagine I want to add/edit an item into my model FIRST model and add fields associated with it by just sending this JSON:
{
"title": "some",
"created_at": "2022-03-06T15:59:52.684469Z",
"fields": [
{
"id": 1,
"title": "Some title?",
},
{
"id": 2,
"title": "Yet another fields",
}
]
}
I know I can get fields from the response and loop through each item to create an instance of Another Model but fields validation will be much harder I think. But if there's more of a drf of doing this thing then it would be great. Also, I have no problem with making another serializer just for POST and PUT requests.
I hope my English was understandable.

You can create a new serializer for validation. To validate multiple items you can do the following,
class PostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=200)
created_at = serializers.DateTimeField()
fields = FirstSerializer(many=True) # your model serializer for the FirstModel
Ref : https://www.django-rest-framework.org/api-guide/serializers/#listserializer

Related

Custom Serializer for Django Rest Framework

I'm searching for a solution to create a serializer / API Endpoint to represent data in a custom order. When adding serializers and viewsets to DRF, I only get the fields associated with that Model. But what I like to have is a custom structure of all my models together. As an example:
I have a model called season, a model called evenings and a model called events. Now I'd like to have an API Endpoint to have that all together, like so:
{
"requestTime": "2021-11-09 08:20",
"requestURL": "/all",
"requestMethod": "GET",
"responseCode": 200,
"season": "2021/2022",
"evenings": [
{
"evevning_id": 0,
"day": "",
"date": "2021-11-11",
"event_count": 2,
"events": [
{},
{}
]
}
]
}
For data structure in the models I have some ForeignKeys like:
season
|
evening
|
event
Any suggestions how to achieve this?
Use nested serializer (Season > Evening > Event) like this.
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = models.Event
fields = ['id',...]
class EveningSerializer(serializers.ModelSerializer):
events = EventSerializer(many=True)
class Meta:
model = models.Evening
fields = ['id', 'day', 'date','events',...]
class SeasonSerializer(serializers.ModelSerializer):
evenings = EveningSerializer(many=True)
class Meta:
model = models.Season
fields = ['id', 'season', 'evenings',...]
make sure when fetching season from database, use prefetch related in queryset.

Serializing many to many relationship with attributes does not show attribute in the relationship

My conceptual model is that there are DemanderFeature objects which have LoadCurve objects linked to them in a many-to-many relationship, along with a single attribute indicating "how many times" the two are associated, using an attribute in the many-to-many relationship called number.
I have been struggling for quite a while now, reading many answers on stackoverflow but I just cannot get it to work in exactly the way that I want. This is my desired output, when looking at the detail view of a DemanderFeature:
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"name": "testdemander",
"loadcurves": [
{"name": "lc", "number": 5},
{"name": "lc2", "number": 10}
],
// Other DemanderFeature fields removed for brevity...
}
]
The closest I have been able to get to this is with this setup:
Models
class LoadCurve(models.Model):
created = models.DateTimeField(auto_now_add=True)
finalized = models.BooleanField(default=False)
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
name = models.CharField(max_length=100)
length = models.IntegerField(default=0)
deleted = models.BooleanField(default=False)
demanderfeatures = models.ManyToManyField("DemanderFeature", through="DemanderFeatureToLoadCurveAssociation")
class Meta:
ordering = ['name']
constraints = [
models.UniqueConstraint(fields=["owner", "name"], condition=models.Q(deleted=False), name="loadcurve_unique_owner_name")
]
class DemanderFeature(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100)
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
demanderfeaturecollection = models.ForeignKey(DemanderFeatureCollection, on_delete=models.CASCADE)
loadcurves = models.ManyToManyField("LoadCurve", through="DemanderFeatureToLoadCurveAssociation")
deleted = models.BooleanField(default=False)
geom = gis_models.PointField(default=None)
class Meta:
ordering = ['name']
constraints = [
models.UniqueConstraint(fields=["owner", "demanderfeaturecollection", "name"], condition=models.Q(deleted=False),
name="demanderfeature_unique_owner_demanderfeaturecollection_name")
]
class DemanderFeatureToLoadCurveAssociation(models.Model):
loadcurve = models.ForeignKey(LoadCurve, on_delete=models.CASCADE)
demanderfeature = models.ForeignKey(DemanderFeature, on_delete=models.CASCADE)
number = models.IntegerField()
Serializers
(I am using __all__ for the sake of debugging, so that I can see everything that is being serialized and available)
class LoadCurveSerializer(serializers.ModelSerializer):
class Meta:
model = LoadCurve
fields = "__all__"
class DemanderFeatureToLoadCurveAssociationSerializer(serializers.ModelSerializer):
class Meta:
model = DemanderFeatureToLoadCurveAssociation
fields = "__all__"
class DemanderFeatureSerializer(serializers.ModelSerializer):
demanderfeaturecollection = serializers.SlugRelatedField(slug_field="name", queryset=DemanderFeatureCollection.objects.all())
loadcurves = LoadCurveSerializer(read_only=True, many=True)
# loadcurves = DemanderFeatureToLoadCurveAssociationSerializer(read_only=True, many=True)
class Meta:
model = DemanderFeature
fields = "__all__"
lookup_field = "name"
There is a commented line in the previous code block which I was trying to use to get the DemanderFeatureToLoadCurveAssociationSerializer because I thought this would be the proper way to get the number field which its related model defines, but when I uncomment this line (and comment the line just below it) I only get this error:
AttributeError at /demanderfeatures/
Got AttributeError when attempting to get a value for field `number` on serializer `DemanderFeatureToLoadCurveAssociationSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `LoadCurve` instance.
Original exception text was: 'LoadCurve' object has no attribute 'number'.
If I do not swap those lines, however, I get this as a result:
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"name": "testdemander",
"loadcurves": [
{
"id": 1,
"created": "2020-12-29T11:29:11.585034Z",
"finalized": true,
"name": "lc",
"length": 0,
"deleted": false,
"owner": 1,
"demanderfeatures": [
1
]
},
{
"id": 2,
"created": "2020-12-29T12:46:31.044624Z",
"finalized": true,
"name": "lc2",
"length": 0,
"deleted": false,
"owner": 1,
"demanderfeatures": [
1
]
}
],
// Other DemanderFeature fields removed for brevity...
}
]
Which does not contain that critical number field which is defined in the DemanderFeatureToLoadCurveAssociation model.
I feel like I am just missing something quite obvious but I have not been able to find it.
I'm putting my specific implementation here in case it is useful to anyone. It is adapted from #JPG's own Q&A which was made as a response to this question (probably because it would be a pain to try to re-create my entire ORM and environment). I adapted their code to my own implementation.
It is worth noting however that this implementation only works one way. I originally tried implementing it with such a conceptual mapping:
DemanderFeature -> Person
LoadCurve -> Group
Membership -> DemanderFeatureToLoadCurveAssociation
But since I wanted to see all the associated LoadCurve objects when retrieving a DemanderFeature object, I actually needed to flip this mapping around so that DemanderFeature objects are equivalent to Group objects and LoadCurve objects are equivalent to Person objects. It is obvious to me now why this is (I am conceptualizing the DemanderFeature as a GROUP of LoadCurves), but at the time I didn't see the technical difference.
Anyway here is the updated serializers.py. Thanks again #JPG for taking the time!
class LoadCurveSerializer(serializers.ModelSerializer):
class Meta:
model = LoadCurve
fields = ["name"]
def serialize_demanderfeaturetoloadcurveassociation(self, loadcurve_instance):
# simple method to serialize the through model fields
demanderfeaturetoloadcurveassociation_instance = loadcurve_instance \
.demanderfeaturetoloadcurveassociation_set \
.filter(demanderfeature=self.context["demanderfeature_instance"]) \
.first()
if demanderfeaturetoloadcurveassociation_instance:
return DemanderFeatureToLoadCurveAssociationSerializer(demanderfeaturetoloadcurveassociation_instance).data
return {}
def to_representation(self, instance):
rep = super().to_representation(instance)
return {**rep, **self.serialize_demanderfeaturetoloadcurveassociation(instance)}
class DemanderFeatureToLoadCurveAssociationSerializer(serializers.ModelSerializer): # create new serializer to serialize the through model fields
class Meta:
model = DemanderFeatureToLoadCurveAssociation
fields = ["number"]
class DemanderFeatureSerializer(serializers.ModelSerializer):
demanderfeaturecollection = serializers.SlugRelatedField(slug_field="name", queryset=DemanderFeatureCollection.objects.all())
loadcurves = serializers.SerializerMethodField()
class Meta:
model = DemanderFeature
fields = ["name", "demanderfeaturecollection", "loadcurves", "geom"]
# fields = "__all__"
lookup_field = "name"
def get_loadcurves(self, demanderfeature):
return LoadCurveSerializer(
demanderfeature.loadcurve_set.all(),
many=True,
context={"demanderfeature_instance": demanderfeature} # should pass this `group` instance as context variable for filtering
).data

nested serialize Django object

hi guys i want to serialize my model's object. and my model like this :
class User (models.Model):
name = models.CharField(max_length=30)
family_name = models.CharField(max_length=30)
and i have another model like this:
class Child (models.Model):
little_name = models.CharField(max_length=30)
user = models.ForeignKey("User", on_delete=models.CASCADE,)
i want to serialize one of my child object with all fields in user field for example like this:
{"id": 1 ,
"little_name": "Sam",
"user": {"id": 1,
"name": "Karim",
"family_name":"Kari"
}
}
i use model_to_dict() and dict() and serializer of django but i can't get user field compeletly and they return it like this :
{"id": 1 ,
"little_name": "Sam",
"user": "id": 1,
}
and i don't want to use django rest serializer
what can i do ?!?
Use model_to_dict twice:
child_dict = model_to_dict(child)
child_dict['user'] = model_to_dict(child.user)

Django rest framework post request while using ModelViewSet

I am new to Django have background in Node.js.
I am creating an API using Django, Django rest framework and PostgreSQL.
# Model for Event.
class Event(models.Model):
heading = models.TextField()
event_posted_by = models.ForeignKey(
CustomUser, on_delete=models.CASCADE, related_name='events_posted', null=True)
event_construction_site = models.ForeignKey(
ConstructionSite, on_delete=models.CASCADE, related_name='construction_site_events', null=True)
def __str__(self):
return str(self.id)
# Event ViewSet
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
# Event Serializer
class EventSerializer(serializers.ModelSerializer):
event_construction_site = ConstructionSiteShortSerializer()
event_posted_by = CustomUserSerializer()
class Meta:
model = Event
fields = ('id', 'heading',
'event_posted_by', 'event_construction_site')
Everything here works fine and expected as well.
Here is the output of my GET Request.
{
"id": 2,
"heading": "ABCD",
"event_posted_by": {
"id": 1,
"email": "abcd#gmail.com",
"first_name": "A",
"last_name": "B",
"profile_picture": "...",
"company": {
"id": 3,
"name": "Company 3"
},
},
"event_construction_site": {
"id": 2
}
},
But now when it comes to create an event this is the view django rest framework shows me.
{
"heading": "",
"event_posted_by": {
"email": "",
"first_name": "",
"last_name": "",
"company": {
"name": ""
},
"profile_picture": "",
"user_construction_site": []
},
"event_construction_site": {}
}
Here as far as I know when need "event_posted_by" by in GET Request but we don't want to post the user info. while creating an event, same for information inside user like user_construction_site here the only behaviour I want is to send the User ID to backend and somehow in my class EventViewSet(viewsets.ModelViewSet) handle this post request and map "event_posted_by" to user data by using user ID sent from client side.
How these kind of problems are genrally handeled in DRF by creating different read / write serailizers?
I managed to resolve the problem by following suggestion by #BriseBalloches in the comment above.
In summary updated the event_posted_by by adding a read_only key.
event_posted_by = UserSerializer(read_only=True)

Django serializer field not working

I did not find a solution although I looked at the questions asked...
When I use this serializer:
class MessageSerializer(ModelSerializer):
sender = UserMobileSerializer(read_only=True)
class Meta:
model = Messages
fields = '__all__'
I get something like this:
{
"id": 62,
"sender": {
"pk": 12,
"email": "john#gmail.com",
"full_name": "John",
"profile_photo": null
},
"created_at": "2018-04-29T00:54:50.437662",
"message": "sdkjnasljdhkajsjdlasdasda",
"read_at": false,
"target": 18
}
I would like the target field to be like sender, that is: display the full user information instead of just the ID.
I tried to add this line: target = UserMobileSerializer(), but I still get only the ID in the output. I also tried target = UserMobileSerializer(read_only=True) but nothing changed.
You are not adding the field you defined to the fields in the serializer's Meta. Try this:
class MessageSerializer(ModelSerializer):
sender = UserMobileSerializer(read_only=True)
class Meta:
model = Messages
fields = ('your', 'fields', 'sender')
EDIT: You need to serialize target, too. Like this:
class TargetSerializer(ModelSerializer):
class Meta:
model = Target
fields = ('id', 'title') # Add fields you want to get in the response.
class MessageSerializer(ModelSerializer):
target = TargetSerializer(read_only=True) # You should have TargetSerializer defined
sender = UserMobileSerializer(read_only=True)
class Meta:
model = Messages
fields = ('your', 'fields', 'target', 'sender')