How do I serialize specific field in Django ManyToMany field? - python

So, I'm trying to use Django Rest Framework for my project. I have two models Category and Content to be serialized, as below:
views.py
class Category(models.Model):
category_name = models.CharField(max_length = 20)
def __str__(self):
return self.category
class Content(models.Model):
category = models.ManyToManyField(Category)
body = models.TextField(blank=True, null=True)
created_at = models.DateField(auto_now_add=True, null=True)
def __str__(self):
return self.created_at
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['category_name']
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = [
'category', 'body', 'created_at',
]
Now the problem is, the JSON object returned has the id of each object in Category, NOT the category_name. So when I console.log in my front-end, I get (for example) [1, 4, 8] but what I need is (for example) ['Wine', 'Beer', 'Whiskey'].
How do I make that possible? Thanks in advance :)

You need to check out StringRelatedField I think.
https://www.django-rest-framework.org/api-guide/relations/#stringrelatedfield
You can serialize related fields as defined in __str__ method in your model.
And also you can serialize your every item in that field like:
{ id: 1, name: 'category name' }
You need to check nested serializers for done that:
https://www.django-rest-framework.org/api-guide/relations/#nested-relationships
For example:
You need something like nested serializer, you can check it from there:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ("id", "name", ) # You can specify whatever fields you want here.
class ContentSerializer(serializers.ModelSerializer):
category = CategorySerializer() # many=True if you want
class Meta:
model = Content
fields = ("id", "category", "body", )
I think what you want is StringRelatedField, return field that yo. The second one, nested relationships was extra

You can make a read only field in serializer and set the source as follows:
class ContentSerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source='category.category_name',
read_only=True)
class Meta:
model = Content
fields = [
'category', 'body', 'created_at',
]
You can then bind category_name from JSON response with your frontend.

Related

Unable to POST JSON data from multiple select element with Django REST Framework

I would like to be able to send an AJAX POST request to my API endpoint to create a new instance of my Asset model with multiple Category instances referenced in my Asset model, hence the many-to-many field type in my Asset model.
I'm able to successfully POST and create new Asset instances, however my category field won't accept any data at all. The category field remains empty when a new Asset instance is created. I think it has something to do with my CategorySerializer. I'm still learning how to use Django REST Framework so I'd appreciate if I could get some help figuring out how to work with serializers in Django REST Framework.
I've already tried modifying the AssetSerializer create method to handle parsing the JSON and validating the data but that hasn't worked. I've also tried other solutions suggested in other posts I've found on StackOverflow but haven't found anything that works for my situation.
Here's my serializers.py file:
class CategorySerializer(serializers.ModelSerializer):
name = serializers.CharField(required=False, read_only=True)
class Meta:
model = Category
fields = ('id', 'name')
class AssetSerializer(serializers.ModelSerializer):
name = serializers.CharField(allow_null=True)
description = serializers.CharField(allow_null=True)
manufacturer = serializers.CharField(allow_null=True)
uid = serializers.UUIDField(read_only=True, allow_null=True)
borrower = BorrowerSerializer(allow_null=True, read_only=True)
condition = serializers.ChoiceField(choices=Asset.CONDITION_TYPE, default='g', allow_null=True)
owner = serializers.ReadOnlyField(source='owner.username')
return_date = serializers.DateField(allow_null=True)
checked_out = serializers.BooleanField(allow_null=True)
category = CategorySerializer(required=False, many=True)
class Meta:
model = Asset
fields = ('uid',
'name',
'manufacturer',
'model',
'description',
'owner',
'condition',
'category',
'borrower',
'checked_out',
'return_date',
'is_dueback',
)
def update(self, instance, validated_data):
instance.borrower = validated_data.get('borrower', instance.borrower)
instance.return_date = validated_data.get('return_date', instance.return_date)
instance.checked_out = validated_data.get('checked_out', instance.checked_out)
instance.name = validated_data.get('name', instance.name)
instance.manufacturer = validated_data.get('manufacturer', instance.manufacturer)
instance.model = validated_data.get('model', instance.model)
instance.description = validated_data.get('description', instance.description)
instance.condition = validated_data.get('condition', instance.condition)
instance.category = validated_data.get('category', instance.category)
instance.save()
return instance
def create(self, validated_data):
return Asset.objects.create(**validated_data)
Here's my Asset model:
class Asset(models.Model):
"""Model representing an Asset"""
uid = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=200)
manufacturer = models.CharField(max_length=64)
model = models.CharField(max_length=128)
description = models.TextField()
category = models.ManyToManyField(Category)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
borrower = models.ForeignKey(Borrower, on_delete=models.CASCADE, null=True, blank=True)
checked_out = models.BooleanField(default=False)
return_date = models.DateField(null=True, blank=True)
CONDITION_TYPE = (
('e', 'Excellent'),
('g', 'Good'),
('f', 'Fair'),
('p', 'Poor'),
)
condition = models.CharField(
max_length=1,
choices=CONDITION_TYPE,
blank=True,
help_text='Asset condition')
class Meta:
ordering = ['return_date']
#property
def is_dueback(self):
if self.return_date and date.today() > self.return_date:
return True
return False
def display_category(self):
"""Create a string for the Category. This is required to display category in Admin."""
return ', '.join(category.name for category in self.category.all())
display_category.short_description = 'Category'
def __str__(self):
return f'{self.uid} - {self.name}'
def get_absolute_url(self):
return reverse('asset-detail', args=[str(self.uid)])
Here's my Category model:
class Category(models.Model):
"""Model representing an Asset category"""
name = models.CharField(max_length=128)
def __str__(self):
return self.name
I'd appreciate any help you could provide. Thank you in advance.
i'm almost new in DRF but i try to help. why you writing all the field in serializer when you using ModelsSerializer? not need to telling ModelSerializer what type of field should be because you are pointing to model in class Meta and DRF know about fields and type and etc . second about allow_null=True in serializer, when Model haven't null=True you can't except DRF can create a not null-able field for instance with null=True so if you wnt a field can be null just add null=True in Model class . for your problem about ManytoMantry field try to use Primary key relation for ManyToMany fields in your serializers then pass id of Category instances in list:
class AssetSerializer(serializers.ModelSerializer):
borrower = BorrowerSerializer(allow_null=True, read_only=True)
category = serializers.PrimaryKeyRelatedField(many=True, queryset=Category.objects.all())
class Meta:
model = Asset
fields = ('uid',
'name',
'manufacturer',
'model',
'description',
'owner',
'condition',
'category',
'borrower',
'checked_out',
'return_date',
'is_dueback',
)
read_only_fields = ( 'uid' , ) # this fields will be read_only
depending on how you using this serializer in your view for save and update have difference way. if your view is generics class so will do create and update itself by POST and PUT method .and for other class view that isn't belong to generics DRF view you can using serializer.save() to create a new instance.wish help you.
pass data something like:
{
"name" : "foo",
"manufacture" : "foo",
.
.
.
"category" : [1,2,3,24,65]
}

Type error to create and update my list in django rest framework

I'm trying to use my api to create and update products in a bundle. I did so:
model.py
class Business(models.Model):
name = models.CharField(max_length=155)
class Product(models.Model):
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=200)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
class Meta:
verbose_name = "Product"
class Bundle(models.Model):
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
products = models.ManyToManyField(Product, related_name="bundles",blank=True, null=True, through="BundleProduct")
class Meta:
verbose_name = "Bundle"
def __str__(self):
return self.name
class BundleProduct(models.Model):
bundle = models.ForeignKey(Bundle, on_delete=models.CASCADE, related_name="bundleproducts")
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="bundleproducts")
number = models.IntegerField(default=1)
class Meta:
verbose_name = "Bundle of Product"
def __str__(self):
return str(self.product.name) + " do " + self.bundle.name
def get_absolute_url(self):
return reverse("BundleProduct_detail", kwargs={"pk": self.pk})
And here is my serializers.py:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = "__all__"
class BundleProductSerializer(serializers.ModelSerializer):
class Meta:
model = BundleProduct
fields = "__all__"
class BundleSerializer(serializers.ModelSerializer):
class Meta:
model = Bundle
fields = "__all__"
My viewset.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
model = Product
class BundleProductViewSet(viewsets.ModelViewSet):
queryset = BundleProduct.objects.all()
serializer_class = BundleProductSerializer
model = BundleProduct
class BundleViewSet(viewsets.ModelViewSet):
queryset = Bundle.objects.all()
serializer_class = BundleSerializer
model = Bundle
When I try to post some products in bundleproducts I receive "Incorrect type. Expected pk value, received list."
Reading about this error, I found some issues relating to PrimaryKeyRelatedField and SlugRelatedField. I know I need to override but I have no idea how to do it.
It's an example of how to post would works:
{
"number": 1,
"bundle": 2,
"product":
[
1,
2
]
}
After watching the video commented by Neil, I created the following method:
class BundleSerializer(
serializers.ModelSerializer
):
products = ProductSerializer(many=True)
def create(self, validated_data):
products = validated_data.pop('products')
bundle = BundleProduct.objects.create(**validated_data)
for product in products:
BundleProduct.objects.create(**product, bundle=bundle)
return Bundle
class Meta:
model = Bundle
fields = "__all__"
But doesn't work. I receive this error: "TypeError at /api/v1/bundle/
'name' is an invalid keyword argument for this function"
If you are making post via BundleSerializer you need to pass products with list of ProductSerializer data not just id since products in BundleSerializer is accepting productsSerializer data. You are getting type error 'name' is an invalid keyword argument for this function" because your validated_data contain name and BundleProduct object Does not have name field.And you are creating BundleProduct objects with validated_data.
Create bundle object and pass id of bundle object to BundleProduct object.
If you do not want to create product and just pass existing product id you need to make ListField
You need to Override get_fields and check the requests
override to_representation to return always List of ProdutSerializer Data
Override create for POST request
Override update for PUT and PATCH Request
Below is solution for POST Request
For PATCH AND PUT Request you need to override update method of ModelSerializer and handle the products accordingly.
class BundleSerializer(serializers.ModelSerializer):
def create(self, validated_data):
products = validated_data.pop('products')
bundle = Bundle.objects.create(**validated_data)
for product_id in products:
product = get_object_or_404(Product, pk=product_id)
BundleProduct.objects.create(product=product, bundle=bundle)
return bundle
class Meta:
model = Bundle
fields = "__all__"
def to_representation(self, instance):
repr = super().to_representation(instance)
repr['products'] = ProductSerializer(instance.products.all(), many=True).data
return repr
def get_fields(self):
fields = super().get_fields()
if self.context['request'].method in ['POST', "PATCH","PUT"]:
fields['products'] = serializers.ListField(
write_only=True,
child=serializers.IntegerField()
)
return fields
sample POST data to BundleSerializer
{
"products":[1,2],
"name":"Offer One",
"description":"description",
"price":1212,
"business":1
}
In my experience, if you want to update a model and a related model in one request, with DRF, the easiest way to do this is to override the "create" method of a serializer. There's a good video on this here which I used as my reference: https://www.youtube.com/watch?v=EyMFf9O6E60
The issue here is that you are posting a list to BundleProduct's product field yet it is an ForeignKey. To join Bundle to a Product, simply POST:
{
"bundle": 2,
"product" 1,
"number": 1
}
You can repeat this:
{
"bundle": 2,
"product" 4,
"number": 1
}
to add yet another product 4 to the same bundle and so on. Just make sure you do them one by one and not in a list as you had done earlier.

Django rest framework serializer with reverse relation

I have two models where employee have relation with person model but person have no relation with employee model.
Like:
class Person(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
class Employee(models.Model):
person = models.ForeignKey(Person, related_name='person_info')
code = models.CharField()
In such cases I want code field data in person serializer.
I can solved this with writing method in person model or using SerializerMethodField in person serializer
like this:
def get_employee_code(self):
return Employee.objects.get(person=self).id
and add this as source in person serializer
employee_code = serializers.CharField(source='get_employee_code')
Or adding employee serializer into person serialiszer
class PersonSerializer(serializers.ModelSerializer):
employee = EmployeeSerializer()
class Meta:
model = Person
fields = ('name', 'address', 'employee')
But i was trying to do this with reverse relation but i can't. I have tried like this, it gives an error
Serializer:
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.CharField(source='person_info.code')
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')
How can i solve this with reverse relation?
At the moment because you are using a ForeignKey field on the person attribute, it means that its returning a list when you access the reverse relation.
One solution would be to use a slug related field, though this must have many and read_only set to True, and will return a list because of the ForeignKey field.
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.SlugRelatedField(
source='person_info',
slug_field='code',
many=True,
read_only=True,
)
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')
The other option is to change your ForeignKey into a OneToOneField, which would still need read_only set to True but it will not return a list.
class Person(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
class Employee(models.Model):
person = models.OneToOneField(Person, related_name='person_info')
code = models.CharField()
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.SlugRelatedField(
source='person_info',
slug_field='code',
read_only=True,
)
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')
Or, if you don't want to change the ForeignKey, you could add a employee_code property method to the model instead to return the first employee code in the person_info relation.
class Person(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
#property
def employee_code(self):
employees = self.person_info.filter()
if employees.exists():
return employees.first().code
return ''
class Employee(models.Model):
person = models.OneToOneField(Person, related_name='person_info')
code = models.CharField()
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.CharField(
read_only=True,
)
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')
you can access the reverse relation with custom SerializerMethodField()
class PersonSerializer(serializers.ModelSerializer):
employee_code = serializers.SerializerMethodField()
def get_employee_code(self, obj):
return obj.person_info.code
class Meta:
model = Person
fields = ('name', 'address', 'employee_code')

Django many-to-many serialization

I want to create a model (Source) with many-to-many relation to the another model (Tag) and create a Source objects without duplicating Tag instance in database.
Here is my models:
class Tag(models.Model):
name = models.CharField(max_length=50, null=False, default='source')
def __unicode__(self):
return self.name
class Source(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=200)
language = models.CharField(max_length=50)
color = models.CharField(max_length=50, default='white')
isFile = models.BooleanField(default=False)
link = models.TextField(default='')
file = models.FileField(upload_to='uploads/', null=True)
tags = models.ManyToManyField('Tag')
class Meta:
ordering = ('title',)
Here is my serializers:
class TagSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = Tag
fields = ('name',)
class SourceSerializers(serializers.ModelSerializer):
tags = TagSerializers(many=True)
class Meta:
model = Source
fields = ('title', 'author', 'language', 'color', 'isFile', 'link', 'file', 'tags')
def create(self, validated_data):
tags_data = validated_data.pop('tags')
source = Source.objects.create(**validated_data)
for tag in tags_data:
t = Tag.objects.create()
t.name = tag.get("name")
t.save()
source.tags.add(t)
source.save()
return source
But when I try to create Source object via http request - the object is created, but without any references to Tags. After some researching I found that validated_data in create(self, validated_data) doesn't contains "tags" field, also I found that validate function of TagSerializer not invoked at any time. What I'm doing wrong?
Use get_or_create method to create Tag object.
def create(self, validated_data):
tags_data = validated_data.pop('tags')
source = Source.objects.create(**validated_data)
for tag in tags_data:
name = tag.get("name")
t = Tag.objects.get_or_create(name=name)
t.save()
source.tags.add(t)
source.save()
return source
Seems the problem was in my requests, without many-to-many relation we can use form-data and all is good, but when we add mant-to-many relation we can't use form-data anymore and have to use only application\json

Django REST Framework relationship serialization

I've been banging my head against this issue and know I have to be missing something simple.
I'm trying to learn Django REST Framework and having issues setting the foreign keys of a new object to existing other objects when POSTing JSON to the server.
models.py
class Genre(models.Model):
name = models.CharField(max_length=200)
class Author(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
def full_name(self):
return self.first_name + ' ' + self.last_name
class Book(models.Model):
title = models.CharField(max_length=200)
genre = models.ForeignKey(Genre)
isbn = models.CharField(max_length=15, default='')
summary = models.CharField(max_length=500, null=True)
author = models.ForeignKey(Author)
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'first_name', 'last_name',)
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = ('id', 'name',)
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
genre = GenreSerializer(read_only=True)
class Meta:
model = Book
fields = ('id','url', 'author', 'genre', 'title', 'isbn', 'summary',)
What I'm trying to is create a new book related to an existing Author and Genre. So, given some JSON like
{"title": "Title",
"author": {"id":1}
"genre" : {"id":2}
...
}
I want to create a new book and have its Genre and Author set to the appropriate entities that are already in the database.
I've tried to change the author and genre fields on BookSerializer to serializers.PrimaryKeyRelatedField() and scoured the docs and SO for answers, including this one. I've tried to change the fields in the JSON to "author": 1 or "genre_id": 2 but I can't seem to get it working. I keep getting django.db.utils.IntegrityError: books_book.genre_id may not be NULL.
I am using a DRF ModelViewSet for the views if that makes a difference.
What am I missing here?
You are getting Integrity error because it's expecting the author instance but you are sending the pk related to author. Try this
serializers.py
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
genre = GenreSerializer(read_only=True)
class Meta:
model = Book
fields = ('id','url', 'author', 'genre', 'title', 'isbn', 'summary',)
def create(self, validated_data):
author_id = self.initial_data.pop("author")
genre_id = self.initial_data.pop("genre")
author = Author.objects.get(id=author_id)
genre = Genre.objects.get(id=genre_id)
book = Book.objects.create(author=author, genre=genre, **validated_data)
return book

Categories

Resources