How to use custom variables in serializers? - python

I want to create a serializer that uses the variables from my model and also counts how many data of the same id is found in the table.
I have created this, but it doesn't work:
class WebsiteSerializer(serializers.Serializer):
item_nr = serializers.IntegerField()
class Meta:
model = URL
fields = (
"id",
"item",
"status",
"item_nr "
)
def get_item_nr (self, obj):
obj.item_nr = Items.objects.filter(item_id=self.context.get(id)).count()
return obj.item_nr
This is the error that I get:
Got AttributeError when attempting to get a value for field item_nr
on serializer WebsiteSerializer. The serializer field might be named
incorrectly and not match any attribute or key on the URL instance.
Original exception text was: 'URL' object has no attribute 'item_nr '.

items_nr will be a SerializerMethodField not IntegerField
The field will be automatically assigned data type based on what you return in get_item_nr.
class WebsiteSerializer(serializers.Serializer):
item_nr = serializers.SerializerMethodField()
class Meta:
model = URL
fields = (
"id",
"item",
"status",
"item_nr "
)
def get_item_nr (self, obj):
obj.item_nr = Items.objects.filter(item_id=self.context.get(id)).count()
return obj.item_nr

Related

Id instead of String when displaying foreign key field in DRF

I'm trying to return the name of the pricing field but all I get is its foreign key id instead. What am I doing wrong here? I looked at some similiar issues on here but I didn't find anything that resembled my situation.
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = (
"assignedteams",
"agent",
"facility",
"organisor",
"avatar",
)
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
class UserSerializer(UserDetailsSerializer):
profile = UserProfileSerializer(source="userprofile")
subscription = UserSubscriptionSerializer(source="usersubscription")
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('profile', 'subscription',)
def update(self, instance, validated_data):
userprofile_serializer = self.fields['profile']
userprofile_instance = instance.userprofile
userprofile_data = validated_data.pop('userprofile', {})
usersubscription_serializer = self.fields['subscription']
usersubscription_instance = instance.usersubscription
usersubscription_data = validated_data.pop('usersubscription', {})
# update the userprofile fields
userprofile_serializer.update(userprofile_instance, userprofile_data)
usersubscription_serializer.update(usersubscription_instance, usersubscription_data)
instance = super().update(instance, validated_data)
return instance
You have 2 options to solve this problem.
option1:
If you want to return only the name of your pricing model you can use SlugRelatedField to do it.
Example:
class UserSubscriptionSerializer(serializers.ModelSerializer):
pricing = serializers.SlugRelatedField('name', readonly=True)
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
Option2:
If you want to return the Pricing object you can create a new ModelSerializer for your Pricing model and use it.
Example:
class PricingSerializer(serializers.ModelSerializer):
class Meta:
model = Pricing
fields = ["id","name"]
class UserSubscriptionSerializer(serializers.ModelSerializer):
pricing = PricingSerializer(readonly=True)
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
There are some other options that can you use but you must explain more about your problem can I will help you with.
you can easily add a new field representation or override the pricing field when want to represent data
so in your serializer add the following code
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
def to_representation(self, instance):
data = super().to_representation(instance)
data['pricing_name'] = instance.pricing.name # or replace the name with your pricing name field
return data
As you are saying pricing returned FK id, so i assume pricing column inside Subscription model is a FK to another model, let's assume it Pricing model.
You can create a serializer for Pricing and use it on UserSubscriptionSerializer,
like the way you created UserProfileSerializer and UserSubscriptionSerializer for UserSerializer
But, using directly a nested serializer will give you problem while doing write operation since as far i can understand you are accepting pricing as FK value when creating or updating
To solve this issue you can do some if/else on get_fields() method
class UserSubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = (
"user",
"pricing",
"status",
)
def get_fields(self):
fields = super().get_fields()
# make sure request is passed through context
if self.context['request'] and self.context['request'].method == 'GET':
fields['pricing']=PricingSerializer()
return fields
Now coming back to the question, since you only need the pricing name which i assume name is a column on Pricing model
simply rewrite the previous code as
def get_fields(self):
fields = super().get_fields()
# make sure request is passed through context
if self.context['request'] and self.context['request'].method == 'GET':
fields['pricing'] = serializers.CharField(source='pricing.name', read_only=True)
return fields
P.S: I haven't tested this code on my computer

How to create 2 objects from separate models with a single serializer and also retrieve them from the database with a single serializer in Django RF?

I have 3 models: Maker, Item and MakerItem that creates the relation between the items and their makers:
class Maker(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=100)
class Item(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=100)
class MakerItem(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
item_id = models.ForeignKey(Item, on_delete=models.CASCADE)
maker_id = models.ForeignKey(Maker, on_delete=models.CASCADE)
the items can have a random amount of makers.
I want to create both the Item and the MakerItem objects at the same time with a single set of data,
for example if a Maker with id = "abcd" already exists, and I go to /item and send a POST request with the following data:
{
"name": "item1",
"makers": [
{
"maker_id": "abcd"
}
]
}
I want the serializer to create the Item object and the MakerItem object.
I have achieved this, with the following setup:
views.py
class ItemListCreate(ListCreateAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class MakerItemSerializer(serializers.ModelSerializer):
class Meta:
model = MakerItem
exclude = ['id', 'item_id']
makers = MakerItemSerializer(many=True)
class Meta:
model = Item
fields = ['id', 'name', 'makers']
def create(self, validated_data):
maker_item_data = validated_data.pop('makers')
item_instance = Item.objects.create(**validated_data)
for each in maker_item_data:
MakerItem.objects.create(
item_id=check_instance,
maker_id=each['maker_id']
)
return item_instance
but when Django tries to return the created object, it always gives me the error:
AttributeError at /item/
Got AttributeError when attempting to get a value for field `makers` on serializer `ItemSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Item` instance.
Original exception text was: 'Item' object has no attribute 'makers'.
What am I doing wrong?
Thanks
EDIT: To clarify, the objects get created and populate the database correctly, but when the browsable API that DRF provides tries to display the created object, it gives me the error above.
Change:
class ItemSerializer(serializers.ModelSerializer):
class MakerItemSerializer(serializers.ModelSerializer):
class Meta:
model = MakerItem
exclude = ['id', 'item_id']
makers = MakerItemSerializer(many=True)
To:
class ItemSerializer(serializers.ModelSerializer):
class MakerItemSerializer(serializers.ModelSerializer):
class Meta:
model = MakerItem
exclude = ['id', 'item_id']
makers = MakerItemSerializer(many=True, source="makeritem_set")
Hope this works!
For clarity, you're attempting to serialise the reverse relationship between MakerItem and Item for this serialiser.
This means that the attribute on your object is automatically set by Django as fieldname_set but you can override this behaviour by setting the related_name kwarg on the field and then makemigrations and migrate it.
In your case you would need to do:
maker_id = models.ForeignKey(Maker, on_delete=models.CASCADE, related_name="maker_items")
And then update the field in the Meta to match the new field name, this way you don't have to manually specify source. Because actually the attribute "makers" is misleading, due to the fact its actually the MakerItem, not the Maker itself.
See https://docs.djangoproject.com/en/3.2/ref/models/relations/ for further details about this behaviour.

Django Rest Framework: How to modify output structure?

Is there a way to group fields in Serializer/ModelSerializer or to modify JSON structure?
There is a Location model:
class Location(Model):
name_en = ...
name_fr = ...
...
If I use ModelSerializer I get plain representation of the object fields like:
{'name_en':'England','name_fr':'Angleterre'}
I want to group some fields under "names" key so I get
{'names':{'name_en':'England','name_fr':'Angleterre'}}
I know I can create custom fields but I want to know if there is a more straightforward way. I tried
Meta.fields = {'names':['name_en','name_fr']...}
which doesn't work
I think it is better using a property. Here is the whole example.
class Location(models.Model):
name_en = models.CharField(max_length=50)
name_fr = models.CharField(max_length=50)
#property
def names(self):
lst = {field.name: getattr(self, field.name)
for field in self.__class__._meta.fields
if field.name.startswith('name_')}
return lst
In views:
class LocationViewSet(viewsets.ModelViewSet):
model = models.Location
serializer_class = serializers.LocationSerializer
queryset = models.Location.objects.all()
And in serializers:
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = ('id', 'names')
My result for my fake data:
[{
"id": 1,
"names": {
"name_en": "England",
"name_fr": "Angleterre"}
}]
Try to create a wrapper serialzer and place the LocationSerializer inside it
class LocationSerialzer(serializers.ModelSerialzer):
name_en = ...
name_fr = ...
...
class MySerializer(serializers.ModelSerializer):
name = LocationSerialzer()
...
Using the above method , you can apply your own customization without being limited to drf custom fields.
You could also not use a property on the model and but use a SerializerMethodField on your serializer like in this implementation.
We used here a _meta.fields, like in the other implementation, to get all the fields that starts with name_ so we can dynamically get the output you desired
class LocationSerializer(serializers.ModelSerializer):
names = serializers.SerializerMethodField()
def get_names(self, obj):
lst = {field.name: getattr(obj, field.name)
for field in obj.__class__._meta.fields
if field.name.startswith('name_')}
return lst
class Meta:
model = Location
fields = ('id', 'names')

DRF 3 - Creating Many-to-Many update/create serializer with though table

I am trying to create a reference app in DRF 3 to demonstrate a nested serializer that can create/update models. The sample code below bombs with "*create() argument after ** must be a mapping, not list*" when trying to create the nested models. It is also no clear to me how I'd handle the .update() as in some cases I just want to be establish additional relationships (Persons).
The sample models:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
persons = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
And the serializers and viewsets:
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from app.models import Group, Person
class PersonSerializer(ModelSerializer):
class Meta:
model = Person
class GroupSerializer(ModelSerializer):
persons = PersonSerializer(many=True)
def create(self, validated_data):
persons = validated_data.pop('persons')
group = Group.objects.create(**validated_data)
if persons: # Bombs without this check
Person.objects.create(group=group, **persons) # Errors here
return group
class Meta:
model = Group
class PersonModelViewSet(ModelViewSet):
serializer_class = PersonSerializer
queryset = Person.objects.all()
class GroupModelViewSet(ModelViewSet):
serializer_class = GroupSerializer
queryset = Group.objects.all()
I am trying to POST some JSON that inserts a Group with two (related) Persons:
{
"persons": [
{ "name" : "name 1" },
{ "name" : "name 2" }
],
"name": "group name 1"
}
I have no clue if there is an easier way, but the only way I managed to get this to work is to reference the 'through' model "memberships" in the Group serializer and write custom code for .create() and .update(). This seems like a lot of work to just set M2M FK's. If someone knows a better way I'd love to hear it.
class GroupMembershipSerializer(ModelSerializer):
class Meta:
model = Membership
fields = ('person',)
class GroupCreateSerializer(ModelSerializer):
memberships = GroupMembershipSerializer(many=True, required=False)
def create(self, validated_data):
person_data = validated_data.pop('memberships')
group = Group.objects.create(**validated_data)
for person in person_data:
d=dict(person)
Membership.objects.create(group=group, person=d['person'])
return group
def update(self, instance, validated_data):
person_data = validated_data.pop('memberships')
for item in validated_data:
if Group._meta.get_field(item):
setattr(instance, item, validated_data[item])
Membership.objects.filter(group=instance).delete()
for person in person_data:
d=dict(person)
Membership.objects.create(group=instance, person=d['person'])
instance.save()
return instance
class Meta:
model = Group
class GroupCreateModelViewSet(ModelViewSet):
serializer_class = GroupCreateSerializer
queryset = Group.objects.all()
So you can create a new Group with related Person(s) using:
{
"name" : "Group 1",
"memberships" : [
{ "person" : 1 },
{ "person" : 2 }
]
}
Use PrimaryKeyRelatedField shown here:
http://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
class GroupSerializer(serializers.ModelSerializer):
persons = serializers.PrimaryKeyRelatedField(
many=True, queryset=Person.objects.all())
class Meta:
model = Group
fields = ('name', 'persons')
Create each person first, for example. Person with ID 1, Name = "Bob". Person with ID 2, Name = "Tim". Then post them to the REST Endpoint using their primary keys So:
# Group create() REST endpoint data to POST
{'name': 'my group', 'persons': [1, 2]}
Now the people that you had created prior, are part of that Group.

How to exclude parent when serializer is nested when using Django REST Framework?

I feel like this is probably in the docs but I just can't seem to figure it out.
If I've got a serializer with a ForeignKey included in its fields how do I exclude that FK when that serializer is nested in the related object?
class EmployerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Employer
fields = ('name', 'person')
class PersonSerializer(serializers.HyperlinkedModelSerializer):
employers = EmployerSerializer()
class Meta:
model = Person
fields = ('name', 'employers')
depth = 1
When I hit the API at http://0.0.0.0:8000/person/1/ it lists something like:
{
"name": "Joe Blow",
"employers": {
"name": "Acme Inc."
"person": "http://0.0.0.0:8000/person/1/"
}
}
The "person" key for "employers" is self-referential and redundant, but only when the serializer is nested within the object it's referring to.
It seems like there should be an option to exclude it when the serializer is nested, but I can't figure it out.
class DynamicFieldsModelSerializer(serializers.HyperlinkedModelSerializer):
"""
A HyperlinkedModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
exclude = kwargs.pop('exclude', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
if exclude:
# Drop fields that are specified in the `exclude` argument.
excluded = set(exclude)
for field_name in excluded:
try:
self.fields.pop(field_name)
except KeyError:
pass
extend EmployerSerializer with DynamicFieldsModelSerializer
class EmployerSerializer(DynamicFieldsModelSerializer):
class Meta:
model = Employer
fields = ('name', 'person')
class PersonSerializer(serializers.HyperlinkedModelSerializer):
employers = EmployerSerializer(exclude=('name',))
class Meta:
model = Person
fields = ('name', 'employers')
depth = 1

Categories