Filter related objects Django - django

Imagine such models in Django:
class Organisation(Model):
...
class Guest(Model):
organisation = ForeignKey(Guest, CASCADE, 'guests')
class Booking(Model):
guest = ForeignKey(Guest, CASCADE, 'bookings')
start_date = DateField()
end_date = DateField()
Using Django Rest Framework it is needed to perform a endpoint for
all organisations to be listed
all guests to be listed
bookings to be filtered agains start_date and listed
Sample of response:
"Organizations": [
{
...,
"Guests": [
{
...,
"Bookings": [
{...},
{...}
]
},
{
...,
"Bookings": [
]
}
]
},
{
...,
"Guests": [
{
...,
"Bookings": [
{...},
]
},
]
}
]
So, as you can see, I need all Organisations and Guests to be present, not only those who have Bookings.
What is the optimal way to perform that?
UPD:
Serializers used:
class BookingShortSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = (
'pk',
'start_date',
'guest',
)
class GuestBookingsSerializer(serializers.ModelSerializer):
bookings = BookingShortSerializer(many=True)
class Meta:
model = Guest
fields = (
'pk',
'name',
'bookings',
)
class OrganizationShortSerializer(serializers.ModelSerializer):
guests = GuestBookingsSerializer(many=True)
class Meta:
model = Organization
fields = (
'pk',
'public_name',
'internal_name',
'guests',
'order',
)
ViewSet used (no filtering now):
class OrganizationBookingsViewSet(mixins.ListModelMixin, GenericViewSet):
ordering = 'order'
serializer_class = OrganizationShortSerializer
permission_classes = (AllowAny,)
def get_queryset(self) -> QuerySet:
return Organization.objects.all()

Looks like this approach using SerializerMethodField solves the task. Should it be considered as a good solution, or not?
class GuestBookingsSerializer(serializers.ModelSerializer):
bookings = serializers.SerializerMethodField()
class Meta:
model = Guest
fields = (
'pk',
'name',
'bookings',
)
def get_bookings(self, guest):
date = self.context.['request'].query_params['start_date__gte']
bookings = guest.bookings.filter(start_date__gte=date).all()
serializer = BookingShortSerializer(bookings, many=True)
return serializer.data

Related

How to make calculations after requesting with M2M fields (DRF)

class Product(models.Model):
name = models.CharField(max_length=255)
ht = models.CharField(default=0)
wd = models.CharField(default=0)
len = models.CharField(default=0)
class Parcel(models.Model):
product_list = models.ManyToManyField(Product)
class ParcelSerializer(serializers.ModelSerializer):
class Meta:
model = Parcel
fields = '__all__'
class ProductSerializer(serializers.ModelSerializer):
product_volume = serializers.ReadOnlyField()
class Meta:
model = Product
fields = '__all__'
class ParcelCreateView(generics.CreateAPIView):
queryset = Package.objects.all()
serializer_class = PackageSerializer
class ParcelListView(generics.ListAPIView):
serializer_class = ParcelSerializer
class ProductCreateView(generics.CreateAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
class ProductListView(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
My output is:
[
{
"id": 1,
"products": [
1
]
},
{
"id": 2,
"products": [
1,
2
]
}
]
http://127.0.0.1:8000/parcel/create
How can I get product id and product volume as below when I post parcel create? I didn't understand what to do because it's ManyToManyField. After the request comes, how can I get product ids from the body and calculate their volume
[
{
"id": 1,
"products": [
1 : Result #Volume of ID : 1 Product (wd*ht*len)
]
},
{
"id": 2,
"products": [
1 : Result #Volume of ID : 1 Product (wd*ht*len),
2 : Result #Volume of ID : 1 Product (wd*ht*len)
]
}
]
You can work with a serializer in your ParcelSerializer:
class ProductSerializer(serializers.ModelSerializer):
# …
pass
class ParcelSerializer(serializers.ModelSerializer):
products = ProductSerializer(many=True)
class Meta:
model = Parcel
fields = '__all__'
You thus need to define the ProductSerializer first, and then use that in the ParcelSerializer.
If you want to return the outcome of str(…) on a Product, you can work with the StringRelatedField [drf-doc]:
class ParcelSerializer(serializers.ModelSerializer):
products = StringRelatedField(many=True)
class Meta:
model = Parcel
fields = '__all__'
then this will thus produce a list of strings.

Django REST Framework: Show only latest of nested object, return as un-nested JSON

What I'm trying to do in Django REST Framework: Return only latest nested object in list for an object and return it as JSON, with the sub-object un-nested.
My models:
class BaseObject(models.Model):
name = models.TextField()
object_type = models.ForeignKey(ObjectType)
class ObjectStatus(models.Model):
baseobject_id = models.ForeignKey('objects.BaseObject', related_name='status')
object_status = models.IntegerField()
object_status_timestamp = models.DateTimeField()
My serializers:
class ObjectStatusSimplifiedSerializer(serializers.ModelSerializer): #helper serializer to simplify status objects
class Meta:
model = ObjectStatus
fields = ['object_status', 'object_status_timestamp']
class ObjectStatusListSerializer(serializers.ModelSerializer): #request for last status of several objects
status = ObjectStatusSimplifiedSerializer(many=True)
class Meta:
model = BaseObject
fields = ['id', 'name', 'object_type', 'status']
My current view:
class ObjectStatusListView(generics.ListCreateAPIView):
serializer_class = ObjectStatusListSerializer
def get_queryset(self):
queryset = BaseObject.objects.all()
id = self.request.query_params.getlist('id')
if id:
queryset = queryset.filter(id__in=id)
return queryset
Current URL:
url(r'^objectstatus/status/list$', views.ObjectStatusListView.as_view()),
So now, when going to, for example, [...]/objectstatus/status/list?id=9, the result I get looks like this:
[
{
"id": 9,
"name": "r5",
"object_type": "router",
"status": [
{
"object_status": 1,
"object_status_timestamp": "2019-10-24T09:40:15.605391Z"
},
{
"object_status": 2,
"object_status_timestamp": "2019-10-24T09:40:28.133296Z"
},
{
"object_status": 3,
"object_status_timestamp": "2019-10-24T09:40:40.829486Z"
},
{
"object_status": 1,
"object_status_timestamp": "2019-10-24T09:40:53.333332Z"
}
]
}
]
What I want is to display only the object status with the most recent timestamp.
Also, I can't figure out how to flatten the JSON object, like this:
[
{
"id": 9,
"name": "r5",
"object_type": "router",
"object_status": 1,
"object_status_timestamp": "2019-10-24T09:40:53.333332Z"
}
]
With the following serializer, you should get the desired output. We filter the status list and get only the latest one and then we flatten the structure as you need.
class ObjectStatusListSerializer(serializers.ModelSerializer): #request for last status of several objects
status = serializers.SerializerMethodField(read_only=True)
class Meta:
model = BaseObject
fields = ['id', 'name', 'object_type', 'status']
def get_status(self, obj):
return ObjectStatusSimplifiedSerializer(instance=obj.status.order_by('object_status_timestamp').first()).data
def to_representation(self, obj):
"""Move fields from status to main object representation."""
representation = super().to_representation(obj)
status_representation = representation.pop('status')
for key in status_representation:
representation[key] = status_representation[key]
return representation
you can try change serializer to like this. I assum your ObjectType have field is name for line code object_type.name
class ObjectStatusSimplifiedSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
object_type = serializers.SerializerMethodField()
#staticmethod
def get_name(instance):
return instance.status.name
#staticmethod
def get_object_type(instance):
return instance.status.object_type.name
class Meta:
model = ObjectStatus
fields = ['id', 'name', 'object_type', 'object_status', 'object_status_timestamp']
class ObjectStatusListSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField()
#staticmethod
def get_status(instance):
queryset = ObjectStatus.objects.filter(baseobject_id=instance).order_by('-object_status_timestamp')[:1]
if queryset.count():
return ObjectStatusSimplifiedSerializer(queryset, many=True).data
return []
class Meta:
model = BaseObject
fields = ['id', 'name', 'object_type', 'status']

Django nested Serializer filter to only one field, not all fields

I have two serializers like below. The output for the below snippet is Workers and with associated Ticket Counter details with all fields (ticket_counter,ticket_counter_name,worker). But I just need only one field that is ticket_counter_name.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = WorkerToCounterSerializer(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
class WorkerToCounterSerializer(serializers.ModelSerializer):
ticket_counter = SerializerMethodField()
ticket_counter_name = serializers.CharField(source='ticket_counter.ticket_counter_name')
class Meta:
model = WorkerToTicketCounter
list_serializer_class = FilteredListSerializer
fields = (
'ticket_counter',
'ticket_counter_name',
'worker',
)
def get_ticket_counter(self, obj):
return obj.ticket_counter.pk
class FilteredListSerializer(ListSerializer):
def to_representation(self, data):
data = data.filter(worker_to_ticket_counter_is_deleted=False)[:1]
return super(FilteredListSerializer, self).to_representation(data)
What above snippet outputs
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
{
"ticket_counter": 7,
"ticket_counter_name": "Entrance Counter",
"worker": 4,
}
]
}
But What I want is
{
"username": "xxxxxxxxxxx",
"ticket_counter": "Entrance Counter"
}
I just need the name of the ticket_counter_name. In my case, there can't be two ticket_counters for a worker. Obviously, it gives only one ticket_counter. Is it possible?
EDIT: using string StringRelatedField
{
"username": "xxxxxxxxxxx",
"ticket_counter": [
"Entrance Counter",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx",
"xxxxxxxxxxxxxxxx"
]
}
EDIT: WorkerToTicketCounter Model
class WorkerToTicketCounter(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ticket_counter = models.ForeignKey(TicketCounter, related_name="workers")
worker = models.ForeignKey(User, related_name='ticket_counter')
worker_to_ticket_counter_is_deleted = models.BooleanField(default=False)
If I'm understood correctly, you only need a SerializerMethodField to perform both filtering and string represantion.
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = serializers.SerializerMethodField(read_only=True)
def get_ticket_counter(self, user):
qs = user.ticket_counter.filter(worker_to_ticket_counter_is_deleted=False)
if qs.exists() and hasattr(qs.first().ticket_counter, 'ticket_counter_name'):
return qs.first().ticket_counter.ticket_counter_name
return None
class Meta:
model = User
fields = ('username', 'ticket_counter',)
You can use StringRelatedField:
class WorkerSerializer(serializers.ModelSerializer):
ticket_counter = StringRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = (
'username',
'ticket_counter',
)
Note to use StringRelatedField you should add __str__ method to your WorkerToTicketCounter model:
class WorkerToTicketCounter:
...
def __str__(self):
return self.ticket_counter.ticket_counter_name

how to display tabularinline in api django rest framework?

I am with the demand of a system to manage the schedule of a cinema and to generate an api.
models.py
class Movie(models.Model):
title = models.CharField('título', max_length=250)
synopsis = models.TextField('sinopse', max_length=500)
year = models.IntegerField('ano')
# ... #
class Exhibition(models.Model):
movie = models.ForeignKey(Movie, verbose_name='Filme')
start = models.DateField('Início')
finish = models.DateField('Encerramento')
class Schedule(models.Model):
CINE_DERBY = 'CD'
CINE_CASAFORTE = 'CCF'
CINEMA = (
(CINE_CASAFORTE, 'Cinema Casa Forte'),
(CINE_DERBY, 'Cinema Derby')
)
data = models.DateTimeField('data')
local = models.CharField('local', max_length=5, choices=CINEMA)
exhibition = models.ForeignKey(Exhibition, verbose_name='Em cartaz')
admin.py
class ScheduleInline(admin.TabularInline):
model = Schedule
extra = 1
class MovieModelAdmin(admin.ModelAdmin):
list_display = ['title', 'synopsis', 'year']
class ExhibitionModelAdmin(admin.ModelAdmin):
inlines = [ScheduleInline]
list_display = ['movie', 'start', 'finish']
serializer.py
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
depth = 1
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ['id', 'data', 'local', 'exhibition']
depth = 1
class ExhibitionSerializer(serializers.ModelSerializer):
movie = MovieSerializer(read_only=True)
movieId = serializers.PrimaryKeyRelatedField(write_only=True,
queryset=Movie.objects.all(),
source='movie')
schedule = ScheduleSerializer(many=True, read_only=True)
class Meta:
model = Exhibition
fields = ['movie', 'movieId', 'start', 'finish', 'schedule']
views.py
class MovieListViewSet(viewsets.ModelViewSet):
serializer_class = MovieSerializer
queryset = Movie.objects.all()
class ScheduleListViewSet(viewsets.ModelViewSet):
serializer_class = ScheduleSerializer
queryset = Schedule.objects.all()
class ExhibitionListViewSet(viewsets.ModelViewSet):
serializer_class = ExhibitionSerializer
queryset = Exhibition.objects.all()
I'm having trouble getting the movie times displayed on the display. I did based on the documentation of nested relationships, but the inline tabular part does not work: schedule is not displayed.
I would like api to generate the following:
[
{
"movie": {
"id": 1,
"title": "Vingadores: Guerra Infinita",
"synopsis": "Homem de Ferro, Thor, Hulk e os Vingadores se unem para combater seu inimigo mais poderoso, o maligno Thanos. Em uma missão para coletar todas as seis pedras infinitas, Thanos planeja usá-las para infligir sua vontade maléfica sobre a realidade.",
"year": 2018,
},
"schedule": [
{
"id": 1,
"data": "2018-04-26T14:00:00Z",
"local": "CFD",
},
{
"id": 2,
"data": "2018-05-03T20:00:00Z",
"local": "CFCF",
},
],
"start": "2018-04-30",
"finish": "2018-08-24"
}
]
The problem you are likely hitting is that DRF is looking for a field or a property on your Exhibition model called schedule but this doesn't exist.
I don't believe DRF can handle a reverse relation using just a field definition, you have to be more specific. Luckily DRF does make it easy to be more specific.
You can make use of the SerializerMethodField.
For example:
class ExhibitionSerializer(serializers.ModelSerializer):
movie = MovieSerializer(read_only=True)
movieId = serializers.PrimaryKeyRelatedField(write_only=True,
queryset=Movie.objects.all(),
source='movie')
schedule = serializers.SerializerMethodField()
class Meta:
model = Exhibition
fields = ['movie', 'movieId', 'start', 'finish', 'schedule']
def get_schedule(self, obj):
return [ScheduleSerializer(s).data for s in obj.schedule_set.all()]
It work for me. Thank you.
Take in addition for one_to_one relations. ))
#staticmethod
def get_picture(obj):
return PictureSerializer(obj.picture).data if hasattr(obj, 'picture') else 'no_picture_found'

django rest framework return a custom object using ModelSerializer and ModelViewSet

I have three models, three serializers, one modelviewset below.
I am using django-rest-framework to make a rest api for android.
The restaurant model was created first. Then I created a star model and an image model.
What I want to do is to add star and image objects into restaurant objects.
finally I've got what I want result but I think my viewset code looks like wrong..
Is there another way not to use "for loop"?
Models
class Restaurant(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
weather = models.ForeignKey(Weather, on_delete=models.CASCADE)
distance = models.ForeignKey(Distance, on_delete=models.CASCADE)
description = models.TextField('DESCRIPTION')
def __str__(self):
return self.name
class Star(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
rating = models.IntegerField('RATING')
def __str__(self):
return self.restaurant
class RestaurantImage(models.Model):
id = models.AutoField(primary_key=True)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
path = models.CharField(max_length=255)
Serializer
class StarSerializer(serializers.ModelSerializer):
class Meta:
model = Star
fields = ('id', 'restaurant', 'user', 'rating', )
class RestaurantDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer()
weather = WeatherSerializer()
distance = DistanceSerializer()
class Meta:
model = Restaurant
fields = ('id', 'name', 'address', 'category', 'weather',
'distance', 'description', )
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantImage
fields = ('id', 'path', 'restaurant')
ViewSet
class RestaurantDetailInfoViewSet(viewsets.ModelViewSet):
queryset = Restaurant.objects.all()
serializer_class = RestaurantSerializer
def list(self, request, *args, **kwargs):
restaurant_list = Restaurant.objects.all()
restaurant_result = []
for restaurant in restaurant_list:
restaurantInfo = Restaurant.objects.filter(id=restaurant.pk)
restaurant_serializer = RestaurantDetailSerializer(restaurantInfo, many=True)
ratingAverageValue = Star.objects.filter(restaurant=restaurant.pk).aggregate(Avg('rating'))
images = RestaurantImage.objects.filter(restaurant=restaurant.pk)
image_serializer = ImageSerializer(images, many=True)
restaurant_dic = {
'restaurant': restaurant_serializer.data,
'ratingAverage': ratingAverageValue['rating__avg']
if ratingAverageValue['rating__avg'] is not None else 0,
'images': image_serializer.data
}
restaurant_result.append(restaurant_dic)
return Response(restaurant_result)
Result
[
{
"restaurant": [
{
"id": 1,
"name": "restaurant1",
"address": "address1",
"category": {
"c_id": 1,
"name": "foodtype1"
},
"weather": {
"w_id": 1,
"name": "sunny"
},
"distance": {
"d_id": 1,
"name": "inside"
},
"description": "description1"
}
],
"ratingAverage": 2.6667,
"images": [
{
"id": 1,
"path": "imagepath",
"restaurant": 1
}
]
},
Solution:
class RestaurantDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer()
weather = WeatherSerializer()
distance = DistanceSerializer()
images = ImageSerializer(many=True, read_only=True)
ratingAverage = serializers.SerializerMethodField(read_only=True)
def get_ratingAverage(self, restaurant):
ratingAvgVal = Star.objects.filter(
restaurant=restaurant
).aggregate(Avg('rating'))['rating__avg']
return ratingAvgVal if ratingAvgVal is not None else 0
class Meta:
model = Restaurant
fields = ('id', 'name', 'address', 'category', 'weather',
'distance', 'description', 'images', 'ratingAverage', )
Explanation:
Here, I have nested the ImageSerializer in the RestaurantSerializer class, since you needed all the fields you've defined in ImageSerializer.
Then, for ratingAverage, I have used the SerializerMethodField which returns the value calculated (your logic) in the method I've defined for it, i.e. get_ratingAverage, which takes the Restaurant instance reference passed as an argument to the method for the field.