Property field in a django model is not serialized. How to fix? - python

I have the following model:
def get_file_path(instance, filename):
return 'files/{0}/{1}'.format(instance.user.username, filename)
class File(models.Model):
fid = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
file = models.FileField(upload_to=get_file_path)
when I try to serialize this model using for example serializers.serialize('json', File.objects.all()) I get:
u'[{"model": "app_name.file", "pk": "45f616b4-d028-488e-83c5-878ce1028018", "fields": {"user": 1, "file": "files/username/filename.txt"}}]'
I want the serialized json to also include the file field's url which file_obj.file.url returns so I added a property field like so:
class File(models.Model):
fid = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
file = models.FileField(upload_to=get_file_path)
def _get_file_url(self):
"Returns file's url"
return self.file.url
url = property(_get_file_url)
However property field url is still not serialized. So I tried to write a custom serializer for the model by extending serializers.ModelSerializer like so:
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = ('fid', 'user', 'file', 'url')
which doesn't work and I get 'module' object has no attribute 'ModelSerializer'. is it because django doesn't have ModelSerializer class and only django rest_framwork does ? if so how do I solve this if I am using django only and not django rest framework ?
Thanks.

when not using DRF - what is a poor idea...
you have to build manually in your view the dict with the data you wish to return and then use from django.http.response import JsonResponse for serializing the result.
def single_object_representation(obj):
return {
'fid': obj.fid,
'user': obj.user.username,
'url': obj._get_file_url()
}
def demo_view(request):
objects_to_serialize = File.objects.all()
result = [single_object_representation(obj) for obj in objects_to_serialize]
return JsonResponse(result, safe=False)
using django.core.serializers
see here: https://docs.djangoproject.com/en/2.0/topics/serialization/#subset-of-fields
but make sure it will work with your property. For some fields like User you should use duble underscore notation __ e.g user__username
otherwise (with DRF) - what I strongly recommend to you
First of all install drf:
pip install djangorestframework
Then if there is still problem with your serializer give a try to the SerializerMethodField (if in doubt see the docs: http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield)
from rest_framework import serializers
class FileSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
class Meta:
model = File
fields = ('fid', 'user', 'file', 'url')
def get_url(self):
return self. _get_file_url()

Related

serialize text field to json

a third-party application I am using is storing json in a textfield.
I would like to serialize this data to json, and I only need to be able to read from this serializer, not write to it. I don't want to have to manipulate the data on the frontend, so I want it to come out at clean json from my api.
class SomeSerializer(serializers.ModelSerializer):
details = serializers.CharField()
class Meta:
model = SomeModel
fields = ( 'id', 'details')
right now this is returning:
[{"id":"someID",
"details":"{\"address\": {\"city\": null}"}"}]
I can't figure out how to use json.loads in a serializer, which would seem the cleanest option.
You can use SerializerMethodField.
import json
from rest_framework import serializers
class SomeSerializer(serializers.ModelSerializer):
details = serializers.SerializerMethodField()
class Meta:
model = SomeModel
fields = ('id', 'details')
def get_details(self, obj):
return json.loads(obj.details)
Note that SerializerMethodField is read_only, cannot for write.

How to create new model with foreignkey in django

class Schedule(models.Model):
name = models.CharField(max_length=50)
class ScheduleDefinition(models.Model):
schedule = models.ForeignKey(Schedule, on_delete=models.DO_NOTHING)
config = JSONField(default=dict, blank=True)
These are my models. I am trying to create a new ScheduleDefinition(The Schedule already exists and I know the ID I want to use for my foreign_key). I have a predefined Schedule id that I want to use, but it is not working..
Posting this body:
{
"schedule_id": 1,
"config": {
"CCC": "ccc"
}
}
Error I get:
null value in column "schedule_id" violates not-null constraint
What am I doing wrong? When I create new ScheduleDefinition models, the Schedule model will already be created previously. I am never going to be creating new Schedule's when I create new ScheduleDefinition's.
Serializer:
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ['id', 'name']
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(read_only=True, many=False)
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'config']
View:
from rest_framework import generics
from .models import Schedule, ScheduleDefinition
from .serializers import ScheduleSerializer, ScheduleDefinitionSerializer
class ScheduleList(generics.ListAPIView):
queryset = Schedule.objects.all()
serializer_class = ScheduleSerializer
class ScheduleDefinitionList(generics.ListCreateAPIView):
queryset = ScheduleDefinition.objects.all()
serializer_class = ScheduleDefinitionSerializer
class ScheduleDefinitionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = ScheduleDefinition.objects.all()
serializer_class = ScheduleDefinitionSerializer
View error:
File "/app/server/schedules/serializers.py", line 13, in ScheduleDefinitionSerializer
schedule_id = serializers.PrimaryKeyRelatedField(source="schedule")
File "/usr/local/lib/python3.7/dist-packages/rest_framework/relations.py", line 247, in __init__
super().__init__(**kwargs)
File "/usr/local/lib/python3.7/dist-packages/rest_framework/relations.py", line 108, in __init__
'Relational field must provide a `queryset` argument, '
AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.
You've specified Schedule as required (not null), but you aren't actually posting any information to it. Currently your serializer is expecting information in the form:
{
"schedule": {
"name": "Foo"
},
"config": {...}
}
schedule_id is being discarded when you post. Furthermore, you've specified that schedule is a read only field, meaning even if you posted a schedule, it would still be discarded.
If you'd like to post to foreign keys, you'll either need to specially handle it by manually writing the create/update logic for a writable nested serializer (which can be a bit of a hassle), or use a different (writable) foreignkey field serializer and serialize your other read-only data another way.
For example, the following setup should work (untested) with the data you're currently trying to POST:
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = serializers.PrimaryKeyRelatedField(
queryset=Schedule.objects.all()
)
schedule_name = serializers.CharField(read_only=True, source="schedule.name")
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'schedule_name', 'config']
With this your post should work, and you'll still have read-only access to the corresponding schedule's name via the schedule_name field in your list/detail views.
EDIT
My earlier version of the code would not have been compatible with the original desired POST data. The following should work without altering the POST
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(many=False, read_only=True)
schedule_id = serializers.PrimaryKeyRelatedField(
source="schedule",
queryset=Schedule.objects.all()
)
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'schedule_id', 'config']

Django rest framework posting expects dictionary

I am trying to post to my API with foreign key relationships. It's throwing me back an error saying it's expecting a dictionary as opposed to int for character, character_opponent and stage. This is because the way my models are set up. They have foreign key relationships. The model in question looks like this:
import uuid
from django.db import models
from django.utils import timezone
from analysis.models import Analysis
from characters.models import Character
from stages.models import Stage
class Match(models.Model):
analysis = models.ForeignKey(Analysis, on_delete=models.CASCADE)
character = models.ForeignKey(Character, on_delete=models.CASCADE, related_name='character')
character_won = models.BooleanField()
character_opponent = models.ForeignKey(Character, on_delete=models.CASCADE, related_name='character_opponent')
character_opponent_won = models.BooleanField()
created_at = models.DateTimeField(editable=False)
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
updated_at = models.DateTimeField(editable=False)
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
def __str__(self):
return '%s vs. %s on %s' % (self.character, self.character_opponent, self.stage)
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.created_at:
self.created_at = timezone.now()
self.updated_at = timezone.now()
return super(Match, self).save(*args, **kwargs)
class Meta:
db_table = "matches"
And here is my serializer:
from rest_framework import serializers
from matches.models import Match
from characters.serializers import CharacterSerializer
from stages.serializers import StageSerializer
class MatchSerializer(serializers.ModelSerializer):
character = CharacterSerializer()
character_opponent = CharacterSerializer()
stage = StageSerializer()
class Meta:
model = Match
fields = ('id', 'analysis', 'character', 'character_won', 'character_opponent', 'character_opponent_won', 'stage')
Is there some option I am missing here to be able to post properly? Clearly I shouldn't have to pass the entire character object each time I want to post something, right? I should just be able to pass the primary key.
From your few comments I understood that you need nested serializer in GET method. What I suggest is, use two[or more] serializers for your API class.
Assuming you are using ModelViewSet API class is using,then you could override get_serializer_class() method as below,
from rest_framework.viewsets import ModelViewSet
class MatchAPI(ModelViewSet):
queryset = Match.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return MatchCreateSerializer
return MatchSerializer
And your MatchCreateSerializer will be like this,
class MatchCreateSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Match
Thus, you only need to provide the PKs of analysis,character etc while creation of Match instance
It will come down to your CharacterSerializer and StageSerializer. If you want to input 1 format (using serialisers.PrimaryKeyRelatedField()), but output another (CharacterSerializer, StageSerializer), you might be best served using 2 serialisers and switching in your view.
In your view you can override get_serializer_class and check your request method, or in the case of a viewset you can check the method being invoked.
When you declare a serializer related field using another serializer, like this
character = CharacterSerializer()
you are telling django-rest-framework that you want a nested serializer. What you want is something like this instead
character = serializers.PrimaryKeyRelatedField()
or you can actually just leave the explicit field declaration out of the serializer (since this is the default), see the doc on serializer relations.

Is there a way to get JSON data without tying to model in django restframework?

I would like to get JSON data on my backend without tying it to a model. For example, in json data like this I want access to quantity but don't want it tied to any model.
{
"email": "some#gmail.com"
"quantity": 5,
"profile": {
"telephone_number": "333333333"
}
}
My serializer:
class PaymentSerializer (serializers.ModelSerializer):
profile = UserProfilePaymentSerializer()
# subscription = UserSubscriptionSerializer()
class Meta:
model = User
fields = ('email', 'profile',)
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = AuthUser.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
def update (self, instance, validated_data):
instance.quantity = validated_data.get('quantity', instance.quantity)
print instance.quantity
return instance
When I send a PATCH request, I get an error
'User' object has no attribute 'quantity'
you can use json
import json
json.loads('{ "email": "some#gmail.com","quantity": 5,"profile": {"telephone_number": "333333333"}}')[u'quantity']
I think you can use simple json too (not tested):
from urllib import urlopen
import simplejson as json
url = urlopen('yourURL').read()
url = json.loads(url)
print url.get('quantity')
AFAIK, passing an extra data to ModelSerializer and attribute missing in model will throw as you stated. If you want to have extra attribute which is not present in model, restore_object needs to overriden.
Few examples GitHub Issue and similar SO Answer
You could try something like this:
class PaymentSerializer (serializers.ModelSerializer):
...
quantity = serializers.SerializerMethodField('get_quantity')
class Meta:
model = User
fields = ('email', 'profile', 'quantity',)
def get_quantity(self, user):
return len(user.email)
...
Basically, you modify Meta, but not the actual model, to add an extra quantity field to the output. You define this field as a SerializerMethodField. The serializer will invoke the method get_quantity to get the value of the field. In the example, the quantity is returned equal to the length of the email address of the user. This is useful in general for computed fields. These derived fields are automatically read-only and will be ignored on PUT and POST.
Reference is here.

Get current user in Model Serializer

Is it possible to get the current user in a model serializer? I'd like to do so without having to branch away from generics, as it's an otherwise simple task that must be done.
My model:
class Activity(models.Model):
number = models.PositiveIntegerField(
blank=True, null=True, help_text="Activity number. For record keeping only.")
instructions = models.TextField()
difficulty = models.ForeignKey(Difficulty)
categories = models.ManyToManyField(Category)
boosters = models.ManyToManyField(Booster)
class Meta():
verbose_name_plural = "Activities"
My serializer:
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
And my view:
class ActivityDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Activity.objects.all()
serializer_class = ActivityDetailSerializer
How can I get the model returned, with an additional field user such that my response looks like this:
{
"id": 1,
"difficulty": 1,
"categories": [
1
],
"boosters": [
1
],
"current_user": 1 //Current authenticated user here
}
I found the answer looking through the DRF source code.
class ActivitySerializer(serializers.ModelSerializer):
# Create a custom method field
current_user = serializers.SerializerMethodField('_user')
# Use this method for the custom field
def _user(self, obj):
request = self.context.get('request', None)
if request:
return request.user
class Meta:
model = Activity
# Add our custom method to the fields of the serializer
fields = ('id','current_user')
The key is the fact that methods defined inside a ModelSerializer have access to their own context, which always includes the request (which contains a user when one is authenticated). Since my permissions are for only authenticated users, there should always be something here.
This can also be done in other built-in djangorestframework serializers.
As Braden Holt pointed out, if your user is still empty (ie _user is returning None), it may be because the serializer was not initialized with the request as part of the context. To fix this, simply add the request context when initializing the serializer:
serializer = ActivitySerializer(
data=request.data,
context={
'request': request
}
)
A context is passed to the serializer in REST framework, which contains the request by default. So you can just use self.context['request'].user inside your serializer.
I had a similar problem - I tried to save the model that consist user in, and when I tried to use
user = serializers.StringRelatedField(read_only=True, default=serializers.CurrentUserDefault()) like on official documentation - but it throws an error that user is 'null'. Rewrite the default create method and get a user from request helped for me:
class FavoriteApartmentsSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = FavoriteApartments
exclude = (
'date_added',
)
def create(self, validated_data):
favoriteApartment = FavoriteApartments(
apartment=validated_data['apartment'],
user=self.context['request'].user
)
favoriteApartment.save()
return favoriteApartment
I modified the request.data:
serializer = SectionSerializer(data=add_profile_data(request.data, request.user))
def add_profile_data(data, user):
data['user'] = user.profile.id
return data

Categories

Resources