DRF Serializer readable - writeable non-model field - django

On a project with Django / DRF; I have the following model structure:
class City(models.Model):
name = models.CharField(max_length=100)
class Company(models.Model):
city = models.ForeignKey(City)
.
.
And following serializer structure for Company model:
class CompanySerializer(serializers.ModelSerializer):
city_name = serializers.CharField(write_only=True)
.
.
class Meta:
model = Company
fields = ('city_name',)
def create(self, validated_data):
# Get city
city_name = validated_data.pop('city_name')
try:
city = City.objects.get(name__iexact=city_name)
except City.DoesNotExist:
city = City.objects.create(name=city_name.title())
company = Company.objects.create(city=city, **validated_data)
return company
While creating a company through the serializer, user provides a city_name, I create a new city with that name if not exists or use an existing entry if exists. In this structure, I want to be able to return city_name field while returning companies. It is not a field on the model, so I could use SerializerMethodField normally, but I also want this field to be writable as well. Do I have any options here?

i think, your solution is simple to add source and remove write_only:
city_name = serializers.CharField(source='city.name')
After changing to this approach, you can get city name in create or update methods as follows:
city_data = validated_data.pop('city')
city_name = city_data.get('name')

A better approach is to create two serializer
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = City
fields = ('name')
class CompanySerializer(serializers.ModelSerializer):
city = CitySerializer(write_only=True)
class Meta:
model = Company
fields = ('city',)
def create(self, validated_data):
# Get city
city = validated_data.pop('city')
try:
city = City.objects.get(name__iexact=city)
except City.DoesNotExist:
city = City.objects.create(name=city.title())
company = Company.objects.create(city=city, **validated_data)
return company

Related

add instances to a manytomany field in django rest framework

I have a very simple question and I'm surprised it hasn't been asked before on this website.
I have the two following models:
# models.py
class Film(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=150)
genre = models.ManyToManyField(Genre)
class Genre(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, unique=True)
I need to make an API that gets 2 integers, id of movie and id of genre, and adds the said genre to the movie. seems very simple but I don't know how I could do this. I could really use the help.
You might want this:
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = ('name', 'id')
class FilmSerializer(serializers.ModelSerializer):
genre = GenreSerializer(many=True, read_only=False)
class Meta:
model = Film
fields = ('name', 'id', 'genre')
def update(self, instance, validated_data):
genres_data = validated_data.pop('genre')
instance = super(FilmSerializer, self).update(instance, validated_data)
for genre_data in genres_data:
genre_qs = Genre.objects.filter(name__iexact=genre_data['name'])
if genre_qs.exists():
genre = genre_qs.first()
else:
genre = Genre.objects.create(**genre_data)
instance.genre.add(genre)
return instance

How to retrieve data from model that current user created and list it for another model's field in django

Let us imagine that I have two models.
First model contains curse details and user that created this course
class Course(models.Model):
course_name = models.CharField(max_length=100, null=False)
description = models.CharField(max_length=255)
user_profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
and my second model is:
class Lesson(models.Model):
course = models.OneToOneField(Course, on_delete=models.CASCADE) #
# inside the course I want my APIVIEW to list only the courses that current user created.
# OnetoOne relationship does not solve the problem.
status = models.CharField(choices=STATUS, null=False, default=GOZLEMEDE,max_length=20)
tariffs = models.FloatField(max_length=5,null=False,default=0.00)
continues_off = models.CharField(max_length=2)
user_profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
My serializers for both Models:
class LessonSerializer(serializers.ModelSerializer):
class Meta:
model = models.Lesson
fields = ('course', 'status', 'tariffs', 'continues_off', 'user_profile')
def create(self, validated_data):
lesson = models.Lesson.objects.create(
course = validated_data['course'],
status = validated_data['status'],
tariffs=validated_data['tariffs'],
continues_off=validated_data['continues_off'],
user_profile=validated_data['user_profile']
)
return lesson
class CourseSerializer(serializers.ModelSerializer):
"""Serializers Course content"""
class Meta:
model = models.Course
fields = '__all__'
def create(self,validated_data):
course = models.Course.objects.create(
course_name = validated_data['course_name'],
description=validated_data['description'],
user_profile=validated_data['user_profile']
)
return course
My Viewset:
class LessonViewset(viewsets.ModelViewSet):
model = models.Lesson
serializer_class = serializers.LessonSerializer
authentication_classes = (SessionAuthentication,)
permission_classes = (IsAuthenticated,BasePermission,)
def get_queryset(self):
user_current = self.request.user.id
return models.Lesson.objects.filter(user_profile=user_current)
How can I get the desired result. I want to get the courses for the current user and show them as a dropdown list in my API view. Just only the courses that user created should be in the dropdown list not all.
OnetoOne relationship gives all results of course table.
i think change your view code to :
def get_queryset(self,id):
return model.objects.filter(user_profile=id)
#You do not need to call it again when you put the Lesson on the model
\

Trying to relate tables in Django

trying to POST request and create new location. now the filed country_id cues many troubles.
those are the classes
class Country(models.Model):
id = UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
country_name = CharField(max_length=255)
federal_rate = FloatField()
social_security = FloatField()
comment = CharField(max_length=255)
class Location(models.Model):
participant_id = ForeignKey('Participant', on_delete=CASCADE, related_name="locations")
country_id = ForeignKey('Country', on_delete=CASCADE, related_name="country")
start_date = DateField()
end_date = DateField()
region = CharField(max_length=255)
tax_policy = CharField(max_length=255, null=True)
Serializer
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = "__all__"
class LoactionSerializer(serializers.ModelSerializer):
country_id = CountrySerializer(many=False)
class Meta:
model = Location
fields = "__all__"
now the only why that I mange to join the tables are adding the country_id line in the seirializer, and whem im trying to create new location, an error occur
this is the way I'm trying to add new location
{
"start_date": "2021-10-10",
"end_date": "2021-11-18",
"region": "markian-land",
"tax_policy": "N/A",
"participant_id": "c5c1a00c-4263-418a-9f3a-3ce40a1ea334",
"country_id": "9067e71f-c6b9-4ecc-ad6b-461843063aee"
}
the Error - {"country_id": {"non_field_errors": ["Invalid data. Expected a dictionary, but got str."]}}
when i mange to send as dict, but i have another error that asking for all Country fields, so i get the specific country from data base, and when im sending it i got json error
You can have more details in this answer
Django Rest Framework doesn't provide this functionality, you need to create a serializer field first.
from rest_framework import serializers
class RelatedFieldAlternative(serializers.PrimaryKeyRelatedField):
def __init__(self, **kwargs):
self.serializer = kwargs.pop('serializer', None)
if self.serializer is not None and not issubclass(self.serializer, serializers.Serializer):
raise TypeError('"serializer" is not a valid serializer class')
super().__init__(**kwargs)
def use_pk_only_optimization(self):
return False if self.serializer else True
def to_representation(self, instance):
if self.serializer:
return self.serializer(instance, context=self.context).data
return super().to_representation(instance)
Then use this new serializer field in Location serializer as,
class LoactionSerializer(serializers.ModelSerializer):
country_id = RelatedFieldAlternative(queryset = Country.objects.all(),
serializer=CountrySerializer)
class Meta:
model = Location
fields = "__all__"
Please note: You have a typo in LoactionSerializer.
this
country_id = ForeignKey(Country, on_delete=CASCADE, related_name="country")
You defined country_id as a CountrySerializer in the LocationSerializer and in the CountrySerializer, fields = "__all__" means you need to pass all the country object fields to make it work.
Try to delete country_id = CountrySerializer(many=False) in the LocationSerializer and try again.

Many to Many field POST requests on API Django Rest Framework

So I have 3 models of interest:
models.py
class Author(models.Model): #Author of books
name = models.CharField(max_length = 100)
#property # This is code to get the books linked to an author
def books(self):
book = Book.objects.filter(authors = self.id)
return ', '.join(map(str,book)) #This makes the book list in this format "A, B, C"
def __str__(self):
return self.name.title()
class Genre(models.Model): #Genre of books
name = models.CharField(max_length = 50, unique = True)
#property
def books(self):
book = Book.objects.filter(genre = self.id)
return ', '.join(map(str,book))
def __str__(self):
return self.name.title()
class Book(models.Model):
name = models.CharField(max_length = 150,) #Name of books
authors = models.ManyToManyField(Author,) #Many to many because multiple books can have multiple authors
genre = models.ManyToManyField(Genre)
def __str__(self):
return self.name.title()
Those are the models, and both genre and author have a many to many relation with books
serializers.py
class AuthorListSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
fields = ('name',)
class GenreListSerializer(serializers.ModelSerializer):
class Meta:
model = models.Genre
fields = ('name',)
class BookListSerializer(serializers.ModelSerializer):
authors = AuthorListSerializer(many = True,) #To represent the relationship as a string instead of id
genre = serializers.SlugRelatedField(many = True, queryset = models.Genre.objects.all(),slug_field = 'name')
class Meta:
model = models.Book
fields = ('name','authors','rating', 'genre')
class BookDetailSerializer(serializers.ModelSerializer):
publisher = serializers.SlugRelatedField(queryset = models.Publisher.objects.all(), slug_field= 'name') #To display publisher name instead of id
authors = serializers.StringRelatedField(many = True,) #To represent the relationship as a string instead of id
genre = serializers.StringRelatedField(many = True,)
class Meta:
model = models.Book
fields = ('name', 'authors', 'rating','genre',
'publisher', 'total_qty', 'avail_qty',
'pub_date','isbn','price',)
So in my Django-Rest-Framework Browsable api, Normal ForeignKey fields have their options displayed to create a new object instance.
For example The publishers in the "BookDetailSerializer" have all the publishers displayed as options for a POST or PUT or PATCH request.
but for the many to many fields that include Genre and Author it is not only blank, but it appears that I cannot put any input whatsoever.
I have tried using the DRF-writable-nested third party package but nothing changed:
class BookListSerializer(WritableNestedModelSerializer):
authors = AuthorListSerializer(many = True,) #To represent the relationship as a string instead of id
genre = serializers.SlugRelatedField(many = True,
queryset = models.Genre.objects.all(),slug_field = 'name')
My question is how can I be able to make POST request with my list of Authors and Genres available through the DRF browsable api?
Thanks in advance!!
This image shows the options available for making a POST request involving publishers
This image shows that you can't add any input for POST request involving both Genre and Authors as many-to many relations.
Update: So I have added a create method and it still doesn't work, as shown below:
class BookListSerializer(serializers.ModelSerializer):
authors = AuthorListSerializer(many = True,) #To represent the relationship as a string instead of id
genre = serializers.SlugRelatedField(many = True,
queryset = models.Genre.objects.all(),slug_field = 'name')
class Meta:
model = models.Book
fields = ('name','authors','rating', 'genre')
def create(self,validated_data):
authors_data = models.Author.objects.all()
book = Book.objects.create(authors = authors_data,**validated_data,)
return book
What can I do?
After struggling for 4 days I figured out the answer
When handling many to many relationships and you want to want the code to be as follows.
class BookListSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ('name','authors','rating', 'genre')
depth = 1
Adding the depth allows the nested relationships to fully serialize.
Because the relationship is nested, and is a many to many relationship you would have to create your own create function in the views.py as follows:
class BookViewSet(GetSerializerClassMixin, viewsets.ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = serializers.BookDetailSerializer
serializer_action_classes = {'list': serializers.BookListSerializer}
permission_classes = [IsAdminOrReadOnly, permissions.IsAuthenticated,]
def create(self,request, *args, **kwargs):
data = request.data
new_book = models.Book.objects.create(name = data["name"], publisher = models.Publisher.objects.get(id = data["publisher"]),
pub_date = data["pub_date"],
price = data["price"],
isbn = data['isbn'],)
new_book.save()
for author in data['authors']:
author_obj = models.Author.objects.get(name = author['name'])
new_book.authors.add(author_obj)
for gen in data['genre']:
gen_obj = models.Genre.objects.get(name = gen['name'])
new_book.genre.add(gen_obj)
serializer = serializers.BookListSerializer(new_book)
return Response(serializer.data)
For the many to many relationships you have to create them after saving the object and add them to the object manually.
There is a foriegn Key relationship that's present there "Publishers"
For that relationship you have to manually point to its location in the database hence the code below.
name = data["name"], publisher = models.Publisher.objects.get(id = data["publisher"]),
For the Detail book serializer in the question its the same thing with the BookListSerializer
That's how I was able to handle POST requests in the Many-to-Many relations

Cannot set values on a ManyToManyField which specifies an intermediary model

How can I set values on to a ManyToManyField which specifies an intermediary model?
In the models.py
class BookUser(models.Model):
email = models.EmailField()
class Book(models.Model):
author_id= models.CharField(max_length=255)
send_to = models.ManyToManyField(BookUser, through='BookUserRelationship')
book_id = models.AutoField(primary_key=True)
file_size = models.CharField(null=True)
class BookUserRelationship(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
user = models.ForeignKey(BookUser, on_delete=models.CASCADE)
shared_date = models.DateTimeField(auto_now_add=True,null=True,blank=True)
Tried to update in serializers.py
class BookSerializer(serializers.ModelSerializer):
send_to = BookUserSerializer(many=True, read_only=True)
class Meta():
model = Book
fields = ('book_id', 'author_id','file_size','send_to')
class BookUserSerializer(serializers.ModelSerializer):
model = BookUser
fields = ('email')
In the views.py for listing the books by passing the book_id as query params
class BookListView(generics.ListCreateAPIView):
serializer_class = serializers.BookSerializer
def get(self, request, *args, **kwargs):
user = self.request.user
book_id = self.request.query_params.get('book_id', None)
if book_id:
book = models.Book.objects.filter(book_id=book_id)
return Response(serializers.BookSerializer(book[0]).data)
You don't need to do anything at all. You have already set the relevant data in your for loop, by creating the BookUserRelationship instances. That is the many-to-many relationship; you should just remove the instance.send_to.set(emails) line.