I have models as below:
model: Business
name
model: Employee
business=FK(Business),
user = FK(User)
model: Item
title
units
model: PurchaseInvoice
business=FK(Business)
employee = FK(Employee)
invoice_no
date
model: PurchasedItems
parent_invoice=FK(PurchaseInvoice, related_name="purchased_items")
item = FK(Item)
quantity
rate
and serializers.py:
class PurchasedItemPOSTSerializer(serializers.ModelSerializer):
class Meta:
model = PurchasedItem
fields = ('item', 'quantity', 'rate')
def validate_quantity(self, value):
if value==0 or value is None:
raise serializers.ValidationError("Invalid quantity")
return value
def validate_rate(self, value):
if value==0 or value is None:
raise serializers.ValidationError("rate?? Did you get it for free?")
return value
class PurchaseInvoicePOSTSerializer(serializers.ModelSerializer):
purchased_items = PurchasedItemPOSTSerializer(many=True)
class Meta:
model = PurchaseInvoice
fields = ('invoice_no','date','purchased_items')
def validate_purchased_items(self, value):
if len(value)==0:
raise serializers.ValidationError("atleast one item is required.")
return value
#transaction.atomic
def create(self, validated_data):
purchased_items_data = validated_data.pop('purchased_items')
purchase_invoice = self.Meta.model.objects.create(**validated_data)
for purchased_item in purchased_items_data:
PurchasedItem.objects.create(purchase_invoice=purchase_invoice, **purchased_item)
return invoice
and views.py
class PurchaseInvoiceViewSet(viewsets.ModelViewSet):
serializer_class = PurchaseInvoicePOSTSerializer
def get_serializer_context(self):
return {'user': self.request.user, 'business_id': self.kwargs['business_id']}
def get_queryset(self, **kwargs):
return PurchaseInvoice.objects.filter(business__id=self.kwargs['business_id'])
def perform_create(self, serializer):
business = Business.objects.get(pk=self.kwargs['business_id'])
employee = get_object_or_404(Employee, business=business, user=self.request.user)
serializer.save(business=business, employee=employee)
The data sent through POST is:
{
"invoice_no": "123",
"date": "2018-07-13",
"purchased_items": [
{"item":1, "quantity":0, "rate":0}],
}
As you can see from the data above no error is raised by validate_quantity when quantity=0 and neither by validate_rate when rate=0 for the item in purchased_items.
You can add custom validate in your serializer. For example:
from rest_framework import serializers
class PurchasedItemPOSTSerializer(serializers.ModelSerializer):
class Meta:
model = PurchasedItem
fields = ('item', 'quantity', 'rate')
def validate(self, data):
# custom anything validate
quantity = data.get('quantity')
# recheck validate in this
if not quantity:
raise serializers.ValidationError('not valid')
return data
Related
I have a model like this:
class Camper(models.Model):
location = models.PointField()
name = models.CharField(max_length=255)
and a viewset like this:
class CamperViewSet(viewsets.ModelViewSet):
...
def retrieve(self, request, *args, **kwargs):
"""Retrieve a Camper instance."""
show_weather = request.query_params.get('showWeather', False)
instance = self.get_object()
if show_weather:
lat = instance.location.y
lon = instance.location.x
instance.weather = getWeatherFromLatLon(lat, lon)
serializer = self.get_serializer(instance)
return Response(serializer.data)
So when I request /api/campers/8?showWeather=true I make another request in my view to get the weather from the current position.
How do I add it to my serializer ? It's an optional field so I need to manage this and it's only used in /campers/id so it will not be used in list/create/put/etc
My serializer looks like this:
class CamperSerializer(serializers.ModelSerializer):
camper_id = serializers.IntegerField(source='id')
class Meta:
model = Camper
fields = ('camper_id', 'name', 'location')
you can add custom serializer for retrive only todo it. I called CamperRetriveSerializer.
Inside CamperRetriveSerializer, you can use SerializerMethodField for define field not have in database.
And you want check param show_weather from request, best is pass value of it to context and get it in serializer.
Like this:
class CamperRetriveSerializer(serializers.ModelSerializer):
weather = serializers.SerializerMethodField()
camper_id = serializers.IntegerField(source='id')
def get_weather(self, obj):
show_weather = self.context.get('show_weather')
if show_weather:
lat = obj.location.y
lon = obj.location.x
return getWeatherFromLatLon(lat, lon)
# define default value if not show_weather in this
return ''
class Meta:
model = Camper
fields = ('camper_id', 'name', 'location', 'weather')
class CamperViewSet(viewsets.ModelViewSet):
...
def retrieve(self, request, *args, **kwargs):
"""Retrieve a Camper instance."""
instance = self.get_object()
show_weather = self.request.query_params.get('showWeather', False)
context = {
'show_weather': show_weather
}
serializer = CamperRetriveSerializer(instance, context=context)
return Response(serializer.data)
You can use two different serializers for this.
class CamperViewSet(viewsets.ModelViewSet):
serializer_class = CamperSerializer
def get_serializer_class(self):
serializer_class = self.serialzier_class
if self.request.method == 'GET':
serializer_class = CamperSerializerGet
return serializer_class
#Serializer for GET request
class CamperSerializerGet(serializers.ModelSerializer):
weather = serialziers.SerializerMethodField()
camper_id = serializers.IntegerField(source='id')
def get_weather(self, obj):
return obj.weather
class Meta:
model = Camper
fields = ('camper_id', 'name', 'location', 'weather')
#For other requests call this
class CamperSerializer(serializers.ModelSerializer):
camper_id = serializers.IntegerField(source='id')
class Meta:
model = Camper
fields = ('camper_id', 'name', 'location')
I have written a search api for my website in django rest framework.
when you search a name (e.g. "jumanji") there might be more than one result for the search for many reasons. what I want is for the result to be ordered by the "rating" field or "releaseDate" field of the Film model.
here are my codes.
# models.py
class Film(models.Model):
filmID = models.AutoField(primary_key=True)
title = models.CharField(max_length=150)
duration = models.PositiveIntegerField()
typeOf = models.IntegerField(validators=[MaxValueValidator(3), MinValueValidator(1),])
rating = models.FloatField(default=0, validators=[MaxValueValidator(10), MinValueValidator(0),])
releaseDate = models.DateTimeField(null=True)
# serializers.py
class FilmSerializer(serializers.ModelSerializer):
class Meta:
model = Film
fields = [
"filmID", "title", "price", "duration", "typeOf", "numberOfFilminoRatings", "filminoRating", "rating",
"releaseDate", "detailsEn", "salePercentage", "saleExpiration", "posterURL", "posterDirectory",
]
# views.py
'''Override get_search_fields method of SearchFilter'''
class DynamicSearch(filters.SearchFilter,):
def get_search_fields(self,view, request):
return request.GET.getlist('search_fields',[])
'''Override page_size_query_param attribute of PageNumberPagination'''
class CustomizePagination(PageNumberPagination):
page_size_query_param = 'limit'
"""Pagination Handler"""
class PaginationHanlerMixin(object):
#property
def paginator(self):
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator =None
else :
self._paginator = self.pagination_class()
else :
pass
return self._paginator
def paginate_queryset(self,queryset):
if self.paginator is None:
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self,data):
if self.paginator is None:
raise "Paginator is None"
return self.paginator.get_paginated_response(data)
class SearchFilm(APIView,PaginationHanlerMixin):
authentication_classes = ()
permission_classes = (AllowAny,)
def __init__(self,):
APIView.__init__(self)
self.search_class=DynamicSearch
self.pagination_class=CustomizePagination
def filter_queryset(self,queryset):
filterd_queryset=self.search_class().filter_queryset(self.request, queryset ,self)
return filterd_queryset
def get(self, request):
films= Film.objects.all()
filtered_queryset=self.filter_queryset(films)
#Get appropriate results for each page
results=self.paginate_queryset(filtered_queryset)
if(results is not None):
serializer=FilmSerializer(results,many=True)
serializer=self.get_paginated_response(serializer.data)
else :
serializer=FilmSerializer(filtered_queryset,many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
It as simple as using function order_by to QuerySet of your films. If you give few options, it will be firstfully ordered by leftmost, then second to the left etc.
films = Film.objects.all().order_by('rating', 'releaseDate')
I'm using Django 2 and Python 3.7. I have these models set up. One (Coop) is dependent on the other (CoopType) using Many-To-Many ...
class CoopTypeManager(models.Manager):
def get_by_natural_key(self, name):
return self.get_or_create(name=name)[0]
class CoopType(models.Model):
name = models.CharField(max_length=200, null=False, unique=True)
objects = CoopTypeManager()
class CoopManager(models.Manager):
# Look up by coop type
def get_by_type(self, type):
qset = Coop.objects.filter(type__name=type,
enabled=True)
return qset
# Look up coops by a partial name (case insensitive)
def find_by_name(self, partial_name):
queryset = Coop.objects.filter(name__icontains=partial_name, enabled=True)
print(queryset.query)
return queryset
# Meant to look up coops case-insensitively by part of a type
def contains_type(self, types_arr):
filter = Q(
*[('type__name__icontains', type) for type in types_arr],
_connector=Q.OR
)
queryset = Coop.objects.filter(filter,
enabled=True)
return queryset
class Coop(models.Model):
objects = CoopManager()
name = models.CharField(max_length=250, null=False)
types = models.ManyToManyField(CoopType)
address = AddressField(on_delete=models.CASCADE)
enabled = models.BooleanField(default=True, null=False)
phone = PhoneNumberField(null=True)
email = models.EmailField(null=True)
web_site = models.TextField()
I have the following serializers set up, designed to return the data in JSON form ...
class CoopTypeField(serializers.PrimaryKeyRelatedField):
queryset = CoopType.objects
def to_internal_value(self, data):
if type(data) == dict:
cooptype, created = CoopType.objects.get_or_create(**data)
# Replace the dict with the ID of the newly obtained object
data = cooptype.pk
return super().to_internal_value(data)
...
class CoopTypeSerializer(serializers.ModelSerializer):
class Meta:
model = CoopType
fields = ['id', 'name']
def create(self, validated_data):
"""
Create and return a new `CoopType` instance, given the validated data.
"""
return CoopType.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `CoopType` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class CoopSerializer(serializers.ModelSerializer):
types = CoopTypeSerializer(many=True)
address = AddressTypeField()
class Meta:
model = Coop
fields = ['id', 'name', 'types', 'address', 'phone', 'enabled', 'email', 'web_site']
extra_kwargs = {
'phone': {
'required': False,
'allow_blank': True
}
}
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types).data
rep['address'] = AddressSerializer(instance.address).data
return rep
def create(self, validated_data):
#"""
#Create and return a new `Snippet` instance, given the validated data.
coop_types = validated_data.pop('types', {})
instance = super().create(validated_data)
for item in coop_types:
coop_type, _ = CoopType.objects.get_or_create(name=item['name']) #**item)
instance.types.add(coop_type)
return instance
However, the dependent field, "type" is always returned as "null," despite the fact that I can see there is valid data in the database. Here is what happens when I run my curl request
curl -v --header "Content-type: application/json" --request GET "http://127.0.0.1:8000/coops/?contains=resource"
[{"id":348,"name":"Garden Resources of Woodlawn (GRoW)","types":{"name":null}
How do I edit my serializer such that it returns the values of the dependent type?
Try to remove rep['types'] = CoopTypeSerializer(instance.types).data from to_representation(...) method,
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types).data
rep['address'] = AddressSerializer(instance.address).data
return rep
OR
use instance.types.all() instead of instance.types, because, here the instance.types is a Manager method, which doesn't return any QuerySet
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['types'] = CoopTypeSerializer(instance.types.all(), many=True).data
rep['address'] = AddressSerializer(instance.address).data
return rep
I want to change ManyToManyField Name. user_groups is manytomanyfield. I tried to use ManyToManyRelatedField and also PrimaryKeyRelatedField but it is giving error. How can i change or with data type should i give like for character field i am giving CharField
class EmployeeProfileSerializer(serializers.ModelSerializer):
employee_id = serializers.CharField(source='user_employee_id')
payroll_id = serializers.CharField(source='user_payroll_id')
phone = serializers.CharField(source='user_phone')
hire_date = serializers.DateField(source='user_hire_date')
pay_rate = serializers.IntegerField(source='user_pay_rate')
salaried = serializers.CharField(source='user_salaried')
excempt = serializers.CharField(source='user_excempt')
state = serializers.CharField(source='user_state')
city = serializers.CharField(source='user_city')
zipcode = serializers.IntegerField(source='user_zipcode')
status = serializers.CharField(source='user_status')
class Meta:
model = UserProfile
fields = [
'employee_id',
'phone',
'payroll_id',
'hire_date',
'pay_rate',
'salaried',
'excempt',
'state',
'city',
'zipcode',
'status',
'user_groups',
]
You can use SerializerMethodField:
class EmployeeProfileSerializer(serializers.ModelSerializer):
...
your_custom_name = SerializerMethodField()
class Meta:
model = UserProfile
fields = ['your_custom_name ', ...]
def get_your_custom_name(self, obj):
# Return ids:
return list(obj.user_groups.all().values_list('pk', flat=True))
# Or using a serializer:
return MyUserGroupSerializer(obj.user_groups.all(), many=True).data
For create and update you have to override the create and update method to assign the new field:
class EmployeeProfileSerializer(serializers.ModelSerializer):
...
your_custom_name = IntegerField()
class Meta:
model = UserProfile
fields = ['your_custom_name', ...]
# If you need validation
def validate_your_custom_name(self, value):
if value:
if int(value) > 5:
return value
return None
def create(self, validated_data):
# Get the data for your new field
my_costum_data = validated_data.get('your_custom_name')
# Do something with it
profile_obj = UserProfile.objects.create(...)
if my_costum_data:
user_group = UserGroupModel.objects.get(pk=int(my_costum_data))
profile_obj.user_groups.add(user_group)
def update(self, instance, validated_data):
# Same as create()
...
I have the following view
class ProductUpdateView(BaseProductUpdateView):
form_class = ProductForm
slug_field = "id"
def form_valid(self, form):
form.instance.hotel = form.cleaned_data["hotel"]
form.instance.parent = form.cleaned_data["parent"]
return super(ProductUpdateView, self).form_valid(form)
and following form:
class ProductForm(BaseProductForm):
hotel = forms.ModelChoiceField(queryset=Hotel.objects.all(), widget=forms.TextInput)
parent = forms.ModelChoiceField(queryset=Product.objects.all(), widget=forms.TextInput, required=False)
class Meta:
model = Product
exclude = ('slug', 'parent', 'hotel', 'status', 'score', 'product_class',
'recommended_products', 'related_products',
'product_options', 'attributes', 'categories')
When I save the form, form saved successfully. I can see the saved values of hotel and parent in admin, but when I re-open the update form page, other fields returns but hotel and parent fields return blank. Any ideas?
I handled it like below
views.py
class ProductUpdateView(BaseProductUpdateView):
form_class = ProductForm
slug_field = "id"
def get_form_kwargs(self, **kwargs):
kwargs = super(ProductUpdateView, self).get_form_kwargs(**kwargs)
kwargs['initial']['hotel'] = self.get_object().hotel.id if \
self.get_object().hotel else None
kwargs['initial']['parent'] = self.get_object().parent.id if \
self.get_object().parent else None
return kwargs
def form_valid(self, form):
form.instance.hotel = form.cleaned_data["hotel"]
form.instance.parent = form.cleaned_data["parent"]
return super(ProductUpdateView, self).form_valid(form)
forms.py
class AutoCompleteModelChoiceField(forms.ModelChoiceField):
widget = forms.TextInput
def clean(self, value):
value = super(AutoCompleteModelChoiceField, self).clean(value)
return value
class ProductForm(BaseProductForm):
hotel = AutoCompleteModelChoiceField(queryset=Hotel.objects.all())
parent = AutoCompleteModelChoiceField(queryset=Product.objects.all(),
required=False)