Using django rest framework, how to add new nested child object for existing parent object - django-rest-framework

I am trying to build a data storage for time series data, for this I have created nested objects Coin and Data, where Coin is parent object and contains Data entries that each data entry is individual object. at this moment my code creates nested object Coin[Data] as I build create function within CoinSerializer, but I could not use proper method to add/create child object within existing Coin object
In my python virtual environment I've been using django 2.1.4 drf 3.9 and python 3.6.. also as a backend db engine for my project I got mongodb and use djongo 1.2 to maintain it
Any suggested idea or way for my problem would be greatly appreciated, as its my first post ever and sorry for any Inappropriate style..
models.py
class Coin(models.Model):
coin_name = models.CharField(max_length=100,blank=True)
class Data(models.Model):
coin = models.ForeignKey(Coin, related_name='data', on_delete=models.CASCADE,blank=True)
date = models.DateField(("Date"),blank=True)
open = models.FloatField(null=True, blank=True)
high = models.FloatField(null=True, blank=True)
low = models.FloatField(null=True, blank=True)
close = models.FloatField(null=True, blank=True)
class Meta:
unique_together = ('coin', 'date',)
ordering = ['date']
def __unicode__(self):
return '%d: %d %d %d %d' % (self.date, self.open, self.high,
self.low, self.close)
serializers.py
class DataSerializer(serializers.ModelSerializer):
class Meta():
model = models.Data
fields = ('coin_id','pk','id','date','open','high','low','close')
class CoinSerializer(serializers.ModelSerializer):
data = DataSerializer(many=True)
class Meta:
model = models.Coin
fields = ('pk','id','coin_name', 'data')
def create(self, validated_data):
data = validated_data.pop('data')
coin = models.Coin.objects.create(**validated_data)
models.Data.objects.create(coin=coin, **data[0])
return coin
my result is kind of this
{
"pk": 101,
"id": 101,
"coin_name": "ripple",
"data": [
{
"coin_id": 101,
"pk": 56,
"id": 56,
"date": "2016-12-25",
"open": 4036.0,
"high": 4101.0,
"low": 3983.0,
"close": 4065.0
}
]
},
and expect to consist lots of data objects which I will add by the time in existing coin object
{
"pk": 101,
"id": 101,
"coin_name": "ripple",
"data": [
{
"coin_id": 101,
"pk": 56,
"id": 56,
"date": "2016-12-25",
"open": 4036.0,
"high": 4101.0,
"low": 3983.0,
"close": 4065.0
}
{
"coin_id": 102,
"pk": 57,
"id": 57,
"date": "2016-12-26",
"open": 4065.0,
"high": 4189.0,
"low": 3967.0,
"close": 4075.0
}
...
...
]
},

You're going about it the wrong way. You should instead make another endpoint for Data too. There you can create data and pass the id of the parent coin. Using the nested architecture is only meaningfull when you're creating both the coin and the data at the same time. In this case, just use a data endpoint to create data while passing the id of the coin
EDIT: BULK CREATE
And just to throw a little light on how to implement bulk create for several Data objects - you will need to imlement it using a loop as model.objects.create() excpects data for a single object. You could use bulk_create but it has a lot of caveats, so I would use a loop

try change your input data with more than one item in array like example:
data = [{'date': '2016-12-25', 'high': 4101.0, 'open': 0.0, 'low': 3983.0, 'close': 4065.0}, {'date': '2016-12-26', 'high': 4101.0, 'open': 0.0, 'low': 3983.0, 'close': 4065.0}]
This example have one more item in array data.
And change this line:
coin = models.Coin.objects.create(**validated_data)
models.Data.objects.create(coin=coin, **data[0])
to
coin = models.Coin.objects.create(**validated_data)
for item_data in data:
models.Data.objects.create(coin=coin, **item_data)
This will create some Data with FK is Coin created.

This is how I did it.. Inside my viewset.ModelViewSet implementation In my case.. The parent class contains a list of manyToMany objects. Im posting new objects in the manyToMany..> Creating them.. then reinserting the IDs into the post data and calling the base class. Worked out pretty simple.. and I like it's contained in the view. Im newer to Django however.. but this worked for me.
class CaseDeepViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
queryset = Case.objects.all().order_by('-id')
def get_serializer_class(self):
if self.request.method in ['GET']:
return CaseDeepSerializer
return CaseSerializer
def create(self, request):
print('IM here: ')
print(request.data)
case_interactions = request.data.pop('new_case_interactions')
listCreatedInteractions = []
for interaction in case_interactions:
print("interaction", interaction)
interaction['issara_staff'] = obj = IssaraUser.objects.get(pk=interaction.get('issara_staff'))
listCreatedInteractions.append(CaseInteraction.objects.create(**interaction).id)
request.data['case_interactions'] = listCreatedInteractions
return super().create(request)

Related

Design pattern for DRY approach to dynamic serializer fields

Basically, what I want to achieve is to make the list of fields in a serializer be optionally dynamic depending on whether the user has provided the list of fields they are interested in.
Here's my serializer for DRF serializer:
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
fields = self.context['request'].query_params.get('fields')
if fields:
fields = fields.split(',')
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
And my serializer:
class MySerializer(serializer_mixins.DynamicFieldsModelSerializer, serializers.ModelSerializer):
# fields...
This achieves the goal of not including the fields that the user has not mentioned in fields param of the queryset. But! We end up with actual query to the database that fetches the entire set of fields. This issue, in turn, could be solved by just adding the following code to the view:
class Rfs(ListAPIView):
serializer_class = MySerializer
def get_queryset(self):
qs = ...
fields = request.query_params.get('fields')
if fields:
qs = qs.only(*fields.split(','))
return qs
However, fills like two issues issues here:
non-DRY pattern since we have to repeat ourselves both in the view and the serializer
Sometimes it might be the case that the field name inside the queryset does not correspond exactly to the field name of the model.
So maybe there's some more elegant and Django-native solution for this usecase ?
I am using drf_queryfields
In dependence of your query_params your view will be modified
GET http://127.0.0.1:8000/snippets/
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
GET http://127.0.0.1:8000/snippets/?fields=id,code
[
{
"id": 1,
"code": "foo = \"bar\"\n",
},
{
"id": 2,
"code": "print \"hello, world\"\n",
}
]
I hope that´s it what you would like to achieve.

Alter Queryset before returning in DRF

I have a model structure similar to the one below:
Store -> some store fields
Books -> some book fields, FK to Store
BookProperty -> name, value, FK to Books (a one to many relationship), FK to store
The book property can store any info for the book eg. no_of_pages, publisher etc added by the store.
I need to make an API endpoint where I can get all BookProperty for a store.
I used the url:
/stores/:store_id/bookproperty
Used a ModelSerializer for BookProperty with fields = [publisher, no_of_pages]
Used a genericViewSet with a ListModelMixin.
The endpoint turned out like this below:
{
"count": 4,
"next": null,
"previous": null,
"results": [
{
"name": "publisher",
"value": "somePublisher"
},
{
"name": "pages",
"value": "1000"
},
{
"name": "publisher",
"value": "someOtherPublisher"
},
{
"name": "publisher",
"value": "somePublisher"
}
]
}
The problem with this is that multiple objects can have the same name, value pairs. I need this information in a way where all the objects are unique and grouped kind of like this:
{
{"name":"publisher", "value":["somePublisher", "someOtherPublisher"]},
{"name":"pages", "value":["1000"]},
}
I'm trying to override the get_queryset(self) but it's not working.
Any help would be appreciated. Thanks in advance!
EDIT:
models.py
class BookProperty(models.Model):
books = models.ForeignKey(
Books,
on_delete=models.CASCADE,
)
name = models.CharField(max_length=100)
value = models.CharField(max_length=100)
store = models.ForeignKey(
"Store",
on_delete=models.CASCADE,
)
serializers.py
class BookPropertySerializer(serializers.ModelSerializer):
class Meta:
model = models.BookProperty
fields = ["name", "value"]
views.py
class BookPropertyViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = serializers.BookPropertySerializer
I think that instead of overriding the get_queryset(self) I should try changing the def list(self, request, *args, **kwargs) :
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
tag_dictionary = defaultdict()
things = list(queryset)
for key, group in itertools.groupby(things, lambda x: x.name):
for thing in group:
if key not in tag_dictionary.keys():
tag_dictionary[key] = [thing.value]
else:
tag_dictionary[key].append(thing.value)
for key in tag_dictionary.keys():
tag_dictionary[key] = list(set(tag_dictionary[key]))
return Response(json.dumps(tag_dictionary))
The above solution is working but might not be the best one.

Get data in multiple level nested serializer using Django Rest

Need to serialize three models nested in three levels.
There are users assigned areas and these contains point. The users contains multiple areas. Areas have multiple points associated.
Users links areas using many to many relationship.
Areas Links with point using Foreign in the points.
Users can be assigned to multiple areas. Areas can have multiple points.
User Profile Model
class UserProfile(AbstractBaseUser,PermissionsMixin):
phone_number= PhoneNumberField( unique=True)
name=models.CharField(max_length=255)
organisation=models.CharField(max_length=255)
is_active=models.BooleanField(default=True)
is_staff=models.BooleanField(default=False)
added_by=models.ForeignKey(settings.AUTH_USER_MODEL,default=1)
group = models.ForeignKey('auth.Group', null=True)
areas=models.ManyToManyField('area.Area',blank=True)
objects=UserProfileManager()
Areas Model
from django.db import models
from django.conf import settings
# Create your models here.
class Area(models.Model):
areaName =models.TextField()
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL )
def __str__(self):
return self.areaName
Point Model
from django.db import models
from django.conf import settings
# Create your models here.
class Point(models.Model):
name =models.TextField()
area = models.ForeignKey('area.Area', on_delete=models.CASCADE)
latitude=models.CharField(max_length=200)
longitude=models.CharField(max_length=200)
timestamp=models.DateTimeField(auto_now=False,auto_now_add=True)
updated=models.DateTimeField(auto_now=True,auto_now_add=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL )
def __str__(self):
return self.name
I want a result like following:
{
"id": 3,
"phone_number": "+919999999999",
"name": "Ak",
"organisation": "sp",
"group": 1,
"areas": [
{
"id": 1,
"areaName": "Area 51",
"user": 1
points:[{
}]
},
{
"id": 2,
"areaName": "Rrea 343",
"user": 1
point:[{}]
}
]
},
{
"id": 4,
"phone_number": "+918888888888",
"name": "Chitra Sahu",
"organisation": "sd",
"group": 2,
"areas": [
{
"id": 1,
"areaName": "Area 51",
"user": 1
point:[{
latitude:'23.2323',
longitude:'23.2323'
},
{
latitude:'21.1223',
longitude:'32.34345'
}
]
},
{
"id": 2,
"areaName": "Rrea 343",
"user": 1
point:[{
latitude:'23.2323',
longitude:'23.2323'
},
{
latitude:'21.1223',
longitude:'32.34345'
}]
}
]
},
So Far I have tried the following
class AreasSerializer(serializers.ModelSerializer):
class Meta:
model=Area
fields=('id','areaName','user')
class AreasUserSerializer(serializers.ModelSerializer):
areas = AreasSerializer(many=True, read_only=True)
class Meta:
model = UserProfile
fields = ('id','phone_number','name','organisation','group','areas')
class AreasUserPointSerializer(serializers.ModelSerializer):
areasUsers=AreasUserSerializer()
class Meta:
model=Point
fields =('id','areasUsers' )
Views
'''Fetch list all question '''
class AreasPointsUsersListApiView(ListAPIView):
serializer_class=serializers.AreasUserPointSerializer
def get_queryset(self):
queryset=UserProfile.objects.all()
user=self.request.query_params.get('user_id',None)
if user is not None:
queryset = queryset.filter(id=user)
#if areas is not None:
# queryset = queryset.filter(areas=areas)
return queryset
.py
This code is not working properly.
I need to serialize it so that the Users consists Areas based on Many to Many relationship. These areas are linked to point using the foreign key in Point.
EDIT
Edit:
Areas serializer
I have resolved this using LocationSerializer which invoked by AreasSerializer.
I am sharing the code snippet. It was pretty easy.
class PointSerializer(serializers.ModelSerializer):
class Meta:
model = Point
fields=('id','latitude','longitude')
class AreasLocationSerializer(serializers.ModelSerializer):
points = PointSerializer(many =True, read_only=True)
class Meta:
model=Area
fields=('id','areaName','points','user')
class AreasUserLocationSerializer(serializers.ModelSerializer):
areas =AreasLocationSerializer(many=True, read_only=True)
class Meta:
model=UserProfile
fields =('id','phone_number','name','areas')

Nested Image Field in custom Page representation wagtail api

We are building a headless CMS with the wagtail API.
Our main model became very long, to make the representation cleaner and more easily accessible for the Frontend,
I am trying to group the different fields of my PageModel into sections.
But I don't manage to serialize the nested ImageField.
This is my PageModel:
class LandingPage(Page):
…
introduction_headline= models.CharField()
introduction_text = RichTextField()
introduction_icon = models.ForeignKey(
'main.CaptionImage',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name = '+',
)
…
I would like to group those fields into one section in the api, like so:
{
"id": 3,
"meta": {…},
"introduction_section": {
"introduction_headline": "intro head",
"introduction_text": "<p>intro text</p>",
"introduction_image": {
"id": 1,
"meta": {
"type": "main.CaptionImage",
"detail_url": "http://localhost/api/v2/images/1/",
"download_url": "/media/original_images/1.png"
},
"title": "german_design_image.png",
"caption": "Pretty Image"
},
},…
I managed to accomplish this in parts by writing a custom IntroductionSection - serializer:
class LandingPage(Page):
…
api_fields = [
APIField('introduction_section', serializer=IntroductionSectionField(source='*')),
…
]
class IntroductionSectionField(Field):
read_only = True
write_only = False
def to_representation(self, value):
return {
"introduction_headline" : value.introduction_headline,
"introduction_text" : value.introduction_text,
"introduction_image" : ?
}
But I simply can't figure out how to serialize the nested Image Field?
I want the same representation as the standard nested-relation-representation of the page model.
I tried around with get_related_field() method of the PageModel, tried to call the ImageSerializer, and all sorts of other things.

Getting rest history from Django simple History

I am using django-simple-history (1.8.1) and DRF (3.5.3). I want to get a rest service containing the history of each element. Let's take an example !
models.py
class Product(models.Model):
name = models.CharField(max_length=50)
price = models.IntegerField()
history = HistoricalRecords()
def __str__(self):
return self.name
So, what must be serializers.py ? I'd like to GET something like :
[
{
"id": 1,
"name": "Apple",
"price": 8,
"history": [
{
"history_id": 1,
"id": 1,
"name": "Apple",
"price": 0,
"history_date": "2016-11-22T08:02:08.739134Z",
"history_type": "+",
"history_user": 1
},
{
"history_id": 2,
"id": 1,
"name": "Apple",
"price": 10,
"history_date": "2016-11-22T08:03:50.845634Z",
"history_type": "~",
"history_user": 1
},
{
"history_id": 3,
"id": 1,
"name": "Apple",
"price": 8,
"history_date": "2016-11-22T08:03:58.243843Z",
"history_type": "~",
"history_user": 1
}
]
}
]
After searching whitout finding the solution, I finally found it by myself. But if someone have a better solution...
I know it's been a year, but anyway, maybe someone finds it useful. Here is my solution (it seems far easier to me):
A new serializer field:
class HistoricalRecordField(serializers.ListField):
child = serializers.DictField()
def to_representation(self, data):
return super().to_representation(data.values())
Now simply use it as a a field in your serializer:
history = HistoricalRecordField(read_only=True)
This makes use of DRF's built in list and dict serializers, only trick is to pass it the correct iterable, which is being done by calling .values() on the simple-history model manager class.
Here's my solution.
In serializers.py :
from rest_framework import serializers
from .models import Product
class sHistory(serializers.ModelSerializer):
def __init__(self, model, *args, fields='__all__', **kwargs):
self.Meta.model = model
self.Meta.fields = fields
super().__init__()
class Meta:
pass
class sProduct(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
history = serializers.SerializerMethodField()
def get_history(self, obj):
model = obj.history.__dict__['model']
fields = ['history_id', ]
serializer = sHistory(model, obj.history.all().order_by('history_date'), fields=fields, many=True)
serializer.is_valid()
return serializer.data
It works ! I'm quite proud about it ! any suggestions ?
There seems to be an even clearer and simpler way
class AnySerializer(serializers.ModelSerializer):
history = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = (....
....
'history',
)
read_only_fields = ('history',)
def get_history(self, obj):
# using slicing to exclude current field values
h = obj.history.all().values('field_name')[1:]
return h
You can create a serializer like this:
class ProductHistorySerializer(serializers.ModelSerializer):
class Meta:
model = Product.history.model
fields = '__all__'
Then in view, You can have the code below:
#...
logs = ProductHistorySerializer(Product.history.filter(price__gt=100), many=True)
return Response({'isSuccess': True, 'data': logs.data})

Resources