Best way to add bool field to Django queryset/serializer - django

I have two models
class A(models.Model):
name = models.CharField(max_length=32)
...
class B(models.Model):
fkey = models.ForeignKey("A", on_delete=models.CASCADE)
...
I want to create queryset of objects A based on condition if some of B objects is referring to A in my DRF serializer
a1 = A.objects.create(name="1")
a2 = A.objects.create(name="2")
b1 = B.objects.create(fkey=a1)
a_objs = A.objects.filter()
serializer = ASerializer(a_objs, many=True)
serializer.data
{
{
"name": "1"
},
"b_attached": true
},
{
{
"name": "2"
},
"b_attached": false
}
What is the best way to achieve this?

You'r response is an invalid json!
you can use nested serialization to achieve something like this:
from rest_framework import serializers
from .models import A, B
class ADetailSerializer(serializers.ModelSerializer):
class Meta:
model = A
fields = ("name",)
class ASerializer(serializers.ModelSerializer):
A_OBJ = serializers.SerializerMethodField(method_name='get_a_obj')
b_attached = serializers.SerializerMethodField(method_name='get_b_attached')
class Meta:
model = A
fields = ("A_OBJ", "b_attached")
def get_b_attached(self, obj):
if obj.b_set.exists():
return True
return False
def get_a_obj(self, obj):
return ADetailSerializer(obj, many=False).data
and the response will be:
[
{
"A_OBJ": {
"name": "1"
},
"b_attached": true
},
{
"A_OBJ": {
"name": "2"
},
"b_attached": false
}
]

Related

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 Rest Framework - Group data by dynamic Key

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, }]

Django DRF - Group by date

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')

Change Django-Rest-Framework serializer from flat to nested?

As per this example of nested and flat:
https://docs.python-guide.org/scenarios/serialization/
I want the foreign key to represented from the following JSON output
{
"cart": {
"cartid": "C0001",
"username": "myuser1"
},
"subtotal": 150.0,
"start_day": "2019-03-20T00:00:00"
},
to be represented like:
{
"C0001": {
"username": "myuser1"
},
"subtotal": 150.0,
"start_day": "2019-03-20T00:00:00"
},
Is there a simple way to have this output in django-rest-framework?
Here are my serializers:
class CartSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='shopper.username')
class Meta:
model = Cart
fields = ['shopper', 'cartid', 'username']
class CartProdSerializer(serializers.ModelSerializer):
cart = CartSerializer(read_only=True, label=Cart.cartid)
class Meta:
model = cart_prod
fields = ['cart', 'subtotal', 'start_day']
And my view:
class CartProdView(viewsets.ModelViewSet):
queryset = cart_prod.objects.all()
serializer_class = CartProdSerializer

Populating a tastypie resource for a multi-table inheritance Django model

Given the following code I was wondering how to populate RecordsResource with each real record data:
models.py
class Record(models.Model):
content_type = models.ForeignKey(ContentType, editable=False, null=True)
user = models.ForeignKey(User, related_name='records')
issued = models.DateTimeField(auto_now_add=True)
date = models.DateField()
def save(self, *args, **kwargs):
if not self.content_type:
self.content_type = ContentType.objects.get_for_model(self.__class__)
super(Record, self).save(*args, **kwargs)
def as_leaf_class(self):
model = self.content_type.model_class()
if model == self.__class__:
return self
return model.objects.get(pk=self.id)
class Record1(Record):
# some fields
# ...
class RecordN(Record):
# some fields
api.py
class BaseModelResource(ModelResource):
class Meta(object):
authentication = ApiKeyPlusWebAuthentication()
authorization= Authorization()
cache = SimpleCache()
throttle = CacheDBThrottle(
throttle_at=350,
# 1 day
expiration=86400
)
if settings.DEBUG:
serializer = PrettyJSONSerializer()
def obj_create(self, bundle, request=None, **kwargs):
return super(BaseModelResource, self).obj_create(bundle, request, user=request.user)
def apply_authorization_limits(self, request, object_list):
return object_list.filter(user=request.user)
class BaseRecordResource(BaseModelResource):
class Meta(BaseModelResource.Meta):
filtering = {
'date': ALL
}
excludes = ['issued']
class RecordsResource(BaseRecordResource):
class Meta(BaseRecordResource.Meta):
resource_name = 'records'
queryset = Record.objects.all()
class Record1Resource(BaseRecordResource):
class Meta(BaseRecordResource.Meta):
resource_name = 'record1'
queryset = Record1.objects.all()
# ...
class RecordNResource(BaseRecordResource):
class Meta(BaseRecordResource.Meta):
resource_name = 'recordn'
queryset = RecordN.objects.all()
Ok, I just solved it. I've simplified the code.
Given the following code...
models.py
from django.db import models
from model_utils.managers import InheritanceManager
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
# https://github.com/carljm/django-model-utils#inheritancemanager
objects = InheritanceManager()
class Restaurant(Place):
custom_field = models.BooleanField()
class Bar(Place):
custom_field = models.BooleanField()
api.py
from core.models import Place, Restaurant, Bar
# http://django-tastypie.readthedocs.org/en/latest/cookbook.html#pretty-printed-json-serialization
from core.utils import PrettyJSONSerializer
from tastypie.resources import ModelResource
class PlaceResource(ModelResource):
class Meta:
queryset = Place.objects.select_subclasses()
resource_name = 'place'
serializer = PrettyJSONSerializer()
class RestaurantResource(ModelResource):
class Meta:
queryset = Restaurant.objects.all()
resource_name = 'restaurant'
serializer = PrettyJSONSerializer()
class BarResource(ModelResource):
class Meta:
queryset = Bar.objects.all()
resource_name = 'bar'
serializer = PrettyJSONSerializer()
Output
http://localhost:8000/api/v1/bar/?format=json
{
"meta": {
"limit": 20,
"next": null,
"offset": 0,
"previous": null,
"total_count": 1
},
"objects": [
{
"address": "dawdaw",
"custom_field": true,
"id": "1",
"name": "dwdwad",
"resource_uri": "/api/v1/bar/1/"
}
]
}
OK
http://localhost:8000/api/v1/restaurant/?format=json
{
"meta": {
"limit": 20,
"next": null,
"offset": 0,
"previous": null,
"total_count": 1
},
"objects": [
{
"address": "nhnhnh",
"custom_field": true,
"id": "2",
"name": "nhnhnh",
"resource_uri": "/api/v1/restaurant/2/"
}
]
}
OK
http://localhost:8000/api/v1/place/?format=json
{
"meta": {
"limit": 20,
"next": null,
"offset": 0,
"previous": null,
"total_count": 2
},
"objects": [
{
"address": "dawdaw",
"id": "1",
"name": "dwdwad",
"resource_uri": "/api/v1/place/1/"
},
{
"address": "nhnhnh",
"id": "2",
"name": "nhnhnh",
"resource_uri": "/api/v1/place/2/"
}
]
}
What I want to achieve
{
"meta": {
"limit": 20,
"next": null,
"offset": 0,
"previous": null,
"total_count": 2
},
"objects": [
{
"address": "dawdaw",
"custom_field": true,
"id": "1",
"name": "dwdwad",
"resource_uri": "/api/v1/bar/1/"
},
{
"address": "nhnhnh",
"custom_field": true,
"id": "2",
"name": "nhnhnh",
"resource_uri": "/api/v1/restaurant/2/"
}
]
}
Solution:
from core.models import Place, Restaurant, Bar
# http://django-tastypie.readthedocs.org/en/latest/cookbook.html#pretty-printed-json-serialization
from core.utils import PrettyJSONSerializer
from tastypie.resources import ModelResource
class RestaurantResource(ModelResource):
class Meta:
queryset = Restaurant.objects.all()
resource_name = 'restaurant'
serializer = PrettyJSONSerializer()
class BarResource(ModelResource):
class Meta:
queryset = Bar.objects.all()
resource_name = 'bar'
serializer = PrettyJSONSerializer()
class PlaceResource(ModelResource):
class Meta:
queryset = Place.objects.select_subclasses()
resource_name = 'place'
serializer = PrettyJSONSerializer()
def dehydrate(self, bundle):
# bundle.data['custom_field'] = "Whatever you want"
if isinstance(bundle.obj, Restaurant):
restaurant_res = RestaurantResource()
rr_bundle = restaurant_res.build_bundle(obj=bundle.obj, request=bundle.request)
bundle.data = restaurant_res.full_dehydrate(rr_bundle).data
elif isinstance(bundle.obj, Bar):
bar_res = BarResource()
br_bundle = bar_res.build_bundle(obj=bundle.obj, request=bundle.request)
bundle.data = bar_res.full_dehydrate(br_bundle).data
return bundle
In RecordsResource class, you need to add model field as well (see https://github.com/tomchristie/django-rest-framework/blob/master/djangorestframework/resources.py#L232-234)
class RecordsResource(BaseRecordResource):
model = Record
class Meta(BaseRecordResource.Meta):
resource_name = 'records'
queryset = Record.objects.all()
Explaining from the beginning:
There are three styles of inheritance that are possible in Django.
Often, you will just want to use the parent class to hold
information that you don't want to have to type out for each child
model. This class isn't going to ever be used in isolation, so
Abstract base classes are what you're after.
If you're subclassing an existing model (perhaps something from
another application entirely) and want each model to have its own
database table, Multi-table inheritance is the way to go.
Finally, if you only want to modify the Python-level behavior of a
model, without changing the models fields in any way, you can use
Proxy models.
The choice here is Multi-table inheritance
Multi-table inheritance
The second type of model inheritance supported by Django is when each model in the hierarchy is a model all by itself. Each model corresponds to its own database table and can be queried and created individually. The inheritance relationship introduces links between the child model and each of its parents (via an automatically-created OneToOneField) Ref
To go from Record to Recordx where 1 <= x <= n you do a_example_record = Record.objects,get(pk=3) and then check what type of Recordx it is by using something like below
if hasattr(a_example_record, 'record1'):
# ...
elif hasattr(a_example_record, 'record2'):
# ...
So now that we know how to get the children from the parent and we need to provide TastyPie with a queryset in its meta, you need to write a custom queryset backed by a custom manager on the Record model that takes all your records (More here Custom QuerySet and Manager without breaking DRY?), checks what type of child it is and appends it to a queryset or a list. You can read about appending here How to combine 2 or more querysets in a Django view?