How to group a queryset by a specific field in Django? - django

I have a qs qs_payments that returns the following:
[
{
"id": 19,
"month": "2021-05-01",
"amount": 3745,
"offer": 38
},
{
"id": 67,
"month": "2021-03-01",
"amount": 4235,
"offer": 39
},
{
"id": 68,
"month": "2021-04-01",
"amount": 4270,
"offer": 39
},
{
"id": 69,
"month": "2021-05-01",
"amount": 4305,
"offer": 39
}
]
I now try to re-group the data so that each month contains all items of that particular month (in the given example there would be two nested items for May/2021-05-21)
I tried the following using itertools but this returns Payment object is not subscriptable. Context is that I want to render one component with its line items in React.js.
class PayrollRun(APIView):
...
payment_runs = [{'subject': k, 'Results': list(g)} for k, g in itertools.groupby(qs_payments, key=itemgetter('month'))]
print(payment_runs)
serializer = PaymentSerializer(qs_payments, many=True)
return Response(serializer.data)
The desired outcome is s.th. like (if that makes sense?)
[
{
"2021-05-01": [{
"id": 69,
"amount": 4305,
"offer": 39
},
{
"id": 19,
"amount": 3745,
"offer": 38
}],
},
"2021-04-01": [{
...
}],
]
Update to try to even simpler approach with a related field to Period
# models.py
class Period(models.Model):
period = models.DateField()
class Payment(models.Model):
offer = models.ForeignKey(Offer, on_delete=models.CASCADE)
month = models.ForeignKey(Period, on_delete=models.CASCADE)
amount = models.PositiveIntegerField()
# serializers.py
class PaymentSerializer(serializers.ModelSerializer):
class Meta:
model = Payment
fields = '__all__'
class PeriodSerializer(serializers.ModelSerializer):
payments = serializers.SerializerMethodField()
class Meta:
model = Period
fields = '__all__'
# views.py
def get(self, request):
# Loop payment table for company related payments, create dict of dict for each month
# Get todays date
today = datetime.date(datetime.now())
# Get the related company according to the logged in user
company = Company.objects.get(userprofile__user=request.user)
# Get a queryset of payments that relate to the company and are not older than XX days
qs_payments = Payment.objects.filter(offer__company=company)
print(qs_payments)
serializer = PeriodSerializer(qs_payments, many=True)
return Response(serializer.data)

What about something like this?
models.py
class Period(...):
date = models.DateField(...)
Then in your serializers:
serializers.py
class PaymentSerializer(...):
...
class PeriodSerializer(...):
payments = serializers.SerializerMethodField()
def get_payments(self, obj):
payments = Payment.objects.filter(
month = obj.date
)
return PaymentSerializer(payments, many=True).data
class Meta:
fields = ['payments', ...]
model = Month
EDIT:
You can simplify this further by adding a foreign key relation to a Month on your Payment model, then you don't need to write the get_payments method, you can just serialize the Payments on each Month directly.
This would look like:
models.py
class Period(...):
date = models.DateField(...)
class Payment(...):
offer = models.ForeignKey(Offer, on_delete=models.CASCADE)
month = models.ForeignKey(Period, on_delete=models.CASCADE, related_name='payments') # <-- this
amount = models.PositiveIntegerField()
serializers.py
class PaymentSerializer(...):
...
class PeriodSerializer(...):
payments = PaymentSerializer(read_only=True, many=True)
class Meta:
fields = ['payments', ...]
model = Month

Related

Django REST serialize field group by date

Problem: Not able to group my JSON output by date
SOLUTION IN BOTTOM OF THIS POST
I am serializing a model and getting this output:
[
{
"date": "2020-11-24",
"name": "Chest",
"workout": {
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 80
}
},
{
"date": "2020-11-24",
"name": "Chest",
"workout": {
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 85
}
},
{
"date": "2020-11-24",
"name": "Chest",
"workout": {
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 90
}
},
I want to get it like the JSON below and group it by date.
[
{
"date": "2020-11-24",
"workout": {
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 80
},
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 85
},
"name": "Chest",
"exercise": 1,
"repetitions": 10,
"weight": 90
},
}
]
I have one model:
class WorkoutLog(models.Model):
date = models.DateField()
name = models.CharField(max_length=50) #When save() name = Workout.name
exercise = models.ForeignKey('Exercise', related_name='log', on_delete=models.CASCADE)
repetitions = models.IntegerField()
weight = models.IntegerField()
Trying to serialize and group the JSON by date:
class WorkoutLogSerializer(serializers.ModelSerializer):
class Meta:
model = WorkoutLog
fields = ['date', 'workout']
workout = serializers.SerializerMethodField('get_workout')
def get_workout(self, obj):
return {
'name': obj.name,
'exercise': obj.exercise_id,
'repetitions': obj.repetitions,
'weight': obj.weight,
}
The code lets me custom the field layout, but not really grouping it by date. Do you have any suggestions on how to structure it?
Many thanks for all help!
in case its needed, here is my view.py
def workout_log(request):
if request.method == 'GET':
workout_log = WorkoutLog.objects.all()
serializer = WorkoutLogSerializer(workout_log, many=True)
return JsonResponse(serializer.data, safe=False)
SOLUTION BY Mahmoud Adel:
views.py
def workout_log(request):
if request.method == 'GET':
workout_log = WorkoutLog.objects.order_by('date').values('date').distinct()
serializer = WorkoutLogSerializer(workout_log, many=True)
return JsonResponse(serializer.data, safe=False)
serializers.py
class WorkoutFieldSerializer(serializers.Serializer):
name = serializers.CharField()
#exercise = serializers.IntegerField()
repetitions = serializers.IntegerField()
weight = serializers.IntegerField()
class WorkoutLogSerializer(serializers.ModelSerializer):
class Meta:
model = WorkoutLog
fields = ['date', 'workout']
workout = serializers.SerializerMethodField('get_workout')
def get_workout(self, obj):
workouts = WorkoutLog.objects.filter(date=obj['date'])
workout_serializer = WorkoutFieldSerializer(workouts, many=True)
return workout_serializer.data
You can do something like this
Let's start first with your view, I will tweak the queryset like that
def workout_log(request):
if request.method == 'GET':
workout_log = WorkoutLog.objects.order_by('date').distinct('date').only('date')
serializer = WorkoutLogSerializer(workout_log, many=True)
return JsonResponse(serializer.data, safe=False)
Then on your WorkoutLogSerializer
class WorkoutLogSerializer(serializers.ModelSerializer):
class Meta:
model = WorkoutLog
fields = ['date', 'workout']
workout = serializers.SerializerMethodField('get_workout')
def get_workout(self, obj):
workouts = WorkoutLog.objects.filter(date=obj.date)
workout_serializer = WorkoutSerializer(workouts, many=True)
return workout_serializer.data
And finally, create WorkoutSerializer
class WorkoutSerializer(serializers.Serializers):
name = serializers.CharField()
exercise = serializers.IntegerField()
repetitions = serializers.IntegerField()
weight = serializers.IntegerField()
The previous way will first be got to the DB to get the distinct dates then on WorkoutLogSerializer will use each date to select the corresponding objects that have it, then we serialize those objects.
We get the intended result by doing so, note that this will result in 2 DB hits, there may be another way that will do it in one DB hit, I will update my answer if I have figured it out
NOTE: I have written this to explain the flow and the logic I didn't run it, although it should work I may forget something that will show an error for you, feel free to try it.
UPDATE: check this answer comments if you are using SQLite.

serializer only certain fields in foreing key relation

I'm trying to serializer two nested models linked by a foreing key:
class Category(models.Model):
sp = models.ForeignKey('species.Sp', on_delete=models.CASCADE, related_name='species_category')
category_name = models.CharField(max_length=50)
class Catch(models.Model):
weight = models.IntegerField()
category = models.ForeignKey('species.Category', on_delete=models.CASCADE,)
I know is possible to use depth option, but it serialize all the fields of the related model, for example:
class CatchesSerializer(serializers.ModelSerializer):
class Meta:
model = Catch
fields = ['id', 'weight', 'category', ]
depth = 1
returns
[
{
"id": 335,
"weight": 4710,
"category": {
"id": 1,
"category_name": "1",
"sp": 41
}
},
...
]
How is the way to serialize only certains fields of the related model? for example:
[
{
"id": 335,
"weight": 4710,
"category": {
"sp": 41,
}
},
...
]
Serializer can be nested, you can try:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['sp']
class CatchesSerializer(serializers.ModelSerializer):
category = CategorySerializer()
class Meta:
model = Catch
fields = ['id', 'weight', 'category']

Reformat Django REST Framework Serializer to get Output

I am trying to get data in a particular format but i'm not able to get the desired output.
My Model:
class Category(models.Model):
name = models.CharField(max_length=40)
class Expense(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category_name")
description = models.CharField(max_length=200)
total_amount = models.IntegerField()
class Expense_Details(models.Model):
expense = models.ForeignKey(Expense, on_delete=models.CASCADE, related_name="payment")
user = models.IntegerField()
amount = models.FloatField()
type = models.CharField(max_length=100) ---->type is owe or lend
When I request /api/expenses/:
Expected Output
{
“total_expenses”: 10,
“Expenses”:
[{
“id”: 1,
“category”: 1,
“created_by”: 1, ------> user logged id
“description”: “lunch”,
“total_amount”: “105”,
“owe”: [{
“user_id”: 1,
“amount”: 10
},
{
“user_id”: 2,
“amount”: 95
}],
“lend”: [{
“user_id”: 3,
“amount”: 10
},
{
“user_id”: 4,
“amount”: 95
}],
}, ...
]
}
My output:
{
"results": [
{
"id": 1,
"category": 1,
"description": "lunch at the burj al arab",
"total_amount": 105,
"payment": [
{
"user": 1,
"amount": -10
},
{
"user": 2,
"amount": -95
},
{
"user": 3,
"amount": 10
},
{
"user": 4,
"amount": 95
}
]
}
]
}
My Serializer:
class ExpenseDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = Expense_Details
fields = ['user', 'amount']
class ExpenseSerializer(serializers.ModelSerializer):
payment = serializers.SerializerMethodField()
def get_payment(self, obj):
return ExpenseDetailsSerializer(obj.payment.all(), many=True).data
class Meta:
model = Expense
fields = ['id', 'category', 'description', 'total_amount', 'payment',]
What Query should I use to get Output in the above format? How will my serializer look like? How can I separate own and lend? Also I have stored own and lend with + and - sign to differentiate between them.
Use a ListField for the same.
Documentation: https://www.django-rest-framework.org/api-guide/fields/#listfield
Also refer How to serialize an 'object list' in Django REST Framework
Here you can try something like:
class ExpenseSerializer(serializers.Serializer):
payment = serializers.ListField(child=ExpenseDetailsSerializer())
def get_payment(self, obj):
return ExpenseDetailsSerializer(obj.payment.all(), many=True).data
class Meta:
model = Expense
fields = ['id', 'category', 'description', 'total_amount', 'payment',]

How can I serialize list with an object relation produced from queryset TruncYear in Django 1.11

I manage to get time series data with TruncYear/TruncMonth/TruncDay/etc like below from Tracking table. However the data for the venue just produce the venue_id. I would like to have that serialized so that it returns the "name" from the relation Venue table.
I am using Django 1.11 a postgres 9.4
Here is my time series code:
tracking_in_timeseries_data = Tracking.objects.annotate(
year=TruncYear('created_at')).values('year', 'venue_id').annotate(
count=Count('employee_id',
distinct = True)).order_by('year')
return Response(tracking_in_timeseries_data, status=status.HTTP_200_OK)
currently it output like this:
[
{
"venue_id": 4,
"year": "2017-01-01T00:00:00Z",
"count": 1
},
{
"venue_id": 2,
"year": "2018-01-01T00:00:00Z",
"count": 2
},
{
"venue_id": 6,
"year": "2019-01-01T00:00:00Z",
"count": 1
}
]
I want to explode venue data to return the id & name like this:
[
{
"venue": {
id: 4,
name: "room A"
},
"year": "2017-01-01T00:00:00Z",
"count": 1
},
{
"venue": {
id: 2,
name: "room B"
},
"year": "2018-01-01T00:00:00Z",
"count": 2
},
{
"venue": {
id: 6,
name: "room C"
},
"year": "2019-01-01T00:00:00Z",
"count": 1
}
]
How to explode the "venue" to return the id and name ? The name is useful for presentation purpose.
UPDATE (here are some attempts that failed):
this only displays count but accumulative ( https://gist.github.com/axilaris/0cd86cec0edf675d654eadb3aff5b066). something weird and not sure why.
class TimeseriesSerializer(serializers.ModelSerializer):
venue = VenueNameSerializer(source="venue_id",many=False, read_only=True)
year = serializers.TimeField(read_only=True)
count = serializers.IntegerField(read_only=True)
class Meta:
model = Tracking
fields = ("venue",
"year",
"count")
class TimeseriesSerializer(serializers.Serializer): <-- here is another try but doesnt work serializers.Serializer
venue_id = VenueNameSerializer(many=False, read_only=True)
year = serializers.TimeField(read_only=True)
count = serializers.IntegerField(read_only=True)
I think the answer is quite close to this: django rest framework serialize a dictionary without create a model
FYI, this is my actual code (must as well put it here) for the test, names may differ slightly but the whole intention is the same. https://gist.github.com/axilaris/919d1a20d3e799101a8cf6aeb4d120b5
What you need is to create a serializer for the Venue which displays the id and name fields and use it as a field in the TrackingSerializer.
In your case there is something more to consider: since you are using values to group, what you get from the queryset is not a Tracking object, thus your venue_id can't be translated to a Venue object by DRF.
To fix this you need to override the to_representation method of VenueSerializer to get the Venue object from its primary key.
I'm including models and views to give you a working example, but you probably only need serializers and the adjusted queryset from the view.
Models
class Employee(models.Model):
first_name = models.CharField(max_length=128)
last_name = models.CharField(max_length=128)
class Venue(models.Model):
name = models.CharField(max_length=128)
class Tracking(models.Model):
venue = models.ForeignKey(Venue)
employee = models.ForeignKey(Employee)
created_at = models.DateTimeField(auto_now_add=True)
Views
class TrackingViewSet(viewsets.ModelViewSet):
queryset = (
Tracking.objects
.annotate(
year=ExtractYear('created_at'),
)
.values('year', 'venue')
.annotate(
count=Count('employee_id', distinct=True),
)
.order_by('year')
)
serializer_class = TrackingSerializer
Serializers
class VenueSerializer(serializers.ModelSerializer):
class Meta:
model = Venue
fields = ['id', 'name']
def to_representation(self, value):
value = Venue.objects.get(pk=value)
return super(VenueSerializer, self).to_representation(value)
class TrackingSerializer(serializers.ModelSerializer):
venue = VenueSerializer(read_only=True)
year = serializers.IntegerField(read_only=True)
count = serializers.IntegerField(read_only=True)
class Meta:
model = Tracking
fields = ['venue', 'year', 'count']
Note that I replaced your TruncYear with ExtractYear, the difference between the two is that the former returns a datetime, the latter an int which is what you want I guess.
If you prefer to use TruncYear you will have to replace:
year = serializers.IntegerField(read_only=True)
with:
year = serializers.DateTimeField(read_only=True)
in TrackingSerializer.

Dictinct Row in queryset using Django Rest Framework

I want to remove the duplicate row, distinct row in a Mysql DB.
"Prpk" has a relation with "MeteoZone" (meteozone).
I've a model like this :
class MeteoZone(models.Model):
name = models.CharField(max_length=45, unique=True)
communes = models.ManyToManyField(Commune, related_name='meteozone', blank=True)
massifs = models.ManyToManyField(Massif, related_name='meteozone', blank=True)
def __str__(self):
return self.name
class Prpk(models.Model):
begin = models.FloatField()
end = models.FloatField()
status = models.CharField(max_length=15)
# Relations
report = models.ForeignKey(Report, on_delete=models.PROTECT, unique=False)
meteozone = models.OneToOneField(MeteoZone, related_name='prpk', on_delete=models.PROTECT, null=False, unique=False)
highway = models.OneToOneField(Highway,related_name='prpk', on_delete=models.PROTECT, null=True, unique=False)
department = models.OneToOneField(Department,on_delete=models.PROTECT, null=True, unique=False)
def __str__(self):
return ("%s-%s" % (self.begin,self.end))
And a serializer :
class PrpkSerializer(serializers.ModelSerializer):
# Get 'meteozones', 'communes' and 'massifs'
meteozone = MeteozoneSerializer(read_only=True)
begin = serializers.FloatField()
end = serializers.FloatField()
status = serializers.CharField(max_length=15)
class Meta:
model = Prpk
fields = ('begin', 'end', 'status', 'meteozone')
ordering = ('begin', )
In my view, in the query_set, I want to display only the unique records.
class PRPKViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows prpk to be viewed
"""
serializer_class = PrpkSerializer
queryset = Prpk.objects.all().order_by('begin').distinct()
# Authentification !
permission_classes = (IsAuthenticated,)
# Only 'get' method
http_method_names = ['get']
#method_decorator(vary_on_cookie)
#method_decorator(cache_page(60*60*1))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
""" allow rest api to filter by submissions """
#queryset = Prpk.objects.all().order_by('begin').values_list('begin', flat=True).distinct()
queryset = Prpk.objects.all().order_by('begin').distinct()
highway = self.request.query_params.get('highway', None)
if highway is not None:
queryset = queryset.filter(highway=highway)
return queryset
Look at a part of records :
id begin end status commune_id department_id highway_id massif_id meteozone_id report_id
76 25 25.6 1 NULL 13 2 45 40 1
77 25 25.6 1 NULL 13 2 161 40 1
In this records, I just one only one record in json, but DRF returns 2 records, like this :
[
{
"begin": 25,
"end": 25.6,
"status": "1",
"meteozone": {
"communes": [],
"massifs": [
{
"id": 45,
"name": "abc"
},
{
"id": 161,
"name": "def"
}
],
"id": 40,
"name": "133"
}
},
{
"begin": 25,
"end": 25.6,
"status": "1",
"meteozone": {
"communes": [],
"massifs": [
{
"id": 45,
"name": "abc"
},
{
"id": 161,
"name": "def"
}
],
"id": 40,
"name": "133"
}
},
{
I add a "distinct" method, but it's not working. Can U help me please ? :)
F.