Add extra field in response output in DRF 3.0 - python

I have the following models
class Restaurant(models.Model):
name_of_the_restaurant = models.CharField(max_length=30, blank=True)
opening_time = models.TimeField(auto_now=False, auto_now_add=False)
closing_time = models.TimeField(auto_now=False, auto_now_add=False)
And
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
city = models.CharField(max_length=30, blank=True)
country = models.CharField(max_length=30, blank=True)
postal_code = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
favourite_restaurant = models.ManyToManyField(Restaurant,
blank=True,
related_name='favourite_restaurant',
related_query_name='favourite_restaurant')
I have defined a serializer for Restaurant model which is mainly :
class RestaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
fields = '__all__'
Now in my ViewSet logic I am doing the following :
class RestaurantListView(generics.ListAPIView):
serializer_class = RestaurantSerializer
def get_queryset(self):
queryset = {'Error': 'Please pass valid url parameters'}
city = self.request.query_params.get('city', None)
postal_code = self.request.query_params.get('postalcode', None)
country = self.request.query_params.get('country', None)
if city is not None or postal_code is not None:
queryset = Restaurant.objects.filter(
Q(city=city) | Q(pincode=postal_code))
if country and city is not None and postal_code is None:
queryset = Restaurant.objects.filter(country=country, city=city)
return queryset
def get(self, request, format=None):
restaurant_qs = self.get_queryset()
ids_list = [restaurant.id for restaurant in restaurant_qs]
favourite_restaurant = is_favourite_restaurant(ids_list, self.request.user)
serializer = RestaurantSerializer(restaurant_qs, many=True)
return Response(serializer.data)
where is_favourite_restaurant is a custom function function which returns queryset of FAVOURITE restaurant(s) of a user. Now in the output for this GET request I am getting result as :
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414"
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397"
}
]
Whereas the desired output I want is to append an extra field is_favourite:true to that restaurant which user has previously marked favourite. And hence the output should be
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414",
"is_favourite": true,
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397"
}
]
EDIT :
Definition of is_favourite_restaurant function :
def is_favourite_restaurant(restaurant_qs, user):
favourite_restaurant_qs = Profile.objects.get(user=user).favourite_restaurant.filter(
pk__in=restaurant_qs.values_list('id', flat=True))
return favourite_restaurant_qs

You can use SerializerMethodField. SerializerMethodField allows add extra field which is read only as you want.
class RestaurantSerializer(serializers.ModelSerializer):
is_favorite = serializers.SerializerMethodField()
class Meta:
model = Restaurant
fields = ('your', 'fields', 'is_favorite')
def get_is_like(self, obj):
return is_favourite_restaurant(obj.id, self.context['request'].user)
Normally, ListAPIView add context to serializer. As you use your create method, you should add manually.
serializer = RestaurantSerializer(restaurant_qs, many=True, context={'request': self.request})
Context allows access some data which is we send from the view.
As you did not shown your is_favourite_restaurant, i can't say that what should you do in that function. I guess you should change ids parameter from array to one id.
Your response looks like
[
{
"id": 2,
"name_of_the_restaurant": "Aniket",
"opening_time": "14:08:33.413402",
"closing_time": "22:08:33.413414",
"is_favourite": True,
},
{
"id": 3,
"name_of_the_restaurant": "Aniket-1",
"opening_time": "14:13:37.656385",
"closing_time": "22:13:37.656397",
"is_favourite": False,
}
]
def is_favourite_restaurant(restaurant_id, user):
favourite_restaurant_qs = Profile.objects.get(user=user).favourite_restaurant.filter(
pk=restaurant_id).exists()
return favourite_restaurant_qs

Related

Django Update serializer and unique constainst

I am trying to create a meal planner with react and django. I am having issues with the form and getting it to the backend. Right now I can submit the form from react and I get the data back to django correctly. Unless I need to add another mealItem for the same date and serviceType ('lunch', 'dinner'). I get the unique constraint error for the following fields: "meals_meal.menu_id, meals_meal.date, meals_meal.type, meals_meal.url".
I have an update function in the serializer that is not being called. What can I do to check for date and type if they exist update the data.
I need the lunch and dinners to be seperate even if they are from the same date.
Models.py
class Meal(models.Model):
menu = models.ForeignKey(
Menu,
related_name='meals',
on_delete=models.CASCADE,
)
date = models.DateField(db_index=True)
type = models.CharField(
choices=[
('lunch', 'Lunch'),
('dinner', 'Dinner'),
],
max_length=10,
)
url = models.URLField(max_length=200, default="")
created_at = models.DateTimeField(
auto_now_add=True,
editable=False,
)
updated_at = models.DateTimeField(
auto_now=True,
editable=False,
)
objects = MealQuerySet.as_manager()
class Meta:
unique_together = ['menu', 'date', 'type', 'url']
ordering = ['date', '-type']
class MealItem(models.Model):
meal = models.ForeignKey(
Meal,
related_name='items',
on_delete=models.CASCADE,
)
name = models.CharField(max_length=100)
type = models.CharField(
choices=[
('entre', 'Entre'),
('side', 'Side'),
('other', 'Other'),
],
max_length=10,
)
is_dairy_free = models.BooleanField(
default=False,
verbose_name='D',
help_text='Dairy Free',
)
is_gluten_free = models.BooleanField(
default=False,
verbose_name='G',
help_text='Gluten Free',
)
is_vegetarian = models.BooleanField(
default=False,
verbose_name='V',
help_text='Vegetarian',
)
created_at = models.DateTimeField(
auto_now_add=True,
editable=False,
)
updated_at = models.DateTimeField(
auto_now=True,
editable=False,
)
Serializers.py
class MealSerializer(serializers.ModelSerializer):
items = MealItemSerializer(many=True)
class Meta:
model = Meal
fields = ['id', 'date', 'type','url', 'items', 'menu', ]
validators = []
def create(self, validated_data):
item_data = validated_data.pop('items')
meal= Meal.objects.create(**validated_data)
for item_data in item_data:
MealItem.objects.get_or_create(meal=meal, **item_data)
return meal
def update(sel, instance, validated_data):
instance.id = validated_data.get('id', instance.id)
instance.date = validated_data.get('date', instance.date)
instance.type = validated_data.get('type', instance.type)
instance.save()
return instance
Views.Py
class MealViewSet(LoginRequiredMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = MealSerializer
pagination_class = MealPagination
def get_queryset(self):
if self.request.user.is_authenticated:
queryset = Menu.objects.all()
menu = get_object_or_404(
queryset, pk=self.kwargs['menu_pk'], users=self.request.user)
return Meal.objects.filter(menu=menu)
else:
print("not auth")
return HttpResponseRedirect(self.request, "/login")
def post(self, request, menu_pk):
data = self.request.data
user = self.request.user
if user.is_authenticated and user.has_perm("meals.change_menu"):
if request.method == "POST":
serializer =MealSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response({'success':"Your post was successfull."})
return Response({'failure': 'post was not authenticated'})
return Response({'failure': "user is not authenticated or does not have permission to submit form"})
def update(self, request):
data = self.request.data
user = self.request.user
if user.is_authenticated and user.has_perm("meals.change_menu"):
if request.method == 'PUT':
serializer = MealItemSerializer(instance=self.get_object(), data=data, partial=True )
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response({"Success": "Your meal was updated"})
This is the result I need to get. But right now I only submit one meal at a time in the items array. As oppposed to adding all three meals like I do in the django admin add meals pannel.
"id": 1,
"date": "2021-11-17",
"type": "lunch",
"url": "#ImageUrlFromFirebase",
"items": [
{
"name": "milk",
"type": "entre",
"is_dairy_free": false,
"is_gluten_free": false,
"is_vegetarian": true
},
{
"name": "beans",
"type": "side",
"is_dairy_free": false,
"is_gluten_free": true,
"is_vegetarian": false
},
{
"name": "sleep",
"type": "other",
"is_dairy_free": true,
"is_gluten_free": false,
"is_vegetarian": false
}
],
"menu": 1
},
The update method is called when the request type is PUT and corresponding to that there has to be an update method on your Viewset, which is missing it seems ? also , if an update is partial (that means only some fields are going to get updated ) then partial = True (has to be), for ref https://www.django-rest-framework.org/tutorial/2-requests-and-responses/
Ideally, I would create another viewset with the name MealItemViewset which will then have the update method on it , so we can separate creation, deletion and updating of Meals and MealItems

How to retrieve a many-to-many field with backward relationships lookup in Django REST Framework serializer?

Please correct my title if it's not correct. My problem is I want to retrieve FinishType's name from Product. I have tried 2 ways to achieve this: first attempt and second attempt.
My simplifed related models in models.py:
class Product(models.Model):
product_id = models.CharField(max_length=6)
color = models.ForeignKey(ColorParent, on_delete=models.SET_NULL, null=True)
collection = models.ForeignKey(ProductCollection, on_delete=models.SET_NULL, null=True)
#property
def get_distributors(self):
return Distributor.objects.filter(distributor__products=self).count()
def __str__(self):
return self.product_id
class FinishType(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class ProductFinishType(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
market = models.ForeignKey(Market, on_delete=models.CASCADE)
finish_types = models.ManyToManyField(FinishType)
def __str__(self):
return '%s - %s' % (self.product, self.market)
class ProductAlias(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
market = models.ForeignKey(Market, on_delete=models.CASCADE)
name = models.CharField(max_length=50, null=True, blank=True)
def __str__(self):
return '%s - %s' % (self.product, self.name)
My serializers.py:
class ProductGridSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField(source='get_name')
finishing = serializers.SerializerMethodField('get_finish_types')
distributor = serializers.ReadOnlyField(source='get_distributors')
#staticmethod
def get_name(obj):
return [pa.name for pa in obj.productalias_set.all()]
#staticmethod
def get_finish_types(obj):
return [pft.name for pft in obj.productfinishtype_set.all().select_related('finish_types')] # first attempt
class Meta:
model = Product
fields = ['id', 'product_id', 'name', 'collection', 'finishing', 'distributor']
First attempt works for name field which fetches ProductAlias's name but gives me this error:
FieldError at /api/product_grids/
Invalid field name(s) given in select_related: 'finish_types'. Choices are: product, market
My get_finish_types() on second attempt:
#staticmethod
def get_finish_types(obj):
product_finish_types = obj.productfinishtype_set.all()
response = ProductFinishTypeSerializer(product_finish_types, many=True, source='finish_types').data
return response
It gives me the whole object datas:
{
"id": 1,
"product_id": "BQ1111",
"name": [
"White Stone"
],
"collection": 1,
"finishing": [
{
"id": 1,
"product": 1,
"market": 1,
"finish_types": [
1,
3,
5
]
}
],
"distributor": 5
},
My desired output is something like:
{
"id": 1,
"product_id": "BQ1111",
"name": [
"White Stone"
],
"collection": 1,
"finishing": [
"Polished",
"Carved",
"Melted"
],
"distributor": 5
},
Create a serializer for FinishType,
class FinishTypeSerializer(serializers.ModelSerializer):
class Meta:
model = FinishType
fields = ('name',)
and wire-up it in ProductGridSerializer using SerializerMethodField
class ProductGridSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField(source='get_name')
distributor = serializers.ReadOnlyField(source='get_distributors')
finishing = serializers.SerializerMethodField()
def get_finishing(self, product):
qs = FinishType.objects.filter(productfinishtype__product=product)
return FinishTypeSerializer(qs, many=True).data
#staticmethod
def get_name(obj):
return [pa.name for pa in obj.productalias_set.all()]
class Meta:
model = Product
fields = ['id', 'product_id', 'name', 'collection', 'finishing', 'distributor']
Inspired by #Arakkal Abu's queryset, I tried it using my first attempt.
FinishTypeSerializer added in serializers.py:
class FinishTypeSerializer(serializers.ModelSerializer):
class Meta:
model = FinishType
fields = ('name',)
ProductGridSerializer in serializers.py:
class ProductGridSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField(source='get_name')
finishing = serializers.SerializerMethodField(source='get_finishing')
distributor = serializers.ReadOnlyField(source='get_distributors')
#staticmethod
def get_name(obj):
return [pa.name for pa in obj.productalias_set.all()]
#staticmethod
def get_finishing(product):
return [pft.name for pft in FinishType.objects.filter(productfinishtype__product=product)]
class Meta:
model = Product
fields = ['id', 'product_id', 'name', 'collection', 'finishing', 'distributor']
The JSON output is:
{
"id": 1,
"product_id": "BQ1111",
"name": [
"White Stone"
],
"collection": 1,
"finishing": [
"Polished",
"Honed",
"Carved"
],
"distributor": 5
},

Populating json from multiple django models

I have the following 2 Django models, Lessons and UserLessons.
Lessons would be created by an admin and UserLessons is basically when a user is in progress with the Lesson or completed the Lesson, linked by a foreign key. UserLesson doesn't necessarily contain a Lesson entry, till the user actually starts with that specific one.
As I'm building an API (with DRF), I need to list the full list of all Lessons - easy.
LessonList = Lesson.objects.all().values('id', 'title')
This returns
[
{
"id": 1,
"title": "Lesson 1"
},
{
"id": 2,
"title": "Lesson 2"
},
{
"id": 3,
"title": "Lesson 3"
}
]
However, I need to be able to merge it with UserLesson (eg UserLessonList = UserLesson.objects.filter(user=request.user).values('id', 'lesson__id', 'completed') which currently returns
[
{
"id": 2,
"lesson__id": 1,
"completed": true
},
{
"id": 3,
"lesson__id": 2,
"completed": true
}
]
Ideally, it should return all the lessons from the db, and the the completed values, defaulting to completed: false if that specific lesson doesn't exist in DB.
Any suggestions?
Edit:
Views
class LessonList(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
LessonList = Lesson.objects.all().values('id', 'title')
UserLessonList = UserLesson.objects.filter(user=request.user).values('id', 'lesson__id', 'completed')
return Response(LessonList)
Models
class Lesson(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=250, verbose_name=u'Title')
slug = models.SlugField(null=True, blank=True, help_text='eg, lesson-1-whats-up')
published = models.BooleanField(default=False)
def __str__(self):
return(self.title)
class UserLesson(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, null=True)
completed = models.BooleanField(default=False)
def __str__(self):
text = str(self.lesson.title)
return(text)
You should use ModelViewSet and serializers. Exactly ModelSerializer. Something like this:
class LessonSerializer(serializers.ModelSerializer):
completed = serializers.SerializerMethodField()
class Meta:
model = Lesson
fields = ['id', 'title', 'completed']
def get_completed(self, obj):
user = self.context.get('request').user
return UserLesson.objects.filter(user=user, lesson=obj, completed=True).exists()
class LessonViewSet(viewsets.ModelViewSet):
queryset = Lesson.objects.filter(published=True)
serializer_class = LessonSerializer
permission_classes = [IsAuthenticated]

How can I access django model property from view?

I have a django model:
class DebtRequest(models.Model):
from_user = models.ForeignKey(User, related_name='debt_requests_from_user')
to_user = models.ForeignKey(User, related_name='debt_requests_to_user')
paying_user = models.ForeignKey(User, related_name='debt_requests_paying_user')
receiving_user = models.ForeignKey(User, related_name='debt_requests_receiving_user')
amount = models.FloatField()
currency = models.CharField(max_length=10, default="USD")
description = models.CharField(max_length=100, blank=True)
date_incurred = models.DateTimeField(default=timezone.now)
deadline = models.DateTimeField()
payed = models.BooleanField(default=False)
overdue = models.BooleanField(default=False)
created = models.DateTimeField(default=timezone.now)
#property
def time_since_created(self):
return (timezone.now() - self.created).total_seconds()
Here is my view:
class ListDebtRequests(generics.ListAPIView):
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
debt_requests_list = Debt.objects.requests(user=request.user)
debt_requests_list_json = json.loads(serializers.serialize('json', debt_requests_list))
debt_requests_json = [debt_requests_json_format(x, request.user.id)
for x in debt_requests_list_json]
return JsonResponse(debt_requests_json, safe=False)
And here is the function debt_requests_json_format:
def debt_requests_json_format(x, user_pk):
fields = x["fields"]
# if == True --> Debt request addressed to current user.
if fields["to_user"] == user_pk:
return {"pk": x["pk"],
"time_since_created": fields["time_since_created"],
"created": fields["created"],
"is_user_to_user": True,
"current_user_pk": user_pk,
"from_user": fields["from_user"],
"from_user_first_name": User.objects.get(pk=fields["from_user"]).firstName,
"from_user_last_name": User.objects.get(pk=fields["from_user"]).lastName,
"paying_user": fields["paying_user"],
"receiving_user": fields["receiving_user"],
"amount": fields["amount"],
"currency": fields["currency"],
"payed": fields["payed"],
"description": fields["description"],
"date_incurred": fields["date_incurred"],
"deadline": fields["deadline"],
"overdue": fields["overdue"]}
else: # if == False --> Debt request has been sent by current user.
return {"pk": x["pk"],
"created": fields["created"],
"is_user_to_user": False,
"current_user_pk": user_pk,
"to_user": fields["to_user"],
"to_user_first_name": User.objects.get(pk=fields["to_user"]).firstName,
"to_user_last_name": User.objects.get(pk=fields["to_user"]).lastName,
"paying_user": fields["paying_user"],
"receiving_user": fields["receiving_user"],
"amount": fields["amount"],
"currency": fields["currency"],
"payed": fields["payed"],
"description": fields["description"],
"date_incurred": fields["date_incurred"],
"deadline": fields["deadline"],
"overdue": fields["overdue"]}
I need the model property to be accessed from within here ideally or pass it in to the function.
The time_since_created property returns the time in seconds since the model instance was created, however I don't know how to access this from my view.
How can I access this from my view?
I try define an example serializer for your model with extra field. This is very simple example.
class DebtRequestSerializer(serializers.ModelSerializer):
is_user_to_user = serializers.SerializerMethodField()
current_user_pk = serializers.SerializerMethodField()
time_since_created = serializers.IntegerField(source='time_since_created')
to_user_first_name = serializers.CharField(source='to_user.firstName')
class Meta:
model = DebtRequest
def get_is_user_to_user(self, obj):
return obj.to_user == self.context.get('request').user.id
def get_current_user_pk(self, obj):
return self.context.get('request').user.id
more details: rest-framework fields

Filtering serializer response data

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

Categories

Resources