Currently, I've the next models:
class Product(models.Model):
name = models.CharField('name', max_length=60)
class Variant(models.Model):
name = models.CharField('name', max_length=60)
class VariantValue(models.Model):
value = models.CharField('value', max_length=60)
variant = models.ForeigKey('variant', to=Variant, on_delete=models.CASCADE)
product = models.ForeigKey('product', to=Product, on_delete=models.CASCADE)
I want to get the next json result:
{
"name": "My product",
"variants": [
{
"variant_id": 1,
"values": ["Value 1", "Value 2", "Value 3"]
},
{
"variant_id": 2,
"values": ["Value 4", "Value 5", "Value 6"]
},
]
}
It's possible with a Serializer or do I've to make the json manually?
I only have the next ModelSerializer:
class ProductModelSerializer(serializers.ModelSerializer):
variant_list = serializers.SerializerMethodField('get_variant_list', read_only=True)
class Meta:
model = Product
fields = [..., 'variant_list']
def get_variant_list(self, obj):
variant_list = VariantValue.objects.filter(product_id=obj.id)
variant_list_grouped = variant_list.values('variant', 'value').annotate(count=Count('variant'))
res = []
for variant in variant_list_grouped:
pass
return []
Thanks!
class ProductModelSerializer(serializers.ModelSerializer):
variant_list = serializers.SerializerMethodField('get_variant_list', read_only=True)
class Meta:
model = Product
fields = [..., 'variant_list']
def get_variant_list(self, obj):
variant_list = VariantValue.objects.filter(product_id=obj.id)
return [{"variant_id": variant.variant_id, "values": VariantValue.objects.filter(variant_id = variant.variant_id).values("values", flat=True)} for variant in variant_list]
Using Krishna Singhal answer as a basis I achieved the following script
def get_variant_list(self, product):
variant_list = VariantValue.objects.filter(product_id=obj.id)
variant_list_grouped = variant_list.values('variant',).annotate(count=Count('variant'))
return [{"variant": variant['variant'], "values": VariantValue.objects.filter(
product_id=obj.id, variant_id=variant['variant']).distinct('value').values_list('value', flat=True)} for variant in variant_list_grouped]
I get the next json:
{
...,
"variantList": [
{
"variant": 1,
"values": [
"Value 1",
"Value 2"
]
},
{
"variant": 3,
"values": [
"Black",
"Red",
"Green"
]
},
{
"variant": 2,
"values": [
"S",
"M",
"G"
]
}
],
}
Create a new serializer for VariantValue and nest it thats it. eg:
# serializer
class VariantValueSerializer(serializers.ModelSerializer):
variant_id = serializer.IntegerField(source="id", read_only=True)
class Meta:
model = VariantValue
fields = ["variant_id", "values"]
class ProductModelSerializer(serializers.ModelSerializer):
variant_list = VariantValueSerializer(source="variantvalue_set", many=True, read_only=True)
class Meta:
model = Product
fields = [..., 'variant_list']
you dont have to manually format serializers are there for it.
Related
Country table that is grandparent table
class Country(models.Model):
country_id = models.IntegerField(primary_key=True)
cname = models.CharField(max_length=100)
def __str__(self):
return self.name
State table which is a direct child of the country
class State(models.Model):
stateid = models.IntegerField(primary_key=True)
state_name= models.CharField(max_length=100)
s_country = models.ForeignKey(Country, related_name='scountry', on_delete =models.CASCADE)
def __str__(self):
return self.state_name
City table which is a direct child of State and indirect child of Country
class City(models.Model):
cityid= models.IntegerField(primary_key=True)
city_name = models.CharField(max_length=100)
city_strength = models.IntegerField()
city_state = models.ForeignKey(State, related_name='cstate', on_delete =models.CASCADE)
def __str__(self):
return self.sname
Serializers
class StateSerializer(serialiizer.ModelSerializer):
class Meta:
model = State
fields = '__all__'
class CitySerializer(serialiizer.ModelSerializer):
class Meta:
model = City
fields = '__all__'
class FliterStateCitySerializer(serializers.ModelSerializer):
state = StateSerializer(many=true)
cities = CitySerializer(many=true)
class Meta:
model = Country
fields = ('id', 'state', 'city')
Views
class FilterStateCityView(generics.ListAPIView):
queryset = Country.objects.all()
serializer_class = FliterStateCitySerializer
The response I am getting is.
[
{
"country_id":"23",
"cname": "USA",
"state" : {
"stateid": "16",
"state_name": "Florida",
"cities" : {
"cityid":"55",
"city_name": "Miami",
"city_strength ": 40005
},
{
"cityid":"56",
"city_name": "Orlando",
"city_strength ": 930067
},
},
{
"stateid": "17",
"state_name": "Texas",
"s_country" : "USA"
},
}
]
Response that i want...
[
{
"country_id":"23",
"stateid": "16",
"cityid":"55",
"state_name": "Florida",
"city_name": "Miami",
"city_strength ": 40005
},
{
"country_id":"23",
"stateid": "16",
"cityid":"56",
"state_name": "Florida",
"city_name": "Orlando",
"city_strength ": 930067
},
{
"stateid": "17",
"state_name": "Texas",
"s_country" : "USA"
},
]
My question is I want to get all states on the basis of country id along with relevant cities in single list as a one-by-one object.
You can use StringRelatedField and PriamryKeyRealtedField to achieve what you are trying to do. You can use source attributes to define the data you want.
for e.g.
city_name = serializers.StringRealtedField(source='self.scountry.cstate.city_name')
state_id = serializers.PrimaryKeyRealtedField(source="self.scountry.stateid").
I hope this helps and solves your problem.
How do I update data in a linked django rest model
Here is my json
{
"doc_type": 1,
"warehouse": 4,
"date": "2022-06-09",
"number": 980,
"contragent": 2,
"comment": "testcom;mgment",
"items": [
{
"product": 7,
"buy_price": "168.00",
"sell_price": "500.00",
"quantity": 17
},
{
"product": 8,
"buy_price": "168.00",
"sell_price": "500.00",
"quantity": 17
}
]
}
I can't change the data in the "items" dictionary
If I submit only one "product" element, will the other one be deleted? And if, for example, three, will it be added?
this is what I have at the moment, records are created without problems, but I can only update the "ConsignmentNote" model
#serialisers.py
class ConsignmentNoteSerializer(serializers.ModelSerializer):
creator = serializers.HiddenField(default=serializers.CurrentUserDefault())
doc_type = serializers.IntegerField(read_only=True)
# contragent_detail = ContragentSerializer(source='contragent', read_only=True)
items = ConsignmentItemSerializer(many=True)
class Meta:
model = ConsignmentNote
fields = ['doc_type', "warehouse", 'date', 'number', 'contragent', 'comment', 'creator', 'items']
read_only_fields = ['id' ]
def create(self, validated_data):
items = validated_data.pop('items')
note = ConsignmentNote.objects.create(**validated_data, doc_type = 1)
for item in items:
product = item.pop('product')
item = ConsignmentItem.objects.create(consignmentnote=note, product=product ,**item)
return note
def update(self, instance, validated_data):
instance.date = validated_data.pop('date', instance.date)
instance.comment = validated_data.pop('comment', instance.comment)
return instance
I'm hoping someone might be able to help me. I am working with Django Rest Framework, and attempting to create an API that allows users to search for Providers that provide specific Procedures in particular Regions, and only return the relevant details.
Set up
I have these models (heavily simplified):
# models.py
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
class Region(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ["name"]
class Procedure(models.Model):
description = models.CharField(max_length=100)
class Meta:
ordering = ["description"]
class Provider(models.Model):
provider_name = models.CharField(max_length=200)
class Meta:
ordering = ["provider_name"]
class Provision(models.Model):
fk_procedure = models.ForeignKey(
Procedure,
related_name="providers",
on_delete=models.RESTRICT,
)
fk_provider = models.ForeignKey(
Provider,
related_name="services",
on_delete=models.CASCADE,
)
discount = models.FloatField(
validators=[MaxValueValidator(100), MinValueValidator(0)],
default=0,
)
class Meta:
ordering = ["-discount"]
unique_together = ["fk_procedure", "fk_provider"]
class ProvisionLinkRegion(models.Model):
fk_provision = models.ForeignKey(
Provision,
related_name="regions",
on_delete=models.CASCADE,
)
fk_region = models.ForeignKey(
Region,
related_name="services",
on_delete=models.RESTRICT,
)
location = models.BooleanField(default=False)
As you can see, there is a ManyToMany link between Provision and Region via ProvisionLinkRegion. I haven't defined this as a ManyToMany field though, as I need to store additional details (location) about the pairing.
I have defined the following serializers on these models:
# serializers.py
from rest_framework import serializers
from models import (
Provider,
Region,
Procedure,
ProvisionLinkRegion,
Provision,
)
class ProvisionLinkRegionSerializer(serializers.ModelSerializer):
class Meta:
model = ProvisionLinkRegion
fields = ["fk_region", "location"]
class ProvisionDetailsSerializer(serializers.ModelSerializer):
regions = ProvisionLinkRegionSerializer(many=True)
class Meta:
model = Provision
fields = ["fk_procedure", "discount", "mff_opt_out", "regions"]
class ProviderProvisionSerializer(serializers.ModelSerializer):
services = ProvisionDetailsSerializer(many=True)
number_services = serializers.IntegerField()
class Meta:
model = Provider
fields = [
"provider_name",
"services",
"number_services",
]
And have defined my API like this:
# api.py
from django.db.models import Prefetch, Count
from rest_framework import generics, pagination, permissions, status
from rest_framework.response import Response
from serializers import ProviderProvisionSerializer
from models import (
Provider,
ProvisionLinkRegion,
Provision,
)
class CustomPagination(pagination.PageNumberPagination):
page_size_query_param = "limit"
def get_paginated_response(self, data):
return Response(
{
"pagination": {
"previous": self.get_previous_link(),
"next": self.get_next_link(),
"count": self.page.paginator.count,
"current_page": self.page.number,
"total_pages": self.page.paginator.num_pages,
"items_on_page": len(data),
},
"results": data,
}
)
class ProvisionListAPI(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ProviderProvisionSerializer
pagination_class = CustomPagination
def get_queryset(self):
queryset = Provider.objects.distinct()
# Extract the query parameters
params = self.request.query_params
region_list = params["region"].split(",")
procedure_list = param["procedure"].split(",")
# Build up the prefetch Provision table filtering on the regions
# and services
services_prefetch_qs = (
Provision.objects.distinct()
.filter(regions__fk_region__in=region_list)
.filter(fk_procedure__in=procedure_list)
.prefetch_related(
Prefetch(
"regions",
queryset=ProvisionLinkRegion.objects.filter(
fk_region__in=region_list
),
)
)
)
# Apply the filters and prefetch required tables
queryset = queryset.filter(
services__regions__fk_region__in=region_list
).prefetch_related(
Prefetch("services", queryset=services_prefetch_qs),
)
# Add the ordering parameters
queryset = (
queryset.annotate(
number_services=Count("services", distinct=True) # FIXME
)
.filter(number_services__gt=0)
.order_by("-number_services")
)
return queryset.all()
def list(self, response):
# Check it has the right headers
params = self.request.query_params
if "procedure" not in params:
return Response(
{"detail": "procedure not provided as a query parameter"},
status.HTTP_400_BAD_REQUEST,
)
if "region" not in params:
return Response(
{"detail": "region not provided as a query parameter"},
status.HTTP_400_BAD_REQUEST,
)
# Paginate and filter queryset
queryset = self.get_queryset()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
My problem is, the number_services value is not correct, as it's not doing the count on the full filtered results. It's only doing it on the pre-filtered ones (although the region filter does work). I also don't want any providers to appear when they don't have any services (hence the .filter(number_services__gt=0)).
I think that it's related to not filtering on the main Provider queryset like I do with the region i.e. to include:
queryset = queryset.filter(services__fk_procedure__in=procedure_list)
But when I include this, it doesn't actually remove the services, but just the providers that don't provide ANY of those services, so the count is still off.
Example
If my data with no filtering or prefetching looks like this:
"results": [
{
"provider_name": "Provider 2.0",
"services": [
{
"fk_procedure": 3,
"discount": 0.05,
"regions": [
{
"fk_region": 1,
"location": true
},
{
"fk_region": 2,
"location": false
}
{
"fk_region": 3,
"location": true
}
]
},
{
"fk_procedure": 5,
"discount": 0.05,
"regions": [
{
"fk_region": 1,
"location": true
}
]
}
]
},
{
"provider_name": "Test Provider",
"services": [
{
"fk_procedure": 2,
"discount": 0.00,
"regions": [
{
"fk_region": 1,
"location": true
}
]
}
]
}
]
If I then run this on it:
GET /api/v1/provision?page=1&limit=10®ion=1,3&services=3`
I want to show all the providers and the relevant details where they relate to either region 1 or 3, and procedure 3.
Actual Result
{
"pagination": {
"previous": null,
"next": null,
"count": 2,
"current_page": 1,
"total_pages": 1,
"items_on_page": 2
},
"results": [
{
"provider_name": "Provider 2.0",
"services": [
{
"fk_procedure": 3,
"discount": 0.05,
"regions": [
{
"fk_region": 1,
"location": true
},
{
"fk_region": 3,
"location": true
}
]
}
],
"number_services": 2
},
{
"provider_name": "Test Provider",
"services": [],
"number_services": 1
}
]
}
Desired Result
{
"pagination": {
"previous": null,
"next": null,
"count": 2,
"current_page": 1,
"total_pages": 1,
"items_on_page": 2
},
"results": [
{
"provider_name": "Provider 2.0",
"services": [
{
"fk_procedure": 3,
"discount": 0.05,
"regions": [
{
"fk_region": 1,
"location": true
},
{
"fk_region": 3,
"location": true
}
]
}
],
"number_services": 1,
}
]
}
Things I've tried
SerializerMethodField
I've been able to get number_services to work using a SerializerMethodField by including:
# serializers.py
class ProviderProvisionSerializer(serializers.ModelSerializer):
...
number_services = serializers.SerializerMethodField()
...
class Meta:
...
def get_number_services(self, obj):
return obj.services.count()
Unfortunately though, I can't use this for ordering, or filtering within the API, and I also can't use it with pagination either, so is pretty useless for what I need it for.
Subquery
In the get_queryset method for the API, I've also tried using what I currently have as a subquery, and using the main queryset where the ID is in the other one, but then I lose all the prefetch from the first subquery, and have regions and services that don't relate to my filter.
## TLDR
How do I filter a queryset in a get_queryset method for a ListAPIView on properties of the children of the main model, and be able to return a count of the remaining children after the filter has taken place?
I have a project with Django Rest Framework and I have a complex ViewSet that uses several models and serializers to compound a large and complex json. Everything works fine, but I notice that HotelSerializer, that is a ModelSerializer, is returning the stored value of field category, instead of the human readable value of its model choices.
This is the model:
class Hotel(models.Model):
ONE_STAR = '*'
TWO_STARS = '**'
THREE_STARS = '***'
FOUR_STARS = '****'
FIVE_STARS = '*****'
GRAND_TOURISM = 'GRAND_TOURISM'
NA = 'NA'
SPECIAL = 'SPECIAL'
ECO = 'ECO'
BOUTIQUE = 'BOUTIQUE'
HOTEL_CATEGORY_CHOICES = (
(ONE_STAR, _('*')),
(TWO_STARS, _('**')),
(THREE_STARS, _('***')),
(FOUR_STARS, _('****')),
(FIVE_STARS, _('*****')),
(GRAND_TOURISM, _('Grand Tourism')),
(NA, _('NA')),
(SPECIAL, _('Special')),
(ECO, _('Eco-Hotel')),
(BOUTIQUE, _('Boutique-Hotel'))
)
company = models.OneToOneField(Company, on_delete=models.CASCADE, primary_key=True, verbose_name=_('Company'))
code = models.CharField(max_length=10, verbose_name=_('Code'))
zone = models.ForeignKey(Zone, on_delete=models.PROTECT, related_name='hotels', verbose_name=_('Zone'))
category = models.CharField(max_length=20, choices=HOTEL_CATEGORY_CHOICES, verbose_name=_('Category'))
capacity = models.IntegerField(verbose_name=_('Capacity'))
position = models.DecimalField(max_digits=11, decimal_places=2, default=0.00, verbose_name=_('Position'))
in_pickup = models.BooleanField(default=False, verbose_name=_('In pickup?'))
is_active = models.BooleanField(default=True, verbose_name=_('Is active?'))
latitude = models.FloatField(null=True, blank=True, verbose_name=_('Latitude'))
longitude = models.FloatField(null=True, blank=True, verbose_name=_('Longitude'))
This is the serializer:
class HotelSerializer(serializers.ModelSerializer):
category = serializers.ChoiceField(choices=models.Hotel.HOTEL_CATEGORY_CHOICES)
class Meta:
model = models.Hotel
fields = ('company', 'code', 'zone', 'category', 'capacity', 'position', 'in_pickup', 'is_active', 'latitude', 'longitude')
depth = 4
def __init__(self, *args, **kwargs):
exclude = kwargs.pop('exclude', None)
super(HotelSerializer, self).__init__(*args, **kwargs)
if exclude is not None:
for field_name in exclude:
self.fields.pop(field_name)
This is the ViewSet function where I get the Hotel model information:
def get_hotel(self, company_id):
hotel = Hotel.objects.get(company=company_id)
import ReservationsManagerApp.serializers
return ReservationsManagerApp.serializers.HotelSerializer(hotel, exclude=('company',)).data
And this is the result I get:
"hotel": {
"code": "xxxx...",
"zone": {
"id": 1,
"name": "Zona hotelera",
"city": {
"id": 5,
"name": "Cancun",
"code": "998",
"state": {
"id": 2,
"name": "Quintana Roo",
"code": "98",
"country": {
"id": 1,
"name": "Mexico",
"code": "MX",
"calling_code": "52"
}
}
}
},
"category": "GRAND_TOURISM",
"capacity": 300,
"position": "1.00",
"in_pickup": true,
"is_active": true,
"latitude": null,
"longitude": null
},
You can see that the Category field returns the value 'GRAND_TOURISM', instead of its human readable version of its choices: 'Grand Tourism'. It even misses the translation, so it must return 'Gran Turismo'.
I don't know what I am missing.
This is correct behavior AFAIK, but it is easy to fix.
One solution is to just do
class HotelSerializer(serializers.ModelSerializer):
category = serializers.ChoiceField(choices=Hotel.HOTEL_CATEGORY_CHOICES)
company = CompanySerializer()
...
i am new to DJango Python webservices and i am facing small issue with the serializers.here is my code.
**views.py**
#csrf_exempt
#api_view(['GET'])
def sqlservice(request):
if request.method == 'GET':
posts = tbl_dcs.objects.all()
for each in posts:
manid = each.managerid_id
manname = Managerid.objects.get(id = manid)
nameser = managerSerializer(manname,many=False)
print nameser.data
serializer = PostSerializer(posts, many=True)
return Response({"resource":serializer.data})
**models.py**
from __future__ import unicode_literals
from django.db import models
class Managerid(models.Model):
managername = models.CharField(max_length=25)
def __unicode__(self):
return self.managername
class tbl_dcs(models.Model):
name = models.CharField(max_length=25)
location = models.CharField(max_length=50)
address = models.TextField()
managerid = models.ForeignKey(Managerid,related_name='items')
phonenumber = models.IntegerField()
def __unicode__(self):
return self.name
**serilizer.py**
from rest_framework import serializers
from mysqlservice.models import tbl_dcs
from mysqlservice.models import Managerid
class managerSerializer(serializers.ModelSerializer):
class Meta:
model = Managerid
fields = ('id', 'managername')
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = tbl_dcs
fields = ('id', 'name','location','address','managerid','phonenumber')
if i run the above code the result is...
{
"resource": [
{
"id": 1,
"name": "ramesh",
"location": "hyd",
"address": "kphb,hyd",
"managerid": 10,
"phonenumber": 345345
},
{
"id": 2,
"name": "kpti",
"location": "kphb",
"address": "kphb,6th phase,hyd",
"managerid": 10,
"phonenumber": 45456
}
]
}
my resulting json i am unable to get the managername.How to get the managername based on managerid from the different table?.Please correct me if i am wrong anywhere.
my final json should be like below...
{
"resource": [
{
"id": 1,
"name": "ramesh",
"location": "hyd",
"address": "kphb,hyd",
"managerid": 10,
"phonenumber": 345345,
"managername":"xxxxx"
},
{
"id": 2,
"name": "kpti",
"location": "kphb",
"address": "kphb,6th phase,hyd",
"managerid": 10,
"phonenumber": 45456,
"managername":"yyyyy"
}
]
}
use serializers.SerializerMethodField to get manager name like below..
class PostSerializer(serializers.ModelSerializer):
managername = serializers.SerializerMethodField('get_manager_name')
class Meta:
model = tbl_dcs
fields = ('id', 'name','location','address','managerid','phonenumber','managername')
def get_manager_name(self, obj):
return obj.managerid.managername