I have a couple models, one of which is already populated with data (book name/ chapter number/ paragraph number data), and I am implementing the feature for each user to be able to add a note per each unique book name/ chapter number/ paragraph number, which I could, but I have been stack for a couple of days trying to retrieve books with the related_name note of the current user if they have any. Here are my models:
Book model that is already populated with data.
from django.db import models
class Book(models.Model):
day = models.CharField(max_length=128)
book = models.CharField(max_length=128)
chapter = models.CharField(max_length=256)
paragraph = models.CharField(max_length=256)
text = models.TextField()
link = models.CharField(max_length=256)
def __str__(self):
return f'{self.book}_{self.chapter}.{self.paragraph} '
class Meta:
ordering = ['-id']
verbose_name = "Paragraph"
verbose_name_plural = "Paragraph"
Here is the Note model that should store the current user's note regarding a specific unique book name / chapter number / paragraph number:
from django.db import models
from django.conf import settings
from rest_framework.reverse import reverse
from paragraphs.models import Book
class Note(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='author', on_delete=models.CASCADE)
paragraph = models.ForeignKey(Book, related_name='note', on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
text = models.TextField(default=None)
def __str__(self):
return f'Note on {self.paragraph}'
class Meta:
ordering = ['created']
def save(self, *args, **kwargs):
"""
"""
options = {'text': self.text} if self.text else {}
super(Note, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('note-detail', args=[self.id])
Here are my serializers:
Book serializer
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
Note serializer
from rest_framework import serializers
from .models import Note
from users.serializers import UserSerializer
from paragraphs.serializers import BookSerializer
class NoteSerializer(serializers.ModelSerializer):
owner = UserSerializer(many=False, read_only=True)
class Meta:
model = Note
fields = ['id', 'owner', 'paragraph', 'text', 'created']
def to_representation(self, instance):
self.fields['paragraph'] = BookSerializer(read_only=True)
return super(NoteSerializer, self).to_representation(instance)
def user(self):
request = self.context.get('request', None)
if request:
return request.user
return None
def create(self, validated_data):
note, _ = Note.objects.update_or_create(
owner=self.user(),
paragraph=validated_data.get('paragraph', None),
defaults={'text': validated_data.get('text', None)})
return note
The data I am getting:
{
"id": 25,
"day": "2",
"book": "Some book",
"chapter": "1",
"paragraph": "3",
"text": "This is an example text that the user would like to attach a note to",
"link": "https://somelink.com",
}
The data I am trying to get:
{
"id": 25,
"day": "2",
"book": "Some book",
"chapter": "1",
"paragraph": "3",
"text": "This is an example text that the user would like to attach a note to",
"link": "https://somelink.com",
"note": "note of current user or none"
}
Any help is appreciated
models.py:
class Book(models.Model):
day = models.CharField(max_length=128)
book = models.CharField(max_length=128)
chapter = models.CharField(max_length=256)
paragraph = models.CharField(max_length=256)
text = models.TextField()
link = models.CharField(max_length=256)
def __str__(self):
return f'{self.book}_{self.chapter}.{self.paragraph} '
class Meta:
ordering = ['-id']
class Note(models.Model):
owner = models.ForeignKey(to = User, related_name='author', on_delete=models.CASCADE)
book = models.ForeignKey(Book, related_name='note', on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
text = models.TextField(default=None)
def __str__(self):
return '%s(%s)' %(self.owner,self.book)
serializers.py:
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ['id', 'owner', 'book', 'text', 'created']
class BookSerializer(serializers.ModelSerializer):
note = serializers.StringRelatedField(many=True, read_only=True)
# note = NoteSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = ['day','book','chapter','paragraph','text','link','note']
Output:
{
"day": "2",
"book": "some book",
"chapter": "1",
"paragraph": "example",
"text": "some textttttttttttttttttttttttttttttttttttttttttttttttt",
"link": "http://127.0.0.1:8000/admin/api/book/add/",
"note": [
"admin(some book_1.example )"
]
}
It's return all field:
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ['id', 'owner', 'book', 'text', 'created']
class BookSerializer(serializers.ModelSerializer):
# note = serializers.StringRelatedField(many=True, read_only=True)
note = NoteSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = ['day','book','chapter','paragraph','text','link','note']
Output:
{
"day": "2",
"book": "some book",
"chapter": "1",
"paragraph": "example",
"text": "some textttttttttttttttttttttttttttttttttttttttttttttttt",
"link": "http://127.0.0.1:8000/admin/api/book/add/",
"note": [
{
"id": 2,
"owner": 1,
"book": 1,
"text": "saaaaaaaaaaaaaaaaaaa",
"created": "2021-02-24T14:34:13.279750Z"
}
]
}
What you're actually trying to achieve is for the NoteSerializer to include fields from the Foreign-key related book model. Overriding the to_representation method of the serializer is clunky and not the way to go. See here a better approach.
Related
The code is given below with the present output and expected output.
In the ProductPriceMapping table the ProductDetail table and PriceList are related with a OneToOne relation, but When the data for Price is fetched with the related_name argument there must be one value for one product, the data is being displayed is a list data type.
models.py
from django.db import models
class PriceList(models.Model):
priceCode = models.BigAutoField(primary_key= True)
maxRetailPrice= models.FloatField(max_length=20)
baseDiscount = models.FloatField(max_length=20, default=0)
seasonalDiscount = models.FloatField(max_length=20, default=0)
def __str__(self):
return '%s'% (self.maxRetailPrice)
class ProductDetail(models.Model):
productCode = models.BigAutoField(primary_key=True)
productName = models.CharField(max_length=100)
manufacturer = models.CharField(max_length=100)
def __str__(self):
return self.productName
class ProductPriceMapping(models.Model):
productPriceCode= models.BigAutoField(primary_key=True)
productCode= models.ForeignKey(ProductDetail,on_delete=models.CASCADE,related_name='price')
priceCode= models.OneToOneField(PriceList,on_delete=models.CASCADE)
def __str__(self):
return '%s' % (self.priceCode)
serializers.py
from rest_framework import serializers
from .models import CategoryDetail, EmployeeDetail, ProductCategoryMapping, ProductPriceMapping, SalaryDetail, ProductDetail, PriceList
class ProductPriceListSerializer(serializers.ModelSerializer):
class Meta:
model = PriceList
fields = ('priceCode','maxRetailPrice',
'baseDiscount', 'seasonalDiscount')
class ProductPriceMappingSerializer(serializers.ModelSerializer):
class Meta:
model= ProductPriceMapping
fields= ('productPriceCode','productCode', 'priceCode')
class ProductDetailsSerializer(serializers.ModelSerializer):
category= serializers.StringRelatedField(many= True, read_only= True)
price = serializers.StringRelatedField( many= True, read_only= True)
class Meta:
model = ProductDetail
fields = ('productCode', 'productName', 'manufacturer','category', 'price')
The result of API as it looks:
[
{
"productCode": 1,
"productName": "NeoChef",
"manufacturer": "LG",
"category": [
"1: Microwave Oven"
],
"price": [
"26000.0" ##expected the price value not be in a list
]
},
{
"productCode": 2,
"productName": "The Frame",
"manufacturer": "Samsung",
"category": [
"2: Television"
],
"price": [
"120000.0" ##expected the price value not be in a list
]
},
{
"productCode": 3,
"productName": "Galaxy S22+",
"manufacturer": "Samsung",
"category": [
"3: Smart Phone"
],
"price": [
"79000.0" ##expected the price value not be in a list
]
}
]
Expected result:
[
{
"productCode": 1,
"productName": "NeoChef",
"manufacturer": "LG",
"category": [
"1: Microwave Oven"
],
"price": "26000.0"
}
]```
In your case, the price field is not a one-to-one related field, you either need to change the productCode to OneToOneField or if you don't want to change the DB field, you can achieve the same result simply with SerializerMethodField. In the first case, removing many=True argument from the serializer field should help. In second case, SerializerMethodField will help you to make your custom representation, e.g.:
class ProductDetailsSerializer(serializers.ModelSerializer):
category= serializers.StringRelatedField(many=True, read_only=True)
price = serializers.SerializerMethodField()
class Meta:
model = ProductDetail
fields = ('productCode', 'productName', 'manufacturer','category', 'price')
def get_price(self, obj):
# If it's guaranteed that there will be only one related object, or retrieve the needed object depending on your demands
return str(obj.price.first())
One simple way is to use MethodField...
class ProductPriceMappingSerializer(serializers.ModelSerializer):
priceCode = serializers.SerializerMethodField()
class Meta:
model= ProductPriceMapping
fields= ('productPriceCode','productCode', 'priceCode')
#staticmethod
def get_priceCode(obj):
return obj.priceCode.maxRetailPrice # or any other fields that you like to show in your response
but if you want to show all of the priceList fields based on your other serializer you can do something like:
class ProductPriceMappingSerializer(serializers.ModelSerializer):
priceCode = ProductPriceListSerializer()
class Meta:
model= ProductPriceMapping
fields= ('productPriceCode','productCode', 'priceCode')
Thank you guys, I solved in this way:
models.py
class ProductPriceMapping(models.Model):
productPriceCode= models.BigAutoField(primary_key=True)
productCode= models.OneToOneField(ProductDetail,on_delete=models.CASCADE,related_name='price')
priceCode= models.ForeignKey(PriceList,on_delete=models.CASCADE)
def __str__(self):
return '%s' % (self.priceCode)
serializers.py
class ProductDetailsSerializer(serializers.ModelSerializer):
category= serializers.StringRelatedField(many= True, read_only= True)
price = serializers.StringRelatedField(read_only= True)
class Meta:
model = ProductDetail
fields = ('productCode', 'productName', 'manufacturer','category', 'price')
Please correct my title if it's not correct. My problem is I want to retrieve FinishType's name from Product. I have tried 2 ways to achieve this: first attempt and second attempt.
My simplifed related models in models.py:
class Product(models.Model):
product_id = models.CharField(max_length=6)
color = models.ForeignKey(ColorParent, on_delete=models.SET_NULL, null=True)
collection = models.ForeignKey(ProductCollection, on_delete=models.SET_NULL, null=True)
#property
def get_distributors(self):
return Distributor.objects.filter(distributor__products=self).count()
def __str__(self):
return self.product_id
class FinishType(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class ProductFinishType(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
market = models.ForeignKey(Market, on_delete=models.CASCADE)
finish_types = models.ManyToManyField(FinishType)
def __str__(self):
return '%s - %s' % (self.product, self.market)
class ProductAlias(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
market = models.ForeignKey(Market, on_delete=models.CASCADE)
name = models.CharField(max_length=50, null=True, blank=True)
def __str__(self):
return '%s - %s' % (self.product, self.name)
My serializers.py:
class ProductGridSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField(source='get_name')
finishing = serializers.SerializerMethodField('get_finish_types')
distributor = serializers.ReadOnlyField(source='get_distributors')
#staticmethod
def get_name(obj):
return [pa.name for pa in obj.productalias_set.all()]
#staticmethod
def get_finish_types(obj):
return [pft.name for pft in obj.productfinishtype_set.all().select_related('finish_types')] # first attempt
class Meta:
model = Product
fields = ['id', 'product_id', 'name', 'collection', 'finishing', 'distributor']
First attempt works for name field which fetches ProductAlias's name but gives me this error:
FieldError at /api/product_grids/
Invalid field name(s) given in select_related: 'finish_types'. Choices are: product, market
My get_finish_types() on second attempt:
#staticmethod
def get_finish_types(obj):
product_finish_types = obj.productfinishtype_set.all()
response = ProductFinishTypeSerializer(product_finish_types, many=True, source='finish_types').data
return response
It gives me the whole object datas:
{
"id": 1,
"product_id": "BQ1111",
"name": [
"White Stone"
],
"collection": 1,
"finishing": [
{
"id": 1,
"product": 1,
"market": 1,
"finish_types": [
1,
3,
5
]
}
],
"distributor": 5
},
My desired output is something like:
{
"id": 1,
"product_id": "BQ1111",
"name": [
"White Stone"
],
"collection": 1,
"finishing": [
"Polished",
"Carved",
"Melted"
],
"distributor": 5
},
Create a serializer for FinishType,
class FinishTypeSerializer(serializers.ModelSerializer):
class Meta:
model = FinishType
fields = ('name',)
and wire-up it in ProductGridSerializer using SerializerMethodField
class ProductGridSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField(source='get_name')
distributor = serializers.ReadOnlyField(source='get_distributors')
finishing = serializers.SerializerMethodField()
def get_finishing(self, product):
qs = FinishType.objects.filter(productfinishtype__product=product)
return FinishTypeSerializer(qs, many=True).data
#staticmethod
def get_name(obj):
return [pa.name for pa in obj.productalias_set.all()]
class Meta:
model = Product
fields = ['id', 'product_id', 'name', 'collection', 'finishing', 'distributor']
Inspired by #Arakkal Abu's queryset, I tried it using my first attempt.
FinishTypeSerializer added in serializers.py:
class FinishTypeSerializer(serializers.ModelSerializer):
class Meta:
model = FinishType
fields = ('name',)
ProductGridSerializer in serializers.py:
class ProductGridSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField(source='get_name')
finishing = serializers.SerializerMethodField(source='get_finishing')
distributor = serializers.ReadOnlyField(source='get_distributors')
#staticmethod
def get_name(obj):
return [pa.name for pa in obj.productalias_set.all()]
#staticmethod
def get_finishing(product):
return [pft.name for pft in FinishType.objects.filter(productfinishtype__product=product)]
class Meta:
model = Product
fields = ['id', 'product_id', 'name', 'collection', 'finishing', 'distributor']
The JSON output is:
{
"id": 1,
"product_id": "BQ1111",
"name": [
"White Stone"
],
"collection": 1,
"finishing": [
"Polished",
"Honed",
"Carved"
],
"distributor": 5
},
I have the following 2 Django models, Lessons and UserLessons.
Lessons would be created by an admin and UserLessons is basically when a user is in progress with the Lesson or completed the Lesson, linked by a foreign key. UserLesson doesn't necessarily contain a Lesson entry, till the user actually starts with that specific one.
As I'm building an API (with DRF), I need to list the full list of all Lessons - easy.
LessonList = Lesson.objects.all().values('id', 'title')
This returns
[
{
"id": 1,
"title": "Lesson 1"
},
{
"id": 2,
"title": "Lesson 2"
},
{
"id": 3,
"title": "Lesson 3"
}
]
However, I need to be able to merge it with UserLesson (eg UserLessonList = UserLesson.objects.filter(user=request.user).values('id', 'lesson__id', 'completed') which currently returns
[
{
"id": 2,
"lesson__id": 1,
"completed": true
},
{
"id": 3,
"lesson__id": 2,
"completed": true
}
]
Ideally, it should return all the lessons from the db, and the the completed values, defaulting to completed: false if that specific lesson doesn't exist in DB.
Any suggestions?
Edit:
Views
class LessonList(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
LessonList = Lesson.objects.all().values('id', 'title')
UserLessonList = UserLesson.objects.filter(user=request.user).values('id', 'lesson__id', 'completed')
return Response(LessonList)
Models
class Lesson(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=250, verbose_name=u'Title')
slug = models.SlugField(null=True, blank=True, help_text='eg, lesson-1-whats-up')
published = models.BooleanField(default=False)
def __str__(self):
return(self.title)
class UserLesson(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, null=True)
completed = models.BooleanField(default=False)
def __str__(self):
text = str(self.lesson.title)
return(text)
You should use ModelViewSet and serializers. Exactly ModelSerializer. Something like this:
class LessonSerializer(serializers.ModelSerializer):
completed = serializers.SerializerMethodField()
class Meta:
model = Lesson
fields = ['id', 'title', 'completed']
def get_completed(self, obj):
user = self.context.get('request').user
return UserLesson.objects.filter(user=user, lesson=obj, completed=True).exists()
class LessonViewSet(viewsets.ModelViewSet):
queryset = Lesson.objects.filter(published=True)
serializer_class = LessonSerializer
permission_classes = [IsAuthenticated]
I have two model, Person and Appointment. A Person have many Appointment.
models.py
class Person(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __str__(self):
return self.name
class Appointment(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='appointments')
date = models.DateField(unique=True)
class Meta:
ordering = 'date',
def __str__(self):
return str(self.date)
and my serializers.py file is:
from rest_framework import serializers
from appointment.models import Person, Appointment
class AppointmentSerializer(serializers.ModelSerializer):
class Meta:
model = Appointment
fields = ('date',)
class PersonSerializer(serializers.ModelSerializer):
appointments = AppointmentSerializer(many=True)
class Meta:
model = Person
fields = ('name', 'email', 'appointments',)
def create(self, validated_data):
appointments_data = validated_data.pop('appointments')
person = Person.objects.create(**validated_data)
for appointment_data in appointments_data:
Appointment.objects.create(person=person, **appointment_data)
return person
and finally my viewsets is:
class PersonViewSet(viewsets.ModelViewSet):
serializer_class = PersonSerializer
queryset = Person.objects.all()
filter_backends = [DjangoFilterBackend]
filter_fields = ['name', 'appointments']
This return persons_list as my expectation:
[
{
"name": "Arif Hasan",
"email": "arif#gmail.com",
"appointments": [
{
"date": "2016-10-10"
},
{
"date": "2016-10-17"
},
{
"date": "2016-11-07"
}
]
},
{
"name": "Atanu Shome",
"email": "atanu#gmail.com",
"appointments": [
{
"date": "2016-11-13"
}
]
}
]
But i can't create new appointment here.
Can't filter person by date range.
You should use tuples for specifying filter_backends, filter_fields.
Also you want to filter by date field so you have to specify appointments__date in your filter_fields insted of just appointments.
You should have your viewset defined as follows:
class PersonViewSet(viewsets.ModelViewSet):
serializer_class = PersonSerializer
queryset = Person.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields = ('name', 'appointments__date',)
Check : Django filtering
I have setup my serializer to return nested content successfully.
However, I have not been able to post data within the nested fields.
I don't get an error when posting the data- but it only posts to the non-nested fields.
I would like for it to take the "name" field OR primary key (of model "TAG") for posting item.
Models.py
class Tag(models.Model):
name = models.CharField("Name", max_length=5000, blank=True)
def __str__(self):
return self.name
class Movie(models.Model):
title = models.CharField("Whats happening?", max_length=100, blank=True)
tag = models.ManyToManyField('Tag', blank=True)
def __str__(self):
return self.title
Serializers.py:
class TagSerializer(serializers.ModelSerializer):
taglevel = filters.CharFilter(taglevel="taglevel")
class Meta:
model = Tag
fields = ('name', 'taglevel', 'id')
class MovieSerializer(serializers.ModelSerializer):
tag = TagSerializer(many=True, read_only=False)
info = InfoSerializer(many=True, read_only=True)
class Meta:
model = Movie
fields = ('title', 'tag', 'info')
def validate(self, data):
print(self.initial_data.__dict__)
data['tag_name'] = []
if 'tag' in self.initial_data.keys():
for entry in self.initial_data['tag']:
data['tag_name'].append(entry['name'])
return data
def create(self, validated_data):
print(validated_data)
tags_data = validated_data.pop('tag')
movie = Task.objects.create(**validated_data)
for tag_data in tags_data:
Movie.objects.create(name=name, **tag_data)
return movie
Sample of posting data:
r = requests.post('http://localhost:8000/api/Data/',{ "title": "TEST_title", "tag": [ { "name": "test1", "name": "test2" } ], "info": [] })
Your json should be.
{
"title": "TEST_title",
"tag": [ {"name": "test1" },
{"name": "test2"}
],
"info": []
}
class TagSerializer(serializers.ModelSerializer):
taglevel = filters.CharFilter(taglevel="taglevel")
class Meta:
model = Tag
fields = ('name', 'taglevel', 'id')
class MovieSerializer(serializers.ModelSerializer):
tag = TagSerializer(many=True, read_only=False)
info = InfoSerializer(many=True, read_only=True)
class Meta:
model = Movie
fields = ('title', 'tag')
def create(self, validated_data):
tags_data = validated_data.pop('tag')
movie = Movie.objects.create(**validated_data)
for tag_data in tags_data:
movie.tag.create(**tag_data)
return movie