Dynamic Serialization for related fields - django

I have the following models:
class Country(models.Model):
"""
Country model
"""
# With name and isoCode (charfield)
...
class State(models.Model):
"""
State model
"""
# With name and isoCode (charfield)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
...
class City(models.Model):
"""
City model
"""
name = models.CharField(max_length=32)
state = models.ForeignKey(State, on_delete=models.CASCADE)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
...
And UserLocation referenced by:
class Location(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="location")
country = models.ForeignKey(Country, on_delete=models.CASCADE)
state = models.ForeignKey(State, on_delete=models.CASCADE)
city = models.ForeignKey(City, on_delete=models.CASCADE)
How do I build a serializer that creates UserLocation, as well as return the info in JSON?
I have tried
class LocationSerializer(serializers.ModelSerializer):
country = serializers.SlugRelatedField(slug_field="isoCode", queryset=Country.objects.all())
state = serializers.SlugRelatedField(slug_field="isoCode", queryset=State.objects.filter(country__isoCode=country))
city = serializers.SlugRelatedField(slug_field="name", queryset=City.objects.all())
class Meta:
model = Location
fields = ["user", "country", "state", "city"]
But it does not work, it gives the error
{"state":["Object with isoCode=BC does not exist."],...
How does one create a dynamic linked serializer? Or how does one work around this?

I think you want following serializer
class LocationSerializer(serializers.ModelSerializer):
country = serializers.ReadOnlyField(source="country.isocode")
state = serializers.ReadOnlyField(source="state.isocode")
city = serializers.ReadOnlyField(source="city.name")
class Meta:
model = Location
fields = ["user", "country", "state", "city"]
However, it seems your model design is incorrect.
You don't need to have country, state foreign keys in Location model, since City model has state, State model has country.

For future users. I found that
class LocationSerializer(serializers.ModelSerializer):
'''Used to serialize user location'''
country = serializers.SlugRelatedField(slug_field="isoCode", queryset=Country.objects.all(), required=True)
class Meta:
model = Location
fields = ["user", "country"]
extra_kwargs = {"user": { "validators":[UniqueValidator(queryset=Location.objects.all(), message=_("User already has a location. Please update user location. Hint: use PUT request"))]}}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if "country" in kwargs['context']['request'].data and len(Country.objects.get(isoCode=self.initial_data["country"]).state_set.all()) > 0:
self.fields["state"] = serializers.SlugRelatedField(slug_field="isoCode", queryset=State.objects.filter(country__isoCode=self.initial_data["country"]), required=True)
if "state" in kwargs['context']['request'].data and len(State.objects.get(isoCode=self.initial_data["state"], country__isoCode=self.initial_data["country"]).city_set.all()) > 0:
self.fields["city"] = serializers.SlugRelatedField(slug_field="name", queryset=City.objects.filter(state__isoCode=self.initial_data["state"],country__isoCode=self.initial_data["country"]), required=True)
Seemed to work for my case

Related

I want to post a list of JSON values to a model's field in Django

I want to post a movie into the collection's movie field( list of movies).
I define the model as
class Movie(models.Model):
# collection = models.ForeignKey(Collection, on_delete = models.CASCADE) #, related_name='reviews'
title = models.CharField(max_length=200)
description = models.CharField(max_length=200)
genres = models.CharField(max_length=200)
uuid = models.CharField(max_length=200)
def __str__(self):
return self.title
class Collection(models.Model):
title = models.CharField(max_length=200)
uuid = models.CharField(max_length=200, primary_key = True)
description = models.CharField(max_length=200)
movie = models.ForeignKey(Movie, on_delete = models.CASCADE)
def __str__(self):
return self.title
this is how i am using the viewset
class CollectionViewSet(viewsets.ModelViewSet):
queryset = models.Collection.objects.all()
serializer_class = serializers.CollectionSerializer
but i am not able to enter values for the movie field
enter image description here
also my serializer
class CollectionSerializer(serializers.ModelSerializer):
class Meta:
model = Collection
fields = '__all__'
By default, DRF will represent the relationship with a PrimaryKeyRelatedField, thus expecting a movie ID.
To achieve what you want (create an instance of movie with a collection), you need to overwrite the foreign key field in your serializer with your own Movie serializer.
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class CollectionSerializer(serializers.ModelSerializer):
movie = MovieSerializer()
class Meta:
model = Collection
fields = '__all__'
def create(self, validated_data):
movie = validated_data.pop('movie')
movie = Movie .objects.create(**movie )
collection = Collection.objects.create(movie=movie, **validated_data)
return collection
You need to overwrite the create method so when creating a Collection, you also create a movie.
However, I am not sure the foreign key is set in the right model in your model. (a movie belongs to many collection but not the other way around?) If that's not what you want, just reverse the logic for the serializer.
Edit:
Sending the following should work fine:
{ "uuid": "1001",
"title": "Action",
"description": "Action Movies",
"movie": { "title": "The Burkittsville 7",
"description": "The story of Rustin Parr.",
"genres": "Horror",
"uuid": "5e904"
}
}
The only problem as I mentionned earlier is in your model you defined the foreign key field in collection. So it expects one single movie instance and not a list, thus I took off the brackets you put around movie. Maybe you should consider setting the foreign key in the Movie model, or use a Many to many relationship.
#models.py
class Movie(models.Model):
# collection = models.ForeignKey(Collection, on_delete = models.CASCADE) #, related_name='reviews'
title = models.CharField(max_length=200)
description = models.CharField(max_length=200)
genres = models.CharField(max_length=200)
uuid = models.CharField(max_length=200)
def __str__(self):
return self.title
class Collection(models.Model):
title = models.CharField(max_length=200)
uuid = models.CharField(max_length=200, primary_key = True)
description = models.CharField(max_length=200)
movie = models.ManyToManyField(Movie, on_delete = models.CASCADE)
def __str__(self):
return self.title
serializers.py:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class CollectionSerializer(serializers.ModelSerializer):
movie = MovieSerializer(read_only=True, many=True)
class Meta:
model = Collection
fields = '__all__'
hope this will give you better unserstand this will work for you

Unable to get data of Foreign key tables in same serializer

Order table
class Orders(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, blank=True, null=True)
tableid=models.IntegerField()
orderid=models.IntegerField()
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
Articles table to save articles like pizza
class OrderArticle(models.Model):
order = models.ForeignKey(Orders, on_delete=models.CASCADE)
article = models.ForeignKey(Articles, on_delete=models.CASCADE)
# article_options = models.ManyToManyField(ArticlesOptions)
Article options to save extra topping or any option available
class OrderArticleOptions(models.Model):
# order = models.ForeignKey(Orders, on_delete=models.CASCADE)
article_option = models.ForeignKey(ArticlesOptions, on_delete=models.CASCADE)
order_article = models.ForeignKey(OrderArticle, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
price = models.DecimalField(max_digits=10, decimal_places=2)
EDIT
Article Option table
class ArticlesOptions(models.Model):
articleoptionrestaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE , blank=True, null=True)
optionname = models.ForeignKey(ArticlesOptionsName, on_delete=models.CASCADE, related_name="optionnames")
min = models.IntegerField()
max = models.IntegerField()
choice_price = models.DecimalField(max_digits=10, decimal_places=2)
choice = models.CharField(max_length=255)
def __str__(self):
return str(self.optionname)
So Now issue is When I try to get all data in one serialize I am not able to get . I am using this example to get
https://www.django-rest-framework.org/api-guide/relations/
EDIT
My serilizers are
class OrderSerializer(serializers.ModelSerializer):
restaurant=RestaurantSerializer(required=False)
class Meta:
model = Orders
fields = ['restaurant','tableid', 'orderid', 'total_amount']
class ArticlesSerializer(serializers.ModelSerializer):
order = OrderSerializer(read_only=True)
article=ListArticleSerializer(read_only=True)
class Meta:
model = OrderArticle
fields = ['order', 'article']
class ArticlesOptionSerializer(serializers.ModelSerializer):
article_option = ListCategoriesSerializer( read_only=True)
order_article=ArticlesSerializer(read_only=True)
class Meta:
model = OrderArticleOptions
fields = ['article_option','order_article','quantity','price']
depth=1
My view.py is
class OrderedArticles(APIView):
def get(self, request, restid):
Options=OrderArticleOptions.objects.filter(order_article=1)
orderserlizer=ArticlesOptionSerializer(Options , many=True)
return Response(success_response({'orders': orderserlizer.data},
"Restaurant with this all data."), status=status.HTTP_200_OK)
My JSON Response is
"article_option":{ },
"order_article":{
"order":{
"restaurant":{ },
"tableid":12,
"orderid":1,
"total_amount":"0.00"
},
"article":{
"id":1,
"category":{ },
"ingredient":[ ],
"articleoptionnames":{ },
"restaurant":{ },
"articlename":"Article1",
"price":"1.90",
"pickuptax":6,
"dineintax":21,
"description":"This is a tekst field with more information about the product",
"image":"/media/Article/c1.264f3b28_sxcPiqi.png"
}
},
While I want these "article_option" to be as child of article like Article {article_option1, article_option2} but its creating new objects with every new article option.
If I understand you correctly, you want to return a representation of OrderArticle which have ArticleOption objects as its children. Which means you should instantiate an ArticlesSerializer in your view, but also modify ArticlesSerializer so that it includes all related article_options as a list (using the source attribute). Something like the following:
class ArticlesOptionSerializer(serializers.ModelSerializer):
article_option = ListCategoriesSerializer(read_only=True)
class Meta:
model = OrderArticleOptions
fields = ['article_option', 'order_article', 'quantity', 'price']
class ArticlesSerializer(serializers.ModelSerializer):
order = OrderSerializer(read_only=True)
article = ListArticleSerializer(read_only=True)
article_options = ArticlesOptionSerializer(read_only=True, source='orderarticleoptions_set', many=True)
class Meta:
model = OrderArticle
fields = ['order', 'article', 'article_options']
Then in your view, you should instantiate your ArticlesSerializer with the appropriate OrderArticle object:
class OrderedArticles(APIView):
def get(self, request, restid):
order_article = OrderArticle.objects.get(pk=1) # get pk/id from request
serializer = ArticlesSerializer(order_article)
return Response(serializer.data, status=status.HTTP_200_OK)

How to display many fields' values with ForeignKey relationship?

Looking for solution of this problem I encountered some similar threads, but referring to older versions of Django/DRF and thus not working in my case.
There are these two models:
class CsdModel(models.Model):
model_id = models.CharField("Item ID", max_length=8, primary_key=True)
name = models.CharField("Item Name", max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class CsdListing(models.Model):
model_id = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='m_id')
name = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='m_name')
(...)
EDIT: Serializers are defined this way:
class CsdModelSerializer(serializers.ModelSerializer):
model_id = serializers.RegexField(regex='^\w{2}\d{3}$', allow_blank=False)
name = serializers.CharField(min_length=6, max_length=50, allow_blank=False)
class Meta:
model = CsdModel
fields = '__all__'
class CsdListingSerializer(serializers.ModelSerializer):
session_id = serializers.RegexField(regex='^s\d{2}$', allow_blank=False)
def validate_session_id(self, value):
(...)
class Meta:
model = CsdListing
fields = '__all__'
What I'd like to see, is model_id and name from CsdModel displayed inside a form created based on CsdListing model. But instead, the ID is duplicated:
How should I rebuild the model(s) to have both ID and name displayed in the form?
You should have only one foreign key. But the listing serializer should then reference the model as a nested serializer.
class CsdListing(models.Model):
model = models.ForeignKey(CsdModel, on_delete=models.CASCADE, default=0, related_name='listing')
class CsdListingSerializer(serializers.ModelSerializer):
model = CsdModelSerializer()
session_id = serializers.RegexField(regex='^s\d{2}$', allow_blank=False)

select related in Django

I am trying to accomplish something very similar to:
How to join 3 tables in query with Django
Essentially, I have 3 tables. In the Django REST we are showing table 3. As you see below (models.py), table 3 has company_name which is a foreign key of table 2 and table 2 is a foreign key of table 1. Both table 2 and 3 are linked by the table 1 ID. Table 1 contains the actual text, which we want to display in the API output, not the ID number.
Table 1: Manufacturer of Car -- Table 2: What the Car is -- Table 3: list of all cars
Models.py
Table 1:
class ManufacturerName(models.Model):
name_id = models.AutoField(primary_key=True)
company_name = models.CharField(unique=True, max_length=50)
class Meta:
managed = False
db_table = 'manufacturer_name'
Table 2:
class CarBuild(models.Model):
car_id = models.AutoField(primary_key=True)
car_icon = models.CharField(max_length=150, blank=True, null=True)
company_name = models.ForeignKey('ManufacturerName', models.DO_NOTHING, db_column='ManufacturerName')
class Meta:
managed = False
db_table = 'car_build'
Table 3:
class CarList(models.Model):
list_id = models.AutoField(primary_key=True)
company_name = models.ForeignKey('CarBuild', models.DO_NOTHING, db_column='CarBuild')
title = models.CharField(unique=True, max_length=100)
description = models.TextField()
class Meta:
managed = False
db_table = 'cars'
Within my views:
This is what I am trying, based on the foreign key relationships:
queryset = CarList.objects.all().select_related('company_name__company_name')
I get no errors when I save and run this, however, the ID is still being returned, and not the text associated with the foreign key relationships:
[
{
"list_id": 1,
"company_name": "http://127.0.0.1:8000/api/1/",
"title": "Really fast car you're driving, and this is dummy text",
Again, I would like to achieve getting the text associated with the company_name foreign key relationships from table 1 to show in the JSON.
serializer and viewset
class manufacturer_name(serializers.HyperlinkedModelSerializer):
class Meta:
model = manufacturer_name
fields = ('name_id', 'company_name')
class manufacturer_name(viewsets.ModelViewSet):
queryset = manufacturer_namee.objects.all()
serializer_class = manufacturer_name
class CarBuildViewSet(viewsets.ModelViewSet):
queryset = CarBuild.objects.all()
serializer_class = CarBuildSerialiser
class CarBuildSerialiser(serializers.HyperlinkedModelSerializer):
class Meta:
model = CarBuild
fields = ('car_id', 'car_icon', 'company_name')
class CarListSerialiser(serializers.HyperlinkedModelSerializer):
class Meta:
model = News
fields = ('list_id', 'company_name', 'title')
class CarListViewSet(viewsets.ModelViewSet):
serializer_class = CarList
def get_queryset(self):
queryset = News.objects.all().select_related('company_name__company_name')
return queryset
Based on detailed conversation to clear few details. Here is the answer.
You need to make small changes to your models as it was quite confusing to understand what you want to achieve.
Models:
class ManufacturerName(models.Model):
name_id = models.AutoField(primary_key=True)
company_name = models.CharField(unique=True, max_length=50)
class Meta:
managed = False
db_table = 'manufacturer_name'
class CarBuild(models.Model):
car_id = models.AutoField(primary_key=True)
car_icon = models.CharField(max_length=150, blank=True, null=True)
manufacturer = models.ForeignKey(ManufacturerName,on_delete=models.SET_NULL)
class Meta:
managed = False
db_table = 'car_build'
class CarList(models.Model):
list_id = models.AutoField(primary_key=True)
car = models.ForeignKey(CarBuild, on_delete=models.DO_NOTHING)
title = models.CharField(unique=True, max_length=100)
description = models.TextField()
class Meta:
managed = False
db_table = 'cars'
And then You need to adjust your serializers.
class CarListSerialiser(serializers.HyperlinkedModelSerializer):
company_name= serializers.SerializerMethodField(read_only=True)
class Meta:
model = CarList
fields = ('list_id', 'company_name', 'title')
def get_company_name(self, obj):
return obj.car.manufacturer.company_name
And you use it in your view:
class CarListViewSet(viewsets.ModelViewSet):
queryset = CarList.object.all()
serializer_class = CarListSerialiser

Django REST Framework - pass related field in POST data

I'm using Django 2.0 and Django REST Framework
I have a model to store contact's address like
class ContactAddress(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
city = models.CharField(max_length=250)
postal = models.CharField(max_length=6)
state = models.ForeignKey(State, on_delete=models.PROTECT, blank=True)
I have two more models to store state and country
class Country(models.Model):
name = models.CharField(max_length=100, unique=True, verbose_name='Country Name')
class State(models.Model):
country = models.ForeignKey(Country, on_delete=models.PROTECT)
name = models.CharField(max_length=100, verbose_name='State Name')
I want to pass state field with POST data while creating a new address record
app/serializers.py
class ContactAddressSerializer(serializers.ModelSerializer):
class Meta:
model = ContactAddress
depth = 2
fields = (
'id', 'city', 'postal', 'state'
)
def create(self, validated_data):
print(validated_data)
and my POST data
[
{
"city": "City Name",
"postal": "110011",
"state": "Bihar"
}
]
But there is no state data in validated data. Printing validate_data return
def create(self, validated_data):
print(validated_data)
{'city': 'City Name', 'postal': '110011', 'contact': <Contact: contact_object>}
How can I pass POST data for related field?
state property has foreign key to another model. If you want to use state property in serializer class, cannot behave like simple property
First, write a serializer for state model, then use this serializer class in contact address class
class StateSerializer(serializers.ModelSerializer):
class meta:
Fields = ('name')
class ContactAddressSerializer(serializers.ModelSerializer):
state = StateSerializer()
class Meta:
model = ContactAddress
depth = 2
fields = (
'id', 'city', 'postal', 'state'
)
def create(self, validated_data):
print(validated_data)