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
Related
Lets say I have the following models:
class Author(models.Model):
first_name = models.CharField(max_length=32)
last_name = models.CharField(max_length=32)
class Book(models.Model):
title = models.CharField(max_length=64)
author = models.ForeignKeyField(Author, on_delete=models.CASCADE)
And I have the following serializer:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'title', 'author')
read_only_fields = ('id')
If I then query my books, A book's data looks like:
{
"id": 1,
"title": "Book Title",
"author": 4
}
Which is what I want, as I return both an array of books, as well as an array of authors, and allow the client to join everything up. This is because I have many authors that are repeated across books.
However, I want to allow the client to either submit an existing author id to create a new book, or all of the data for a new author. E.g.:
Payload for new book with existing author:
{
"title": "New Book!",
"author": 7
}
or, payload for a new book with a new author:
{
"title": "New Book!",
"author": {
"first_name": "New",
"last_name": "Author"
}
}
However the second version, will not pass the data validation step in my serializer. Is there a way to override the validation step, to allow either an author id, or a full object? Then in my serializer's create method, I can check the type, and either create a new author, get its id, and create the new book, or just attach the existing id. Thoughts?
I believe that it is not possible to do it in the way you want ( using one field author).
It just because one serializer cannot handle two different types for one field.
Note: i might be wrong about the previous statement.
However, the following is a potential solution for you. You just need to use different field name to create new author.
class BookSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(
required=False,
queryset=Author.objects.all(),
)
author_add = AuthorSerializer(write_only=True, required=False)
class Meta:
model = Book
fields = ('id', 'title', 'author', 'author_add')
read_only_fields = ('id')
def create(self, validated_data):
author_add_data = validated_data.pop('author_add', None)
if author_add is not None:
validated_data['author'] = Author.objects.create(**author_add_data)
return super().create(validated_data)
Note: you need to handle a case where you send both author and author_add. Probably add a check into validation step and raise ValidationError if both are provided.
Offtopic hint: you dont need to explicityl state read_only_fields = ('id',) - primary keys are read-only.
For anyone else trying to do this, here is what I ended up getting working.
For my book serializer I did the following:
class BookSerializer(serializers.ModelSerializer):
# make author a foreign key/id, read-only field so that it isn't
# processed by the validator, and on read returns just the id.
class Meta:
model = Book
fields = ('id', 'title', 'author')
read_only_fields = ('id', 'author',)
# override run_validation to validate our author
def run_validation(self, data):
# run base validation. Since `author` is read_only, it will
# be ignored.
value = super(Book, self).run_validation(data)
# inject our validated author into the validated data
value['author'] = self.validate_author(data['author'])
return value
# Custom author validation
def validate_author(self, author):
errors = OrderedDict()
if isinstance(author, int): # if just a key, retrieve the author
try:
author_instance = Author.objects.get(pk=author)
except Author.DoesNotExist:
errors['author'] = "Author with pk {} does not exist.".format(author)
raise ValidationError(errors)
else: # if passed an author object...
author_serializer = AuthorSerializer(data=author, many=False)
author_serializer.is_valid(raise_exception=True)
author_instance = author_serializer.save()
return author_instance
I need to do a bit more error checking (e.g.- no author passed), but it works quite well- the consumer of the API can submit either an author id, or a serialized author object to create a new author. And the API itself returns just an id as was needed.
I've been bugging on this issue for some time now. I have two models : Acquisitions and RawDatas.
Each RawData have one Acquisition, but many RawDatas can have the same Acquisition.
I want to create or get the instance of Acquisition automatically when I create my RawDatas. And I want to be able to have all informations using the serializer.
class Acquisitions(models.Model):
class Meta:
unique_together = (('implant', 'beg_acq', 'duration_acq'),)
id = models.AutoField(primary_key=True)
implant = models.ForeignKey("Patients", on_delete=models.CASCADE)
beg_acq = models.DateTimeField("Beggining date of the acquisition")
duration_acq = models.DurationField("Duration of the acquisition")
class RawDatas(models.Model):
class Meta:
unique_together = (('acq', 'data_type'),)
id = models.AutoField(primary_key=True)
acq = models.ForeignKey("Acquisitions", on_delete=models.CASCADE)
data_type = models.CharField(max_length=3)
sampling_freq = models.PositiveIntegerField("Sampling frequency")
bin_file = models.FileField(db_index=True, upload_to='media')
And my serializers are these :
class AcquisitionSerializer(serializers.ModelSerializer):
class Meta:
model = Acquisitions
fields = ('id', 'implant', 'beg_acq', 'duration_acq')
class RawDatasSerializer(serializers.ModelSerializer):
acq = AcquisitionSerializer()
class Meta:
model = RawDatas
fields = ('id', 'data_type', 'sampling_freq', 'bin_file', 'acq')
def create(self, validated_data):
acq_data = validated_data.pop('acq')
acq = Acquisitions.objects.get_or_create(**acq_data)
RawDatas.objects.create(acq=acq[0], **validated_data)
return rawdatas
My problem is that, using this, if my instance of Acquisitions already exists, I get a non_field_errors or another constraint validation error.
I would like to know what is the correct way to handle this please ?
So I can automatically create this using the nested serializer, and when I only want to have informations (such as a GET request), I can have all the field I need (every field of the two models).
Thanks in advance for your help !
Try this:
class AcquisitionSerializer(serializers.ModelSerializer):
class Meta:
model = Acquisitions
fields = ('id', 'implant', 'beg_acq', 'duration_acq')
class RawDatasSerializer(serializers.ModelSerializer):
class Meta:
model = RawDatas
fields = ('id', 'data_type', 'sampling_freq', 'bin_file', 'acq')
def create(self, validated_data):
acq_data = validated_data.pop('acq')
acq = Acquisitions.objects.filter(id=acq_data.get('id')).first()
if not acq:
acq = AcquisitionSerializer.create(AcquisitionSerializer(), **acq_data)
rawdata = RawDatas.objects.create(acq=acq, **validated_data)
return rawdata
I have a model which contains sensitive data, let's say a social security number, I would like to transform that data on serialization to display only the last four digits.
I have the full social security number stored: 123-45-6789.
I want my serializer output to contain: ***-**-6789
My model:
class Employee (models.Model):
name = models.CharField(max_length=64,null=True,blank=True)
ssn = models.CharField(max_length=16,null=True,blank=True)
My serializer:
class EmployeeSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
class Meta:
model = Employee
fields = ('id','ssn')
read_only_fields = ['id']
You can use SerializerMethodField:
class EmployeeSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
ssn = SerializerMethodField()
class Meta:
model = Employee
fields = ('id','ssn')
read_only_fields = ['id']
def get_ssn(self, obj):
return '***-**-{}'.format(obj.ssn.split('-')[-1]
If you don't need to update the ssn, just shadow the field with a SerializerMethodField and define get_ssn(self, obj) on the serializer.
Otherwise, the most straightforward way is probably to just override .to_representation():
def to_representation(self, obj):
data = super(EmployeeSerializer, self).to_representation(obj)
data['ssn'] = self.mask_ssn(data['ssn'])
return data
Please add special case handling ('ssn' in data) as necessary.
Elaborating on #dhke’s answer, if you want to be able to reuse this logic to modify serialization across multiple serializers, you can write your own field and use that as a field in your serializer, such as:
from rest_framework import serializers
from rest_framework.fields import CharField
from utils import mask_ssn
class SsnField(CharField):
def to_representation(self, obj):
val = super().to_representation(obj)
return mask_ssn(val) if val else val
class EmployeeSerializer(serializers.ModelSerializer):
ssn = SsnField()
class Meta:
model = Employee
fields = ('id', 'ssn')
read_only_fields = ['id']
You can also extend other fields like rest_framework.fields.ImageField to customize how image URLs are serialized (which can be nice if you’re using an image CDN on top of your images that lets you apply transformations to the images).
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.
I have models:
class Emp(models.Model):
full_name = models.CharField(max_length=100)
mobile = models.CharField(max_length=10,blank=True,null=True)
email = models.EmailField(blank=True,null=True)
class Enquiry(models.Model):
date = models.DateField(default=timezone.now)
number = models.AutoField(primary_key=True)
products = models.ManyToManyField(Product, related_name="products")
referred_by_emp = models.ForeignKey(
Emp,related_name='ref_emp',
null=True,blank=True,
)
The serializer classes:
class ReferrerSerializer(serializers.ModelSerializer):
class Meta:
model = Emp
fields = (
'full_name','mobile','email',
)
class EnquirySerializer(serializers.ModelSerializer):
class Meta:
model = Enquiry
fields = (
'name','mobile','email',
'products',
'referred_by_emp',
)
I wish to get the attributes of Emp i.e full_name, mobile, email in the form while entering the Enquiry details. The views for the 2 models:
class RefViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Emp instances to be viewed or edited.
"""
model = Emp
queryset = Emp.objects.all()
serializer_class = ReferrerSerializer
class EnquiryViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows Enquiry instances to be viewed or edited.
"""
model = Enquiry
queryset = Enquiry.objects.all()
serializer_class = EnquirySerializer
While entering the Enquiry details in the django REST api, I wish to capture the Emp details also and submit the form. And the details should be captured in the respective models. How can this be achieved? I am new to django's REST Api and didn't find a proper way of doing it. Plz guide me with detailed code to achieve this. I tried itertools.chain, but perhaps didn't use it correctly Further I would like to invoke the curl command for the same.Thanx in advance
Using django 1.6.5
Ok this is not something i recommend doing, but I've have been there when it just has to happen.
class ReferrerSerializer(serializers.ModelSerializer):
class Meta:
model = Emp
fields = ('full_name','mobile','email')
class EnquirySerializer(serializers.ModelSerializer):
class Meta:
model = Enquiry
fields = ('name','mobile','email','products',)
# As the fields doesn't already exist you can 'copy' them into the serializer
EnquirySerializer.base_fields["full_name"] = ReferrerSerializer().fields["full_name"]
# Then in the view we can create another model instance.
# you should use the Serializer or a Form class and not save the data directly to a Emp instance here but i left that out for clarity.
class SomeViewSet(ModelViewSet):
model = Enquiry
# post() should only handle new/create calls, but if you want it more clear you can override create() instead.
def post(self, request, *args, **kwargs):
self.emp_data = {}
self.emp_data["full_name"] = self.request.DATA.pop("full_name")
return super(SomeViewSet, self).post(request, *args, **kwargs)
def pre_save(self, obj):
emp = Emp.objects.create(**self.emp_data)
obj.referred_by_emp = emp
return obj