Djangorestframework Modelresource add a field from an foreignkey - django

I have an api in django-rest framework that now returns this json data:
[
{
"id": 1,
"foreignobject": {
"id": 3
},
"otherfields": "somevalue"
}
]
But I want it to return something like this (flatten the foreigneky to its ID only):
[
{
"id": 1,
"foreignobject_id":3,
"otherfields": "somevalue"
}
]
Doing this in the model Resource, now I Have (simplified):
class ForeignKeyInDataResource(ModelResource):
model = TheOtherModel
fields = ('id',)
class SomeModelResource(ModelResource):
model = SomeModel
fields = ( 'id',('foreignobject','ForeignKeyInDataResource'),'otherfields',)
I tried already something like:
class SomeModelResource(ModelResource):
model = SomeModel
fields = ( 'id','foreignobject__id','otherfields',)
but that did not work
for the complete story, this how the view returns the data, list is a result of a query over the SomeModel:
data = Serializer(depth=2 ).serialize(list)
return Response(status.HTTP_200_OK, data)

I'm not really in a position to support REST framework 0.x anymore, but if you decide to upgrade to 2.0 this is trivial - simply declare the field on the serializer like so: foreignobject = PrimaryKeyRelatedField()

I found another option: (by reading the ModelResource documentation...)
In the Modelresource you can define a function(self,instance), which can return the id.
in the fields you can add this function!
so, this works:
class SomeModelResource(ModelResource):
model = SomeModel
fields = ( 'id','foreignobject_id','otherfields',)
def foreignobject_id(self, instance):
return instance['foreignobject']['id']

Related

Django can i only pass "id" in POST request, despite displaying nested fields?

in my post requests to OrderProduct model, i want to only have to pass order.id and product.id and it works... untill i add a serializer to retrieve product.name. It might be because i didnt understand documentation about nested requests, but im unable to advance further into my project :(
[
{
"id": 2,
"order": 1,
"product": 1,
}
]
^ here's how it looks without nested serializer, and thats the data that i wanna have to input
[
{
"id": 2,
"order": 1,
"product": {
"id": 1,
"name": "gloomhaven",
},
},
^ here's how it looks after i add an additional serializer. I pretty much want these nested fields to be read only, with me still being able to send simple post requests
here are my serializers
class OrderProductSerializer(serializers.ModelSerializer):
product = Product()
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
class Product(serializers.ModelSerializer):
class Meta:
model = Product
fields = (
"id",
"name")
Is there any way for me to accomplish this? Thank you for trying to help!
Just overwrite to_representation method of the serializer
def to_representation(self, instance):
response = super().to_representation(instance)
response['other_field'] = instance.id# also response['other_field'] = otherSerializer(instance.model)
return response
This can solve your problem
I think you are missing many=True
class OrderProductSerializer(serializers.ModelSerializer):
product = Product(many=True)
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]

create a django serializer to create three model instance at once

{
"product_name": "CRVRVgfhghg",
"product_price": "0.01",
"product_location": "KIKUYU,KENYA",
"product_description": "VFVFVFVFVFVF",
"product_category_name": "livestock",
"product_farmer_name": "james",
"product_category_data": {
"product_category_name": "livestock",
"product_category_description": "livestock one"
},
"product_product_file_data": {
"product_file_name": "ok"
}
}
i have three tables: product_category,product and product_product_files...what i want is to populate all the three tables at once using one view and url pattern... is there a way i can do this using serializers??
I think what you are looking for is the following documentation DRF writable nested serializers.
Looking at this you'll see that they state the following:
'By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be saved:'
This is the example they use:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
def create(self, validated_data):
tracks_data = validated_data.pop('tracks')
album = Album.objects.create(**validated_data)
for track_data in tracks_data:
Track.objects.create(album=album, **track_data)
return album
The data they put in then should look like this:
data = {
'album_name': 'The Grey Album',
'artist': 'Danger Mouse',
'tracks': [
{'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
{'order': 2, 'title': 'What More Can I Say', 'duration': 264},
{'order': 3, 'title': 'Encore', 'duration': 159},
],
Looking at your code-snippet I would guess these models are related in a One-to-one relationship. In this case many=False which is also the default. You could do that for both models.
I think you would be able to get to the right code with this information, if not please let me know.

Django DRF: how to groupby on a foreign fields?

I have a model where users can upvote other users for specific topics. Something like:
#models.py
Class Topic(models.Model):
name = models.StringField()
def __str__(self):
return str(self.name)
Class UserUpvotes(models.Model):
"""Holds total upvotes by user and topic"""
user = models.ForeignKey(User)
topic= models.ForeignKey(Topic)
upvotes = models.PositiveIntegerField(default=0)
Using DRF, I have an API that returns the following: topic_id, topic_name, and upvotes, which is the total upvotes for a given topic.
One of the project requirements is for the API to use these field names specifically: topic_id, topic_name, and upvotes
#serializers.py
class TopicUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.StringRelatedField(source="topic")
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
My trouble is aggregating these fields. I'm filtering the UserUpvotes by user or team and then aggregating by topic.
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_id": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_id": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
At first, I tried creating a TopicSerializer, and then assigning it to the topic field in TopicUpvotesSerializer. But then, the resulting json would have a nested "topic" field and the aggragation would fail.
Attempt 1
#views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
My problem is that the topic_id and topic_name fields are not showing. I get something like:
[
{
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": "12",
"upvotes": 3
},
]
Attempt 2
Another queryset attempt:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
Which yields:
[
{
"upvotes": 14
},
{
"upvotes": 3
},
]
The aggregation worked on the queryset level, but the serializer failed to find the correct fields.
Attempt 3
This was the closest I got:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.values("topic_id", "topic", "upvotes")
.order_by("-upvotes")[:n]
)
[
{
"topic_name": 3,
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "12",
"upvotes": 3
},
]
I have no idea why "topic_name" is simply transforming the "topic_id" into a string, instead of calling the string method.
Work with a serializer for the topic:
class TopicSerializer(serializers.ModelSerializer):
upvotes = serializers.IntegerField(read_only=True)
class Meta:
model = Topic
fields = ['id', 'name', 'upvotes']
then in the ModelViewSet, you annotate:
from django.db.models import Sum
from rest_framework.viewsets import ModelViewSet
class TopicViewSet(ModelViewSet):
serializer_class = TopicSerializer
queryset = Topic.objects.annotate(upvotes=Sum('userupvotes__upvotes'))
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_name": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
The serialized FK will always give you the ID of the related model. I am not sure why you name it topic_name if that is equal to an ID. Now, if you really want to get the name field of the Topic model
in the topic_name = serializers.StringRelatedField(source="topic") you should give it a source="topic.name"
However, if you trying to get the ID of the relation you can still use ModelSerializer :
class TopicUpvotesSerializer(serializers.ModelSerializer):
class Meta:
model = UserUpvotes
fields = "__all__"
#willem-van-onsem's answer is the correct one for the problem as I had put it.
But... I had another use case (sorry! ◑﹏◐), for when the Users API used UserUpvotes serializer as a nested field. So I had to find another solution. This is was I eventually ended up with. I'm posting in case it helps anyone.
class UserUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.SerializerMethodField()
def get_topic_name (self, obj):
try:
_topic_name = obj.topic.name
except TypeError:
_topic_name = obj.get("skill__name", None)
return _topic_name
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
I still have no idea why the SerializerMethodField works and the StringRelatedField field doesn't. It feels like a bug?
Anyways, the rub here is that, after the values().annotate() aggregation, obj is no longer a QuerySet, but a dict. So accessing namedirectly will give you a 'UserUpvotes' object is not subscriptable error.
I don’t know if there are any other edge cases I should be aware of (this is when I REALLY miss type hints in Django), but it works so far

Django: How to retrieve all attributes from related models for GeoJSON serialization?

I have two Models Site and Cell, every site has multiple Cells.
from django.db import models
from django.contrib.gis.db.models import PointField
class SiteManager(models.Model):
def get_by_natural_key(self, name, state_code, location):
return self.get(name=name, state_code=state_code, location=location)
class Site(models.Model):
name = models.CharField(max_length=10)
state_code = models.PositiveSmallIntegerField()
location = PointField()
objects = SiteManager()
class Meta:
unique_together = [['name', 'state_code', 'location']]
def natural_key(self):
return (self.name, self.state_code, self.location)
class Cell(models.Model):
tech = models.CharField(max_length=5)
azimuth = models.IntegerField()
sector_id = models.CharField(max_length=10)
frequency_band = models.CharField(max_length=15)
power = models.DecimalField(decimal_places=2, max_digits=4)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
def natural_key(self):
return (self.tech, self.azimuth,) + self.site.natural_key()
natural_key.dependencies = ['astmaps.site']
I want to retrieve the complete Cell attributes with the related attributes in the Site model, for me to Serialize the resultant Cell's, into GeoJson data, I can easily Serialize the Site model like:
from django.core.serializers import serialize # GeoJSON Serializer
sites = Site.objects.all()
sitesData = serialize('geojson', sites, geometry_field='location',
fields=('name', 'state_code'))
which gives me a GeoJson featureCollection object like:
{
"type":"FeatureCollection",
"crs":{
"type":"name",
"properties":{
"name":"EPSG:4326"
}
},
"features":[
{
"type":"Feature",
"properties": {
"name":"02101",
"state_code":2
},
"geometry":{
"type":"Point",
"coordinates":[
1.34944,
36.1586
]
}
}
]
}
But when It comes to the Cell model, I can't successfully get the geometry field from the related model always null.
Since the Cell model has the Site model as a related model, I've used the function select_related() to get the related attributes:
cells = Cell.objects.select_related('site').all()
cellsData = serialize('geojson', cells, geometry_field='site_location',
fields=('azimuth', ...))
But the GeoJson Serialize function could not identify the Site model attributes from the cells QuerySet:
{
"type":"FeatureCollection",
"crs":{
"type":"name",
"properties":{
"name":"EPSG:4326"
}
},
"features":[
{
"type":"Feature",
"properties":{
"azimuth":340
},
"geometry":null
},
{
"type":"Feature",
"properties":{
"azimuth":340
},
"geometry":null
},
{
"type":"Feature",
"properties":{
"azimuth":240
},
"geometry":null
}
]
}
I've tested the query returned by Django ORM equivalent directly on the database:
cells = Cell.objects.select_related('site').all()
>>> print(cells.query)
SELECT "app_cell"."id", "app_cell"."tech", "app_cell"."azimuth", "app_cell"."sector_id", "app_cell"."frequency_band", "app_cell"."power", "app_cell"."site_id", "app_cell"."id", "app_site"."name", "app_site"."state_code", "app_site"."location"::bytea FROM "app_cell" INNER JOIN "app_site" ON ("app_cell"."site_id" = "app_site"."id")
Which gives me a correct results (all the attributes or columns of the two models):
I've also used Natural Keys, which is the serialization strategy for foreign keys and other relations (as I've read in the documentation and changed the models accordingly):
cellsData = serialize('geojson', cells, geometry_field='site_location',
fields=('azimuth', ...), use_natural_foreign_keys=True)
But the same result, the Serialize method couldn't identify the Site model attributes.
How can I get all the attributes of multiple related models to get serialized using the GeoJSON Serializer?
I managed to get what I want by using raw sql query plus the json_build_object and ST_AsGeoJSON of PostGIS extention:
from django.db import connection
def sites_list(request):
cursor = connection.cursor()
cursor.execute("""select json_build_object(
'type', 'FeatureCollection',
'features', json_agg(ST_AsGeoJSON(t.*)::json)
)
from ( SELECT "app_cell"."id", "app_cell"."tech", "app_cell"."azimuth", "app_cell"."sector_id",
"app_cell"."frequency_band", "app_cell"."power", "app_cell"."site_id", "app_site"."name"
AS "site_name", "app_site"."state_code", "app_cell"."location"::bytea::geometry AS "site_location"
FROM "app_cell" INNER JOIN "app_site" ON ("app_cell"."site_id" = "app_site"."id")
) as t(id, tech, azimuth, sector_id, frequency_band, power, site_id, site_name, state_code, geom);""")
geojson = cursor.fetchone()
return JsonResponse(geojson[0], safe=False)
I had some problems in the JavaScript side, when using:
cursor.fetchall()
return JsonResponse(geojson, safe=False)
the returned GeoJSON object was surrounded by [[]] double brackets, because cursor.fetchall() return a tuple, So, I've used the cursor.fetchone() to get the geoJSON object surrunded only by single brackets [], and used the the index 0 to of the resulted tuble to get the only tuple content as a string, and finally the JsonResponse function will return that result as a JSON object.

How do I create new models not affecting the nested serializer

I already have a general idea of how it should be done. The only issue that I face now is how to actually send the data. I don't want to create new Projects I just want to add them to the notifications. How do I pass the data, the actual JSON?
class NotificationsScheduleSerializer(ModelSerializer):
projects = ProjectSerializer(many=True) # Thats the Many2Many Field
user = HiddenField(default=CurrentUserDefault())
class Meta:
model = NotificationsSchedule
fields = [
"pk",
"projects",
"period",
"week_day",
"created_at",
"time",
"report_type",
"user",
]
def create(self, validated_data):
breakpoint() # I don't ever get "projects" in validated_data just Empty OrderedDict
projects_data = validated_data.pop("projects", [])
notification = NotificationsSchedule.objects.create(**validated_data)
return notification
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ["pk", "name"]
I want to be able to pass something like this.
{
"projects": [290, 289],
"period": "daily",
"week_day": 2,
"time": "16:02:00",
"report_type": "word_report"
}
But it expects dict instead.
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
You have to set read_only,
projects = ProjectSerializer(many=True, read_only=True)
And when creating Notifications ,
notification = NotificationsSchedule.objects.create(**validated_data)
notification.projects.add(*self.initial_data.get("projects"))
notification.save()