I need to group the result of a queryset by date on DRF
""" Django model """
class Matches(models.Model):
name = models.CharField(max_length=100)
platform = models.CharField(max_length=100)
mode = models.CharField(max_length=100)
kills = models.IntegerField()
date = models.DateTimeField()
""" Serializer """
class MatchesSerializer(serializers.ModelSerializer):
class Meta:
model = models.Matches
fields = ('name', 'platform', 'mode', 'kills', 'date')
""" views """
class Matches(generics.ListAPIView):
serializer_class = serializers.MatchesSerializer
filter_backends = (filters.OrderingFilter,)
lookup_field = 'name'
ordering = ('-date',)
def get_queryset(self):
username = self.kwargs['name']
return models.Matches.objects.filter(name=username)
Desired output (just an example):
[
{
'date':'2019-01-01',
'data':[
{
'platform':'ps4',
'mode':'solo',
'kills':10,
'date':'2019-01-01 10:00:00'
},
{
'platform':'ps4',
'mode':'duo',
'kills':10,
'date':'2019-01-01 12:00:00'
},
{
'platform':'ps4',
'mode':'squad',
'kills':10,
'date':'2019-01-01 11:00:00'
},
]
},
{
'date':'2019-01-02',
'data':[
{
'platform':'ps4',
'mode':'solo',
'kills':1,
'date':'2019-01-02 10:00:00'
},
{
'platform':'ps4',
'mode':'duo',
'kills':2,
'date':'2019-01-02 12:00:00'
},
{
'platform':'ps4',
'mode':'squad',
'kills':3,
'date':'2019-01-02 11:00:00'
},
]
}
]
For me, the easy solution is to make a raw querie on django and create a serializer, but it feels not so pythonic...
So it appears that DRF has some beautiful way to make it look nice, maybe using to_representation...
I used the itertools.groupby iterator. Check my code below.
from itertools import groupby
events = [["2020-04-01", "A"], ["2020-04-01", "B"], ["2020-04-02", "C"], ["2020-04-02", "D"]]
for k, g in groupby(events, lambda x: x[0]):
list = []
for group in g:
list.append(group[1])
output[k] = list
The output will be grouped by date as follows
{'2020-04-01': ['A', 'B'], '2020-04-02': ['C', 'D']}
Please make sure your order by date first.
You can use the function raw from Django ORM
results = Matches.objects.raw('SELECT * FROM myapp_matches GROUP BY date')
Related
I am trying to achieve is to get all comments based on photo_id, and count the total replies at the same time.
models.py:
class PhotoComment(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
photo = models.ForeignKey(UserPhoto,on_delete=models.CASCADE)
comment_text = models.TextField(max_length = 500)
comment_reply = models.ForeignKey('self',related_name='replies',blank=True,null=True,on_delete = models.CASCADE)
...
def __str__(self):
return '%s(photo_%s)' %(self.user,self.photo)
views.py:
class PhotoCommentView(APIView):
def patch(self,request,formate=None):
photo_id = request.data.get('photo_id')
all_comment = list(PhotoComment.objects.filter(photo__id = photo_id,comment_reply = None).values('id','user__username','comment_text','user__profile__profile_pic','vote_upvote').annotate(total_reply = Count('comment_reply')))
return Response({"Data" : all_comment},status=status.HTTP_200_OK)
Real result is like this:
{
"Data": [
{
"id": 12,
"user__username": "pradip",
"comment_text": "this is awesome pic bro....",
"user__profile__profile_pic": "profile_pic/gyroscope_HUq0uJ0.png",
"vote_upvote": 2,
"total_reply": 0⬅⬅⬅⬅
},
...
]
}
What I want for result is like this(Here comment id 12 contain total 3 replies.):
{
"Data": [
{
"id": 12,
"user__username": "pradip",
"comment_text": "this is awesome pic bro....",
"user__profile__profile_pic": "profile_pic/gyroscope_HUq0uJ0.png",
"vote_upvote": 2,
"total_reply": 3⬅⬅⬅⬅
},
....
]
}
I have no idea with this problem I need some help.
If you know any other easy or recommended solution to achieve this please tell me.
Thank you.
As stated by others, properties are refering to a specific object instance. Querysets are used to do things on a range of objects.
Therefore to replicate for a queryset the property you defined for an object instance, you need to setup an annotation:
from django.db.models import Count
all_comment = list(PhotoComment.objects.annotate(total=Count('comment_reply')
).filter(photo__id = request.data.get('photo_id')
).values('id','comment_text','total'))
Hope the general principle explained here will enable you to get to where you want to go
I use SerializerMethodField() in serializer to count comment reply.
serializers.py:
class ShowCommentSerializer(serializers.ModelSerializer):
total_reply = SerializerMethodField()
user_name = serializers.CharField(source='user.username')
profile_pic = serializers.CharField(source='user.profile.profile_pic')
class Meta:
model = PhotoComment
fields = ('id','user_name','profile_pic','comment_text','total_reply','created_date')
read_only_fields = fields
def get_total_reply(self, obj):
total = obj.replies.count()
return total
views.py:
class PhotoCommentView(APIView):
def patch(self,request,formate=None):
photo_id = request.data.get('photo_id')
comment_inst = PhotoComment.objects.filter(photo = photo_id,comment_reply=None)
serializer = ShowCommentSerializer(comment_inst,many=True)
return Response({"Data" : serializer.data},status=status.HTTP_200_OK)
Output:
{
"Data": [
{
"id": 29,
"user_name": "admin",
"profile_pic": "profile_pic/Airbus.png",
"comment_text": "good bro....",
"total_reply": 3,
"created_date": "2021-03-10T02:25:43.940127+05:30"
},
]
}
Let's say I have a model:
class Entry(models.Model):
content = models.CharField(max_length=140)
email = models.EmailField()
upload_date = models.DateTimeField(default=timezone.now)
class Meta:
db_table = 'entries'
What I want to achieve is the viewset to return list of content and upload_date values per single email (since many entries can have the same email value), like that:
[
{
"email": "address1#test.test",
"entries": [
{
"upload_date": "2020-09-03",
"content": "test content 1"
},
{
"upload_date": "2020-09-02",
"content": "test content 2"
},
...
]
},
{
"email": "address2#test.test",
"entries": [
{
"upload_date": "2020-09-03",
"content": "test content 11"
},
{
"upload_date": "2020-09-02",
"content": "test content 12"
},
...
]
},
...
]
I tried messing around with .values() and .annotate(), but with no luck.
Quick answer with groupby itertools function :
def entry_converter(entry):
return {'upload_date': entry.upload_date, 'content': entry.content}
def grouping_key(entry):
return entry.email
from itertools import groupby
entries = Entry.objects.order_by('email')
grouped_entries = [dict(email=key, entries=list(map(entry_converter, group))) for key, group in groupby(entries, key=grouping_key)]
Update 1
Integration with DRF ViewSet
def entry_converter(entry):
return {'upload_date': entry.id, 'content': entry.id}
def grouping_key(entry):
return entry.email
def group_entries(entries):
return [
dict(email=key, entries=list(map(entry_converter, group))) for key, group in groupby(entries, key=grouping_key)
]
class EntrySerializer(ModelSerializer):
class Meta:
model = Entry
fields = '__all__'
class EntryViewSet(ModelViewSet):
queryset = Entry.objects.all()
serializer_class = EntrySerializer
def list(self, request, *args, **kwargs):
queryset = self.get_queryset().order_by('email')
data = group_entries(queryset)
page = self.paginate_queryset(data)
if page is not None:
return self.get_paginated_response(page)
return Response(data)
Keep in mind that I haven't used .filter_queryset() method of ModelViewSet as it may modify the queryset by adding wrong ordering or wrong filtering. Because groupby function needs sorted data as an input. Also, I have not overridden ModelSerializer class for generalizing .list() method of ModelViewSet as it may create extra complexity.
I am trying to order the user profiles based on the timestamp of the last message in between both the users.
I am using SerializerMethodField to get the timestamp of the last message.
is there any way I can sort the data?
class UserProfileSerializer(serializers.ModelSerializer):
lastmessage = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id','lastmessage']
def get_lastmessage(self,obj):
k = self.context.get('view').kwargs['sid']
data =( Message.objects.filter(receiver=obj.id,sender=k) | Message.objects.filter(sender=obj.id,receiver=k)).order_by('-timestamp').values('message','timestamp')
if len(data) == 0:
return ""
else:
data = data.first()
data["timestamp"] = str(data["timestamp"])
return str(data)
My view:
class UserChatViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserProfileSerializer
Now my views return:
[{
"id": 4,
"lastmessage": "{'message': 'random', 'timestamp': '2020-06-14 23:49:33.077749+00:00'}"
},
{
"id": 5,
"lastmessage": ""
},
{
"id": 6,
"lastmessage": "{'message': 'sample', 'timestamp': '2020-06-14 11:53:03.880833+00:00'}"
},
{
"id": 7,
"lastmessage": ""
}]
But I want it to sort based on the timestamp of last message
You can overwrite list in order to achieve this:
def list(self, request, *args, **kwargs):
response = super().list(request, args, kwargs)
# sort response.data['results']
return response
Also, lastmessage can be a dict instead of a str, so it's easier to work with.
The order of your response should be handled in the view .
from django.db.models import Subquery, OuterRef
lm = Message.objects.filter(sender=OuterRef("id"), receiver=self.kwargs['sid']).order_by('-timestamp')
data = User.objects.all().annotate(
lastmessage=Subquery(
lm.values('timestamp')[:1]
)
).order_by('-lastmessage__timestamp')
I'm trying to format data when querying my API. I can retrieve my data like that :
"results": [
{
"Cat1": [
{
"job": String,
"position": Integer
}
]
},
{
"Cat1": [
{
"job": String,
"position": Integer
}
]
},
{
"Cat2": [
{
"job": String,
"position": Integer
}
]
}
]
But I want something like that:
"results": [
{
"Cat1": [
{
"job": String,
"position": Integer
},
{
"job": String,
"position": Integer
}
]
},
{
"Cat2": [
{
"job": String,
"position": Integer
}
]
}
]
I use a serializer like this:
class CustomSerializer(serializers.ModelSerializer):
category = CatSerializer()
job = JobSerializer()
class Meta:
model = MyModel
fields = '__all__'
def to_representation(self, value):
return {
value.category.name: [{"job": value.job.name,
"position": value.position, }]
cat1 and cat2 are dynamics, they are from another table. I don't understand how to create my arrays properly using those serializers. The category is a #Property field in my model who's a foreign key of job.
My models:
class MyModel(models.Model):
CHOICES = [(i, i) for i in range(4)]
partner = models.ForeignKey(Partner, on_delete=models.CASCADE)
job = models.ForeignKey(
Job, on_delete=models.CASCADE)
position = models.IntegerField(choices=CHOICES)
#property
def category(self):
return self.job.category.domain
def __str__(self):
return '%s | %s | %s | position: %s' % (self.partner.name, self.domain.name, self.job.name, self.position)
class Job(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
code = models.CharField(
max_length=255, unique=True)
name = models.CharField(
max_length=255)
class Category(models.Model):
domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
code = models.CharField(
max_length=5)
name = models.CharField(max_length=255)
hourly_rate = models.FloatField(
null=True, blank=True)
How should I deal with serializers to format my data properly ?
EDIT:
I ended with something like that, except for the ListSerializer.
I used 2 ModelSerilizers
class MyModelCustomSerializer(serializers.ModelSerializer):
position = serializers.IntegerField(read_only=True)
job = serializers.CharField(source='job.name', read_only=True)
class Meta:
model = MyModel
fields = ['job', 'position']
def to_representation(self, value):
return {"position": value.position,
"job": {"name": value.job.name, "slug": value.job.slug,
"title": value.job.seo_title}
}
And
class CategoryCustomSerializer(serializers.ModelSerializer):
models = MyModelustomerSerializer(many=True)
class Meta:
model = Category
fields = ['category', 'MyModel']
def to_representation(self, value):
filters = {'job__category__domain__name': value.name}
myModels = MyModel.objects.filter(**filters)
serializer = MyModelCustomSerializer(instance=myModels, many=True,)
return {value.name: serializer.data}
But if I try to use a jobSerializer who already exist instead of
"job": {"name": value.job.name, "slug": value.job.slug,
"title": value.job.seo_title}
},
I got this error: Object of type 'Job' is not JSON serializable, but it's working anyway because i don't need all fields
I would go the direction of implementing a custom ListSerializer for the ModelSerializer and overriding its to_representation method.
from rest_framework import serializers
from collections import OrderedDict
class CustomListSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
list_rep = OrderedDict()
for item in iterable:
child_rep = self.child.to_representation(item)
k, v = list(child_rep.items()).pop()
list_rep.setdefault(k, []).append(v)
return [
{k: v}
for k, v in list_rep.items()
]
Then set the model Meta to use it
class CustomSerializer(serializers.ModelSerializer):
category = CatSerializer()
job = JobSerializer()
class Meta:
model = MyModel
fields = '__all__'
list_serializer_class = CustomListSerializer
def to_representation(self, value):
return {
value.category.name: [{"job": value.job.name,
"position": value.position, }]
My API endpoint is http://127.0.0.1:7009/apps/2/versions
I am unable to filter, order and search the particular version data
models.py:
class App(models.Model):
app_id = models.CharField(max_length=40,help_text="app_id of the app",unique=True)
name=models.CharField(max_length=40,help_text="name of the app")
class AppVersion(models.Model):
app_version = models.CharField(max_length=200)
app_description = models.CharField(max_length=200,blank=True)
Views.py :
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter,SearchFilter
class AppVersionListView(mixins.ListModelMixin,mixins.CreateModelMixin,generics.GenericAPIView):
lookup_field = 'pk'
serializer_class = appVersionsSerializer
queryset = AppVersion.objects.all()
filter_backends = (DjangoFilterBackend, OrderingFilter,SearchFilter,)
filter_fields = ('id','app_version','app_description ')
ordering_fields = '__all__'
search_fields =('app_version','app_description')
serializers.py : (class for app_versions)
class appVersionsSerializer(QueryFieldsMixin,serializers.ModelSerializer):
class Meta:
model = AppVersion
fields = ('apps','app_version','app_description')
I want to filter the versions data based on "app_description" i.e.,
GET:
http://127.0.0.1:7009/apps/2/versions?app_description =FileCommander
Expected Output :
Should filter the data and return results which is having that description
[
{
"apps": 2,
"app_version": "v1",
"app_description ":"FileCommander"
}]
Actual Output :
[
{
"apps": 2,
"app_version": "v1",
"app_description ":"FileCommander"
},
{
"apps": 2,
"app_version": "v2",
"app_description ":"cooking"
}]
How can I search , filter or order the versions data?