I have a serializer that gives this data
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id', 'name', 'cost', 'currency')
class UserSerializer(serializers.ModelSerializer):
posts = PostSerializer(many=True)
class Meta:
model = User
fields = ('id', 'name')
and it gives the response like this,
{
"id": 1,
"name": "joe",
"posts": [
{
"id": 20,
"name": "first post",
"cost": 20.00,
"currency": "USD"
},
{
"id": 21,
"name": "second post",
"cost": 30.00,
"currency": "USD"
}
]
}
However I want to change/add the fields of the response based on few conditions,
Eg. if cost is less than 25, make it zero and add a discount field for every post.
This is how I am doing.
class MyPostView(APIView):
def get(request):
query_set = User.objects.all()
user_and_posts = UserSerializer(query_set)
response_data = user_and_posts.data
# I am modifying the serializer data here :<
for post in response_data['posts']:
post['discount'] = 10 # some value
if post['cost'] < 25:
post['cost'] = 0
return Response(serializer.data, status=status.HTTP_200_OK)
To me modifying the primitive data like this is not looking right,
is there any alternate way in django rest to do this?
or could've done better with serializer?
In general, what's the best way to alter the response data we get from serializer and
format it in the way client wants? In languages like Java, we will have serializer for model and another serializer for output.. Can I do something similar?
If it is something that is model related and can be derived by manipulating model variables, I would advise to add properties into your model
class Post(models.Model):
name = models.CharField(max_length=64)
cost = models.DecimalField(max_digits=8, decimal_places=2)
currency = models.CharField(max_length=3)
#property
def alternative_cost(self):
if self.cost < 25:
return 0
else:
return self.cost
Then you have to add the newly created property to serializer.
Related
I'm new to Django REST Framework.
I have a nested model for saving orders of a restaurant. When I send a GET request I get the following response:
[
{
"menu": {
"id": 1,
"food_name": "food1"
},
"user": {
"id": 49,
"username": "A"
}
},
{
"menu": {
"id": 1,
"food_name": "food1"
},
"user": {
"id": 63,
"username": "B"
}
}
]
But I want to group users with the same menu like this:
[
{
"menu": {
"id": 1,
"food_name": "food1",
"users": {
"1": {
"id": 49,
"username": "A"
},
"2": {
"id": 63,
"username": "B"
}
}
}
}
]
Here is my code:
models.py
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
views.py
class OrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
serilizers.py
class OrderSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
menu = MenuSerializer()
class Meta:
model = Order
fields = ['id', 'user', 'menu']
Thanks
Since your output suggests that you are working with menus, I suggest you create a separate viewset that works with menus and returns the expected output you want here.
Doing the expected results in the order viewset is possible, but is tricky to optimise using select_related and prefetch_related. But if you really want this in your order viewset, then you can do this with:
class MenuSerializer(serializers.ModelSerializer):
users = serializers.SerializerMethodField()
class Meta:
model = Menu
fields = ['id', 'food_name', 'users']
def get_users(self, obj):
users = User.objects.filter(pk__in=obj.order_set.all().values('user'))
return UserSerializer(users, many=True).data
class OrderSerializer(serializers.HyperlinkedModelSerializer):
menu = MenuSerializer()
class Meta:
model = Order
fields = ['id', 'menu']
You also need to change the queryset of the view if you want distinct results, otherwise you will get duplicates:
class OrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all().distinct('menu')
serializer_class = OrderSerializer
Do note that this is not optimised, and each row in your order table will require hitting the database just to get the users through menu. distinct('menu') also only works on PostgreSQL.
You can nest User into Menu if you want to group by menu
class OrderSerializer(serializers.HyperlinkedModelSerializer):
menu = MenuSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'menu']
class MenuSerializer(serializers.ModelSerializer):
user = UserSerializer(many=True)
class Meta:
model = Menu
fields = ['id', 'user']
I have a model CitizenInfo that contains a field 'relatives' with many to many relationship on itself.
When i do a POST request what i want is to validate and deserialize the raw data from JSON using the optimal way.
What i found so far is a library for recursive model fields. https://github.com/heywbj/django-rest-framework-recursive and this not super informative topic https://github.com/encode/django-rest-framework/issues/4183
This is my POST request:
[
{
"citizen_id": 1,
"name": "Dave",
"relatives": [2]
},
{
"citizen_id": 2,
"name": "Jack",
"relatives": [1, 3]
},
{
"citizen_id": 3,
"name": "Alex",
"relatives": [2]
}
]
model.py:
class CitizenInfo(models.Model):
citizen_id = models.PositiveIntegerField(primal_key=True)
name = models.CharField(max_length=255)
relatives = models.ManyToManyField('self', blank=True)
views.py
class CitizenInfoImportView(APIView):
def post(self, request):
# Because of many=True we will use BulkCitizensSerializer
serializer = CitizenListSerializer(data=request.data, many=True)
if serializer.is_valid():
serializer_response = serializer.save()
return Response(status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
# Using this function for bulk_save operation
def save_citizens(citizen_data):
new_citizen = CitizenInfo(citizen_id=citizen_data.get('citizen_id'),
name=citizen_data.get('name'))
# Looping through relative list and do some custom serialization
for relative in citizen_data.get('relatives'):
pass
return new_citizen
class BulkCitizensSerializer(serializers.ListSerializer):
def create(self, validated_data):
new_citizens = [save_citizens(citizen_data) for citizen_data in validated_data]
return CitizenInfo.objects.bulk_create(new_citizens)
class CitizenListSerializer(serializers.ModelSerializer):
class Meta:
model = CitizenInfo
field = '__all__'
list_serializer_class = BulkCitizensSerializer
Also i had a limit that for one request it should be no more than 1000 relatives (2000 connected citizens). And i though that recursion is probably a bad way to solve it.
Any advice?
I need to filter some Models in Django and return them trough REST, but I have some difficulties. I have 4 Models connected with Foreign key's like so:
class Standort(models.Model):
name = models.CharField(max_length=40)
farbe = models.CharField(max_length=20, default="Black")
class Gruppe(models.Model):
standort = models.ForeignKey(Standort)
name = models.CharField(max_length=40)
class Person(models.Model):
name = models.CharField(max_length=40)
gruppe = models.ForeignKey(Gruppe, related_name='personen')
class Eintrag(models.Model):
person = models.ForeignKey(Person, related_name='eintrage')
typ = models.ForeignKey(Typ)
datum = models.DateField()
and Iam serializing them like so:
class EintragSerializer(serializers.ModelSerializer):
class Meta:
model = Eintrag
fields = ('datum', 'typ')
class PersonenSerializer(serializers.ModelSerializer):
eintrage = EintragSerializer(read_only=True, many=True)
class Meta(object):
model = Person
fields = ('id', 'name', 'eintrage')
class GruppenPersonenEintraegeSerializer(serializers.ModelSerializer):
personen = PersonenSerializer(read_only=True, many=True)
class Meta(object):
model = Gruppe
fields = ('id', 'name', 'personnel')
and my view looks like this:
class GruppenPersonenEintraege(APIView):
def get(self, request, standort, jahr):
gruppen = Gruppe.objects.filter(standort=standort)
serializer = GruppenPersonenEintraegeSerializer(gruppen, many=True)
return Response(serializer.data)
The result looks like this:
[
{
"id": 2,
"name": "2.Schicht",
"personen": [
{
"id": 1,
"name": "Rolf der Tester",
"eintrage": [
{
"datum": "2017-02-16",
"typ": 3
},
{
"datum": "2017-02-15",
"typ": 3
},
{
"datum": "2018-04-05",
"typ": 2
}
]
}
]
},
{
"id": 3,
"name": "Test",
"personen": []
}
]
This is totally fine, my Problem is when i also want to filter the year of "eintrage.datum"by adding: .filter(standort=standort, personen__eintrage__datum__year=2017)afterGruppe.objects. Then the entry with "id": 2 is repeated 3 times and the one with "id": 3 isn't displayed at all. how do i filter just the entry's of the second nested dict?
To avoid "id":2 repeated multi times, you can just add a list(set()) surround the filter queryset result, the django restful framework can also treat the list the same way as queryset. Also notice that in django orm, the hash of a model instance is the prime_key of in db, so that's why the set can work on queryset.
As for "id":3 not showing, I also have no ideas as you did, maybe double better check the db again. A little bit more info will be more helpful.
I have a ManyToMany relation with tag and items:
class Tag(BaseModel):
name = models.CharField(max_length=255) # ToDo Change max length
description = models.TextField(blank=True, null=True)
class Item(BaseModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
image = models.ImageField(upload_to='items', blank=True)
title = models.TextField(blank=False, null=True)
message = models.TextField(blank=True, null=True)
fav_count = models.IntegerField(default=0)
tags = models.ManyToManyField(Tag, related_name='tags')
I need all fields to be serialized, but i wish to only limit the response values.
Example:
What I'm receiving now:
{
"user": 2,
"image": null,
"title": "test3",
"message": "testmessage",
"fav_count": 0,
"tags": [
{
"id": 7,
"name": "tag1",
"description": null
},
{
"id": 8,
"name": "tag2",
"description": null
}
]
}
But i only wish to receive the tag ids not the name and description...
My simple view:
if request.method == 'GET':
items = Item.objects.all()
serializer = ItemSerializer(items, many=True)
return Response(serializer.data)
Would i need to rebuild my response data to include/exclude or is there a better way to do this? (or if iv missed the terminology)
use PrimaryKeyRelatedField DRF field in your serializer
Example
class ItemSerializer(serializers.ModelSerializer):
tags = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Item
fields = ('tags', 'image',.....other fields)
Response
{
'image': 'image1',
...........
'tags': [
89,
90,
91,
...
]
..........
}
In you want to do it dynamically based on a request parameter.
class ItemSerializer(serializers.ModelSerializer):
tags = serializers.SerializerMethodField()
def get_tags(self, obj):
if self.request.get('some_condition'):
data_tags = TagSerializer(obj.tags, many=True).data
data = map(data.pop('field_to_remove') for data in data_tags)
return list(data)
else:
return TagSerializer(obj.tags, many=True).data
Then, pass request to your serializer when you init it in your view.
serializer = ItemSerializer(data, context={'request':self.request})
You probaply using serializer for Tag model and declare it in ItemSerializer so view is showing full TagSerializer info.
If you want to show only pk field, just use default representation, don't declare special serializer for Tag in ItemSerializer
I am using Django and I want to use filters in that
My product and company Models are
class Product(models.Model):
name = models.CharField(max_length=200)
companyId = models.ForeignKey(Comapany)
class Company(models.Model):
domain = models.CharField(max_length=200)
I want to retrieve Products based on currently user's companyId. So I have implemented My view like this..
class ListProducts(APIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAdminUser,)
def get(self, request):
if request.user.is_authenticated():
userCompanyId = request.user.get_profile().companyId
products = Product.objects.filter(companyId__id__exact = userCompanyId)
serializer = ProductSerializer(products)
return Response(serializer.data)
My Product Data
{
"_id": ObjectId("5284ceaae9cfff79368e1f29"),
"companyId": "528458c4bbe7823947b6d2a3",
"name": "Apple Juice"
}
{
"_id": ObjectId("5267bb4ebbe78220588b4567"),
"companyId": "52674f02bbe782d5528b4567",
"name": "Small Soft & Moist Cranberry"
}
My company Data
{
"_id": ObjectId("528458c4bbe7823947b6d2a3"),
"domain": "Manufacturing"
}
{
"_id": ObjectId("52674f02bbe782d5528b4567"),
"domain": "Manufacturing"
}
I am getting output as []
The problem is that while studying filters from django doc I am not able to get it.. SO please help me..
I resolved the error. Actually The way of specifying how Product model's companyId references Id field of company model was wrong. So I saw How userprofile references user model .I am mentioning here my changes.
Product Model
class Product(models.Model):
name = models.CharField(max_length=200)
company = models.ForeignKey(Company,null=True)
View
class ListProducts(APIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAdminUser,)
model = Product
def get(self, request):
if request.user.is_authenticated():
userCompanyId = request.user.get_profile().companyId
products = Product.objects.filter(company = userCompanyId)
serializer = ProductSerializer(products,many=True)
return Response(serializer.data)
put company_id in Company data like this
{
"_id": ObjectId("5284ceaae9cfff79368e1f29"),
"company_id": "528458c4bbe7823947b6d2a3",
"name": "Apple Juice"
}
Obviously your user profile model's companyId is not an integer field, so you should adjust your Product model to have a compatible companyId type. Oh and yes: don't you have a Company model ?
This line:
ProductSerializer(products)
Should be:
ProductSerializer(products, many=True)
Otherwise though your issue doesn't seem to be related to Django REST framework, just to the particular filter you're running not returning any results.
Double check that request.user.get_profile() is returning a correct profile, double check that userCompanyId is set correctly, and double check the number of results returned by Product.objects.filter(companyId__id__exact=userCompanyId).count().
I'd guess you either have userCompanyId set incorrectly, or there simply aren't any matching Product records.