Hide the (obvious) relation from nested serializer in DRF - django

Django + rest framework. This seems like it should be a frequent and common issue, but I could not find anything like it, so here I ask:
I have a Document and its Items:
class DocumentSerializer(ModelSerializer):
...
items = ItemsSerializer(many=True, required=False)
class Meta:
model = Document
exclude = ()
class ItemsSerializer(ModelSerializer):
...
class Meta:
model = DocumentItem
exclude = ('document', ) # hide the ForeignKey as it should be obvious by nesting
Expected result for JSON serialized data something like:
{
"id": 1, "date": "2021-01-01T00:00:00Z", "title": "Test doc",
"items": [
{"code": 43, quantity: 3},
{"code": 28, quantity: 15}
]
}
It should be fairly obvious that the "document" field from ItemsSerializer should be derived from the parent serializer at the time of storage. The field itself being a ForeignKey to Document, of course.
However, I can't get past the ValidationError({"document":["This field is required."]}).
If I say the field is not required, then save()complains AssertionError: The '.create()' method does not support writable nested fields by default.
What is the accepted way of handling relations like this in serializers?

My suggestion:
Check if it makes sense to have a Document without Items.
Always when you have nested Serializer, method #create and #update should be overridde. Check documentation

Related

How to return a standard Django Rest Framework JSON from an enum?

I'm not very familiar with DRF and I haven't found a solution on google for this problem (most answers are about a model with a field as enum, my problem is different)
You see, we have an Enum in a Django application. Let's call it SomeValuesEnum.
class SomeValuesEnum(Enum):
ONE_VALUE = "One value"
ANOTHER_VALUE = "Another value"
What I need to do is to create a GET endpoint that returns the following
{
"count": 2,
"page_count": 1,
"next": null,
"previous": null,
"results": [
{
"value": "One value",
"name": "ONE_VALUE"
}, {
"value": "Another value",
"name": "ANOTHER_VALUE"
}
]
}
I know I need to create a serializer, but I haven't been able to create one and "feed it".
For example, I started with something like this:
class SomeValueSerializer(serializers.Serializer):
Meta:
model = SomeValuesEnum,
fields = '__all__'
and on the view:
class SomeValueListView(APIView):
serializer_class = SomeValueSerializer
def get(self, request):
choices = [{"value": target.value, "name": target.value.capitalize()} for target in SomeValuesEnum]
serializer = SomeValueSerializer(data=choices)
return Response(status=status.HTTP_200_OK, data=serializer.data)
I also tried this
class IncidentSerializer(serializers.Serializer):
name = serializers.CharField(required=False, allow_blank=True, max_length=100)
value = serializers.CharField(required=False, allow_blank=True, max_length=100)
I'm not sure if I'm failing on the creation of the serializer, or in how I invoke him on the view (or maybe both)
Any guidance on the right direction will be greatly appreciate it.
An enum is not a django model. You can have a DRF serializer for something that isn't a model, but you shouldn't give it a model field of something that isn't a model.
See here: Declaring Serializers
Not here: Model Serializers
Furthermore, you are creating a class-based view here:
SomeValueListView(APIView)
You don't necessarily need this, you could use a function based view, which may be easier for you to understand given you are new to DRF. I only say function based views are easier to understand since there isn't so much built in functionality. This can make it easier to debug for someone new to DRF. You can still use the serializer by calling it directly in the view.
See here: Function Based Views
And here: Serializing Objects
Finally...given this code:
choices = [{"value": target.value, "name": target.value.capitalize()} for target in SomeValuesEnum]
I am making the assumption that there could be multiple distinct objects going into this serializer, given that you are using a list comprehension. Either you need to call the serializer separately for each object in the array, or you call the serializer with (many=True). Pretty sure this is your main issue. Like this:
serializer = SomeValueSerializer(data=choices, many=True)
See here: Dealing with multiple objects
Also, in my experience it is better to parse incoming data to the serializer within the serializer itself, not in the view. To me it is a separation of concerns issue, but other people may feel differently. This would look something like this:
class SomeValueListView(APIView):
serializer_class = SomeValueSerializer
def get(self, request):
serializer = SomeValueSerializer(data=SomeValuesEnum)
return Response(status=status.HTTP_200_OK, data=serializer.data)
class SomeValueSerializer(serializer.Serializer):
def to_internal_value(self, data)
name = data.value.capitalize()
value = data.value
return {
'name': name,
'value': value,
}

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 - get serialized fields from related model

I can't figure out how to serialize a query that includes fields from a reverse related model. My models look like this. Every vote is linked to a single album:
# models.py
class Album(models.Model):
name = models.CharField(max_length=50)
class Vote(models.Model):
album = models.ForeignKey(Album, on_delete=models.CASCADE)
user_vote = models.BooleanField(default=0)
What I'd like to do is perform a query that returns all Album objects, as well as a sum of the votes attributed to that album. That's easy enough, but when I serialize the query, the "total_votes" field is lost:
# views.py
# this works fine
query = Album.objects.annotate(total_votes = Sum(vote__user_vote))
# after serialization, I lose the field "total_votes"
serialized = serializers.serialize('json', list(query))
return serialized
Unfortunately, the field "total_votes" doesn't appear in the serialized result since, according to Django documentation, "only the fields that are locally defined on the model will be serialized."
So my question is, how do I get the following serialized result (assuming there are 100 votes for Abbey Road and 150 for Astral Weeks)? Any help would be greatly appreciated.
[
{
"pk": 1,
"model": "app.album",
"fields": {
"name": "Abbey Road",
"total_votes": 100
},
{
"pk": 2,
"model": "app.album",
"fields": {
"name": "Astral Weeks",
"total_votes": 150
},
...
]
According to the source, there’s no way to do this using serializers.serialize. The base django serializer will only serialize local_fields on the model instance:
for field in concrete_model._meta.local_fields:
if field.serialize:
if field.remote_field is None:
if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_field(obj, field)
else:
if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
self.handle_fk_field(obj, field)
for field in concrete_model._meta.many_to_many:
if field.serialize:
if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_m2m_field(obj, field)
In order to get what you want, you’d have to roll your own serialization function (e.g., something that converts your model to a dict and then uses the DjangoJSONEncoder).

Altering incoming Django url queries using tastypie : handling foreign key traversal on the backend

So we've got a Django/tastypie server going that has the following (simplified) model. If I remove our alter_detail_data_to_serialize:
{
"release": false,
"resource_uri": "/api/packages/1",
"id": 1,
"branch": {
"resource_uri": "/api/branches/1",
"id": 1,
# ... more bits the client doesn't need to know about
"version": "0.1"
},
"revision": "72"
}
With the alter, it becomes:
{
"release": false,
"branch": "0.1",
"revision": "72"
}
Which is what we want to work with through the API: It removes the foreign key traversal to simplify the JSON and do any CRUD without issues: supplying a version is sufficient to identify the branch. The issue is, to query this, requires
/api/packages?branch__version=1.0, where this not intuitive and exposes the structure of the underlying database. We'd prefer to be able to query:
/api/packages?branch=1.0 and handle the foreign key traversal on the backend.
alter_detail_data_to_serialize and alter_deserialized_detail_data allow me to interface with the simplified JSON and do any non-searching CRUD without issues, but is it possible to allow a /api/packages?branch=1.0 query and have the django/tastypie server correct that to /api/packages?branch__version=1.0, hiding the database structure?
Some additional code that might be relevant:
class PackageResource(ModelResource):
branch = fields.ForeignKey(BranchResource, 'branch', full=True)
class Meta:
queryset = Packages.objects.all()
resource_name = 'packages'
collection_name = 'packages'
def alter_detail_data_to_serialize(self, request, data):
data.data['branch'] = data.data['branch'].data['version']
return data
Branch Resource:
class BranchResource(ModelResource):
class Meta:
queryset = Branches.objects.all()
resource_name = 'branches'
collection_name = 'branches'
In the object resource, you can add something like this:
class PackageResourse(ModelResource):
version = fields.CharField( attribute = 'branch__version' )
class Meta:
resource_name='package'
What this is doing it making the PackageResource have a variable that is the same as its foreign key's variable. Now you can use api/packages?version=1.0 on the PackageResource.

Serializers in django rest framework with dynamic fields

I am trying to build a small api with django rest framework but I don't want to map directly the tables with calls (as in the examples).
I have the following database schema:
In models.py:
class ProductType(models.Model):
name = models.CharField(max_length=255, blank=False, null=False, unique=True)
class Product(models.Model):
#staticmethod
def get_accepted_fields(self):
return {'color': 'pink', 'size': 34, 'speed': 0, 'another_prop': ''}
name = models.CharField(max_length=255, blank=False, null=False, unique=True)
class ProductConfig(models.Model):
product_type = models.ForeignKey(ProductType)
product = models.ForeignKey(Product)
# a json field with all kind of fields: eg: {"price": 123, "color": "red"}
value = models.TextField(blank=True)
As you can see, every product can have multiple configurations and the value field is a json with different parameters. The json will be one level only. The configuration will have a flag if is active or not (so, the 1 product will have only 1 active configuration)
So, the data will look for example like this:
store_producttype
=================
1 type1
2 type2
store_product
=============
id name
1 car
store_productconfig
===================
id product_type_id product_id value active
1 2 1 { "color": "red", "size": 34, "speed": 342} 0
2 1 1 { "color": "blue", "size": 36, "speed": 123, "another_prop": "xxx"} 1
What I want to know is how can I get /product/1/ like this:
{
"id": 1,
"name": "car",
"type": "type1",
"color": "blue",
"size": 36,
"speed": 123,
"another_prop": "xxx",
}
and to create a new product posting a json similar with the one above.
The json fields are defined but some of them can miss (eg: "another_prop" in the productconfig.id=1
On update, anyway, it will create a new row in productconfig and it will put inactive=0 on the previous one.
So, every product can have different configuration and I want to go back to a specific configuration back in time in some specific cases). I am not really bound to this data model, so if you have suggentions for improvement I am open to them, but I don't want to have that properties as columns in the table.
The question is, what will be the best way to write the serializers for this model? There is any good example somewhere for a such use case?
Thank you.
Let's take this step by step:
In order to get a JSON like the one you posted, you must first transform your string (productConfig value field) to a dictionary. This can be done by using ast.literal_eval ( see more here).
Then, in your product serializer, you must specify the source for each field, like this:
class ProductSerializer(serializers.ModelSerializer):
color = serializer.Field(source='value_dict.color')
size = serializer.Field(source='value_dict.size')
type = serializer.Field(source='type.name')
class Meta:
model = Product
fields = (
'id',
'color',
'size',
'type',
)
This should work just fine for creating the representation that you want. However, this will not create automatically the product config, because DRF doesn't yet allow nested object creation.
This leads us to the next step:
For creating a product with a configuration from JSON, you must override the post method in your view, and create it yourself. This part shouldn't be so hard, but if you need an example, just ask.
This is more of a suggestion: if the json fields are already defined, wouldn't it be easier to define them as separate fields in your productConfig model?