I want to Translate the variables "name" and "description" of my CategorySerializer, when serializing.
from rest_framework import serializers
from api.models import Category
from django.utils.translation import ugettext_lazy as _
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'name', 'image', 'description')
Is the serializer method field a good aproach?
PD: this names are translated on the django.po.
Yes, simply define a SerializerMethodField and return the translation on the fly. The following example changes the field 'description' to 'translation':
class CategorySerializer(serializers.ModelSerializer):
translation = SerializerMethodField('get_description_string')
class Meta:
model = Category
fields = ('id','translation',)
def get_description_string(self,obj):
return obj.description
serializers.py
from rest_framework import serializers
from api.models import Category
from django.utils.translation import ugettext_lazy as _
class CategorySerializer(serializers.ModelSerializer):
name_ = serializers.ReadOnlyField(source='get_name')
description_ = serializers.ReadOnlyField(source='get_description')
class Meta:
model = Category
fields = ('id', 'name', 'image', 'description')
def get_name(self):
return _(self.name)
def get_description(self):
return _(self.name)
If you want, you can change fields' name as "name" and "description". And then;
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
ret = OrderedDict()
fields = self._readable_fields
for field in fields:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
# We skip `to_representation` for `None` values so that fields do
# not have to explicitly deal with that case.
#
# For related fields with `use_pk_only_optimization` we need to
# resolve the pk value.
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
# override to_representation function
if field.field_name == "name" or field.field_name == "description":
ret[field.field_name] = _(attribute)
return ret
Related
Views.py -
#api_view(['GET'])
def view_items(request):
if request.query_params:
items = Item.objects.filter(**request.query_param.dict()) #error line
else:
items=Item.objects.all()
serializer=ItemSerializer(items,many=True)
if items:
return Response(serializer.data)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
The **request.query_param should be changed into **request.query_params.
Let's say you want to filter by name.
#api_view(['GET'])
def view_items(request):
name = request.query_params.get("name", None)
if name:
items = Item.objects.filter(name=name)
I would suggest you to use django-filters, that's a better way to structure your code.
Create Item app
urls.py
app_name = "app-name"
router = SimpleRouter()
router.register("items", ItemViewSet, basename="item")
views.py
class ItemViewSet(views.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
filterset_class = ItemFilterSet
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ("id", "name", "label")
filters.py
from django_filters import rest_framework as filters
class ItemFilterSet(filters.FilterSet):
class Meta:
model = Item
fields = ("name", "label")
New to Django and DRF, I have a method in the model properties which accept arguments. I have managed to call it successful though a serializer class with default paramenters and getting a JSON response. My problem is I can't pass argument to that function named balance. I have successful pass my argument from view to serializer class but from serializer to model that where I have failed. I thought will be appreciated.
model.py
class Item(models.Model):
entered_by = models.ForeignKey(User, on_delete=models.PROTECT)
name = models.CharField(max_length=50, blank=True)
#property
def balance(self, stock_type='Retail'):
stock = Stock.objects.filter(item=self, type=stock_type, status='InStock').aggregate(models.Sum('quantity')).get('quantity__sum')
return stock or 0
views.py
def getItemInfo(request):
if request.is_ajax and request.method == "GET":
id = request.GET.get("item_id", None)
sell_type = request.GET.get("sell_type", None)
item = Item.objects.get(id=id)
if item:
serializer = ItemSerializer(item, context={'sell_type':sell_type})
return JsonResponse(serializer.data, status = 200, safe=False)
else:
return JsonResponse({"data":False}, status = 400)
serializer.py
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
balance = serializers.SerializerMethodField()
class Meta:
model = Item
fields = ('entered_by', 'name', 'balance', 'sell_mode')
def get_balance(self, object):
sell_type = self.context.get("sell_type")
if sell_type:
return object.balance(sell_type)
return object.balance
The error I'm getting
'int' object is not callable
#property couldn't be called. So I made member variable and setter (with calculation) methods in Item model, then make sure setter method will be called in get_balance serializer method, just before returning balance.
Django ORM model itself is just a class; you can do anything class could, not just only linking with ORM.
My Code:
models.py
from django.db import models
from django.contrib.auth.models import User
class Item(models.Model):
entered_by = models.ForeignKey(User, on_delete=models.PROTECT)
name = models.CharField(max_length=50, blank=True)
_balance = 0
def calculate_balance(self, stock_type='Retail'):
stock = Stock.objects.filter(item=self, type=stock_type, status='InStock').aggregate(
models.Sum('quantity')).get('quantity__sum')
self._balance = stock or 0
#property
def balance(self):
return self._balance
serializer.py
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
balance = serializers.SerializerMethodField()
class Meta:
model = Item
fields = ('entered_by', 'name', 'balance')
def get_balance(self, object):
sell_type = self.context.get("sell_type")
if sell_type:
object.calculate_balance(sell_type)
return object.balance
views.py
from .models import Item
from .serializer import ItemSerializer
from django.http.response import JsonResponse
def getItemInfo(request):
if request.is_ajax and request.method == "GET":
id = request.GET.get("item_id", None)
if id is None:
return JsonResponse({"data": False}, status=400)
sell_type = request.GET.get("sell_type", None)
try:
item = Item.objects.get(id=id)
serializer = ItemSerializer(item, context={'sell_type': sell_type})
return JsonResponse(serializer.data, status=200, safe=False)
except Item.DoesNotExist:
return JsonResponse({"data": False}, status=400)
Having the following Model:
class Book(models.Model):
name = models.CharField()
author = models.CharField()
date = models.DateField()
class Meta:
unique_together = ('name', 'author')
class BookSerializerWrite(serializers.ModelSerializer):
class Meta:
model = Book
class BookView(ApiView):
def put(self, request, *args, **kwargs):
serializer = BookSerializerWrite(data=request.data)
if serializer.is_valid():
serializer.save()
The view above does not work as the serializer.is_valid() is False.
The message is:
'The fields name, author must make a unique set'
Which is the constraint of the model.
How do I update the model?
I would rather not override the serializer's validation method.
I also cannot access the validated_data for an update as in
https://www.django-rest-framework.org/api-guide/serializers/#saving-instances
as this is empty due to the fact that the form does not validate.
Is there a builtin solution?
You can achieve it using UpdateAPIview
serializers.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('name', 'author', 'date')
views.py
from rest_framework.generics import UpdateAPIview
from .serializers import BookSerializer
class BookUpdateView(UpdateAPIView):
serializer_class = BookSerializer
urls.py
from django.urls import path
from . import views
url_patterns = [
path('api/book/<int:pk>/update/', views.BookUpdateView.as_view(), name="book_update"),
]
Now, post your data to above url. It should work.
Reference: https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py
I have a DRF ViewSet called "QueryCriteriaViewSet" which I'm using in a query builder which allows users to select a field and then select from related criteria. So, for example, a user can select the "reg_status" field and then select from the related criteria of "Active" and "Inactive".
This works totally fine when I select a field from the main "person" model. But I'm running into problems when I select a field from a related model like the "lookup_party" model. The weird thing though, is that when I print the queryset to the console it works perfectly, but when I get call the API it returns a list of empty objects.
As a further example, here's what happens when I make the calls:
api/querycriteria/?fields=reg_status returns:
[
{"reg_status": "Active"},
{"reg_status": "Inactive"}
]
while api/querycriteria/?fields=party__party_name returns:
[
{},
{},
{},
{},
{}
]
even though when I print(queryset) prior to returning the queryset, the following is printed:
<QuerySet [{'party__party_name': None}, {'party__party_name': 'Democratic'},
{'party__party_name': 'Non-Partisan'}, {'party__party_name': 'Registered
Independent'}, {'party__party_name': 'Republican'}]>
Here's the full ViewSet:
class QueryCriteriaViewSet(DefaultsMixin, viewsets.ModelViewSet):
serializer_class = QueryCriteriaSerializer
def get_queryset(self):
fields = self.request.GET.get('fields', None)
queryset = Person.objects.values(fields).distinct()
print(queryset)
return queryset
def get_fields_to_display(self):
fields = self.request.GET.get('fields', None)
return fields.split(',') if fields else None
def get_serializer(self, instance=None, data=empty, many=False,
partial=False):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
fields = self.get_fields_to_display()
return serializer_class(instance, data=data,
many=many, partial=partial,
context=context, fields=fields)
Let me know if any additional information would be helpful.
Here's my serializer:
from django.contrib.auth import get_user_model
from rest_framework import serializers
from rest_framework.reverse import reverse
from ..models.people import Person
from ..models.people_emails import Email
from ..models.people_addresses import Address
from ..models.people_phones import Phone
from ..models.people_tags import PersonTag
from ..models.people_elections import PersonElection
from ..models.people_districts import PersonDistrict
from ..models.people_attributes import Attribute
from .serializer_dynamic_fields import DynamicFieldsModelSerializer
from .serializer_tag import TagSerializer
from .serializer_email import EmailSerializer
from .serializer_address import AddressSerializer
from .serializer_phone import PhoneSerializer
from .serializer_election import ElectionSerializer
from .serializer_attribute import AttributeSerializer
from .serializer_district import DistrictSerializer
class QueryCriteriaSerializer(DynamicFieldsModelSerializer):
emails = EmailSerializer(many=True, required=False)
addresses = AddressSerializer(many=True, required=False)
phones = PhoneSerializer(many=True, required=False)
tags = TagSerializer(many=True, required=False)
elections = ElectionSerializer(many=True, required=False)
attributes = AttributeSerializer(many=True, required=False)
districts = DistrictSerializer(many=True, required=False)
class Meta:
model = Person
fields = ('id', 'elected_official', 'title', 'first', 'last', 'middle', 'suffix',
'full_name', 'birthdate', 'sex', 'website', 'deceased', 'registered', 'party',
'reg_date', 'reg_status', 'reg_state', 'county', 'match_id',
'date_added', 'date_updated', 'do_not_call', 'do_not_mail', 'do_not_email', 'do_not_text', 'emails',
'addresses', 'phones', 'tags', 'attributes', 'elections', 'districts')
And here's the DynamicFieldsModelSerializer:
from django.contrib.auth import get_user_model
from rest_framework import serializers
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
ok now found the problem, DynamicFieldsModelSerializer is only working if the fields you are passing to it is a subset of the serializer original fields.
you should use a serializers in a way that accepts extra fields, something like this:
class ExtraDynamicFieldsModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
extra_fields = kwargs.pop('fields', [])
self.extra_fields = set()
# Instantiate the superclass normally
super(ExtraDynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
allowed = set(extra_fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
for field_name in allowed - existing:
self.extra_fields.add(field_name)
def to_representation(self, obj):
data = super().to_representation(obj)
for field in self.extra_fields:
data[field] = obj[field]
return data
Here is my MSV:
models.py
import mongoengine
class PersonAddressModel(mongoengine.DynamicEmbeddedDocument):
country = mongoengine.fields.StringField()
town = mongoengine.fields.StringField()
class PersonModel(mongoengine.DynamicDocument):
name = mongoengine.fields.StringField()
age = mongoengine.IntField()
is_married = mongoengine.fields.BooleanField()
address = EmbeddedDocumentListField(PersonAddressModel)
serializers.py
from rest_framework_mongoengine import serializers
from .models import PersonInfoModel, PersonAddressModel
import mongoengine
class PersonAddressSerializer(serializers.EmbeddedDocumentSerializer):
class Meta:
model = PersonAddressModel
fields = '__all__'
class PersonSerializer(serializers.DynamicDocumentSerializer):
class Meta:
model = PersonModel
fields = '__all__'
views.py
from rest_framework_mongoengine import viewsets
from .serializers import PersonSerializer, PersonAddressSerializer
from rest_framework.response import Response
from rest_framework import status
import djongo
from .models import PersonModel
class PersonView(viewsets.ModelViewSet):
lookup_field = 'id'
serializer_class = PersonSerializer
def create(self, request):
serializer = self.serializer_class(data=request.data)
try:
serializer.is_valid()
serializer.save()
except djongo.sql2mongo.SQLDecodeError:
return Response(
status=status.HTTP_503_SERVICE_UNAVAILABLE
)
return Response(
status=status.HTTP_201_CREATED
)
I'm sending the following json object:
{
"name": "Helmut",
"age": 21,
"is_married": true,
"address": [{"country": "Germany", "town": "Berlin", "street": "Wolfstraße 1"}]
}
However when I check the database I see the following object:
{
"_id" : ObjectId("5b5e201c540d1c3b7a4491e8"),
"name" : "Helmut",
"age" : 21,
"is_married" : true,
"address" : [
{
"country" : "Germany",
"town" : "Berlin"
}
]
}
That's to say, Helmut's street is missing. I wonder why! My bet is that it has something to do with the serializers but I can't figure out what it is exactly.
EDIT
I want both my documents to be dynamic because some jsons may contain fields with unpredictable names and values and I have to save them as well, so I make my both model classes dynamic. However I can expand the PersonInfoModel but can't do the same with the PersonAddressModel. The street field won't show up in the DB (MongoDB).
On the other hand, I want some of the fields present in the address list to be required.
In your PersonAddressModel you defined only two fields, which are country and town. Which means, in DB, you defined the schema with those two fields, street is not included.
So, change your models to add extra field, as
class PersonAddressModel(mongoengine.DynamicEmbeddedDocument):
country = mongoengine.fields.StringField()
town = mongoengine.fields.StringField()
street = mongoengine.fields.StringField()
UPDATE
If your address field is dynamic and it's a dict like field, then use mongoengine.fields.DictField() as,
import mongoengine
class PersonModel(mongoengine.DynamicDocument):
name = mongoengine.fields.StringField()
age = mongoengine.IntField()
is_married = mongoengine.fields.BooleanField()
address = mongoengine.fields.DictField()
Update-2
You can do a field level validation in PersonSerializer as,
from rest_framework_mongoengine import serializers
from rest_framework import serializers as drf_serializer
class PersonSerializer(serializers.DynamicDocumentSerializer):
def validate_address(self, address):
required_fields = ['street', 'country']
if not all([True for field in required_fields if field in address]):
raise drf_serializer.ValidationError('some required fields are missing')
return address
class Meta:
model = PersonModel
fields = '__all__'
Since rest_framework_mongoengine doesnt have a ValidationError class, we using DRF's ValidationError class
UPDATE-3
Inorder to raise validation error, you have to pass True to .is_valid() (reff doc - Raising an exception on invalid data)method, as
class PersonView(viewsets.ModelViewSet):
lookup_field = 'id'
serializer_class = PersonSerializer
def create(self, request):
serializer = self.serializer_class(data=request.data)
try:
serializer.is_valid(True) # Change is here <<<<
serializer.save()
except djongo.sql2mongo.SQLDecodeError:
return Response(
status=status.HTTP_503_SERVICE_UNAVAILABLE
)
return Response(
status=status.HTTP_201_CREATED
)