I have a model is pretty straight forward and I've created a property on the model to essentially return child data as JSON. The property is simple:
#property
def question_data(self):
from api.models import TemplateQuestion
questions = TemplateQuestion.objects.filter(template__id=self.template.id)
question_dict = [obj.as_dict() for obj in questions]
return(json.dumps(question_dict, separators=(',', ': ')))
Which does its job and outputs valid JSON. That said I'm at a total loss of how to add that property to the Serializer as JSON and not a string like
{
"questions": "[{\"sequence\": 2,\"next_question\": \"\",\"modified_at\": \"2016-01-27T19:59:07.531872+00:00\",\"id\": \"7b64784e-a41d-4019-ba6e-ed8b31f99480\",\"validators\": []},{\"sequence\": 1,\"next_question\": null,\"modified_at\": \"2016-01-27T19:58:56.587856+00:00\",\"id\": \"99841d91-c459-45ff-9f92-4f75c904fe1e\",\"validators\": []}]"
}
It's stringifying the JSON and which I need as proper JSON.
Serializer is probably too basic but I haven't worked with DRF in a while and have never tried to append JSON to the serialized output.
class BaseSerializer(serializers.ModelSerializer):
class Meta:
abstract = True
class SurveySerializer(BaseSerializer):
included_serializers = {
'landing_page': 'api.serializers.LandingPageSerializer',
'trigger': 'api.serializers.TriggerSerializer',
'template': 'api.serializers.TemplateSerializer'
}
questions = serializers.ReadOnlyField(source='question_data')
class Meta:
model = Survey
fields = ('id',
'name',
'slug',
'template',
'landing_page',
'trigger',
'trigger_active',
'start_date',
'end_date',
'description',
'fatigue_limit',
'url',
'questions',)
meta_fields = ('created_at', 'modified_at')
I'll add that I'm also bolting on the Django Rest Framework JSON API formatting but I think that in the end I'm just not getting how to append JSON to a model's serialization without it being returned as a string.
You should not be dumping the results of the method to JSON. Just return the dict; DRF's serializer will take care of converting it.
Related
Im very new to Django, struggling to find the answer Im looking for. Please bear with me.
In my app/urls.py file I have
router = routers.DefaultRouter()
router.register(r'drugs', views.DrugViewSet)
In my views.py file I have
class DrugViewSet(viewsets.ModelViewSet):
queryset = Drug.objects.all().order_by('name')
serializer_class = DrugSerializer
And my DrugSerializer class looks like
class DrugSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Drug
fields = ('id', 'name', 'description', 'recommended_dose_mg', 'count', 'create_date', 'update_date')
So when I do a GET on /drugs, I correctly get the list of drugs. And when I take a UUID (primary key) on a specific drug and do a GET on /drugs/<primary_key>, it correctly pulls the specific drug.
However, I want the GET on /drugs/<primary_key> to display different data. I want to join it with another model that I have and return a new json response. How exactly would I do this?
You can use Nested Serializer
For example, the following serializer:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
Would serialize to a nested representation like this:
{
'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},
...
],
}
Lookup field
There is a field in ViewSet called lookup_field. It actually defines a field of the model to use in such query variable.
So you can do this:
class DrugViewSet(viewsets.ModelViewSet):
queryset = Drug.objects.all().order_by('name')
serializer_class = DrugSerializer
lookup_field = 'id'
Here is the link of the documentation: https://www.django-rest-framework.org/api-guide/generic-views/#attributes
But actually it is generated automatically for retrieve action in ViewSet and id is used. You can install some of swagger plugins to see it.
For example:
drf-yasg
drf-spectacular
Different serializers
If you want to set different serializers for different actions you can override a model of the ViewSet called get_serializer_class:
class DrugViewSet(viewsets.ModelViewSet):
queryset = Drug.objects.all().order_by('name')
serializer_class = DrugSerializer
def get_serializer_class(self):
if self.action == 'detail':
return DrugDetailSerializer
return DrugSerializer
Documentation here: https://www.django-rest-framework.org/api-guide/generic-views/#get_serializer_classself
In the DrugDetailSerializer you can specify what data to serialize when GET /drug/<primary_key> field is called.
I have a nested writable serializer.
class GamerSerializer(serializers.ModelSerializer):
account= AccountSerializer()
document = DocumentSerializer()
class Meta:
model = Gamer
fields = [
'chosen_game',
'gamer_experience',
'account',
'document'
]
This serializers is supposed to create Account object, a Gamer object related to Account as well as Document object related to Gamer.
By default the nested serializer always accepts nested objects as data like this:
serializer = self.get_serializer(data= { account: {...acount}, document: {...document}, chosen_game: "Minecraft", "gamer_experience": "1 year" } )
but I want the serializer to accept normalized flattened data( we assume that names of the model attributes do not overlap). Like this :
serializer = self.get_serializer(data= { account_name: '', account_type:'', document_name: '', document_file: '', chosen_game: "Minecraft", "gamer_experience": "1 year" })
How can I achieve this result ?
I'm trying to display a 'nested' model in my API response and having trouble shaping the data.
I have the model the API is called from:
something like
class Rules(Model):
conditions = models.ManyToManyField(RulesPoliciesConditions)
...
...
class RulesPoliciesConditions(Model):
rules = models.ForeignKey(Rules, ...)
policies = models.ForeignKey(Policy, ...)
Rules and Policies are their own models with a few TextFields (name, nickname, timestamp, etc)
My problem is that when I use the Rules model to call a field called conditions, only the rules and policies PK display. I want to reach the other attributes like name, timestamp, nickname, etc.
I tried making my fields (in my Serializer) try to call specifically like "conditions__rules__name" but it's invalid, I also tried "conditions.rules.name" which is also invalid. Maybe I'm using the wrong field in my serializer, I'm trying out conditions = serializers.SlugRelatedField(many=True, queryset=q, slug_field="id")
My intention is to display something like:
conditions: [
{
rules: {id: rulesId, name: rulesName, ...},
policies: {id: policiesId, name: policiesName, ...}
}, ...
]
or just even:
conditions: [
{
rules: rulesName,
policies: policiesName
}, ...
]
since right now it just returns the rulesId and policiesId and it doesn't "know" about the other fields
EDIT: I found a relevant question on SO but couldn't get a relevant answer
Django REST Framework: Add field from related object to ModelSerializer
This can be achieved by using nested serializers. The level of nesting can be controlled/customized by various methods
class RulesPoliciesConditionsSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = RulesPoliciesConditions
depth = 1
class RulesSerializer(serializers.ModelSerializer):
conditions = RulesPoliciesConditionsSerializer(many=True)
class Meta:
fields = '__all__'
model = Rules
Pass your Rules queryset to the RulesSerializer serializer to
get the desired output
Example
rules_qs = Rules.objects.all()
rules_serializer = RulesSerializer(rules_qs, many=True)
data = rules_serializer.data
References
1. serializer depth
2. Nested serializer
You can use nested serializers for the purpose.
class RuleSerializer(serializers.ModelSerializer):
...
class Meta:
model = Rules(rulesId, rulesName)
fields = ('id', 'email', 'country')
class RulesPoliciesConditionsSerializer(serializers.ModelSerializer):
rules = RuleSerializer()
policies = PolicySerializer()
...
Given some model
class Loan(models.Model):
time_of_loan = models.DateTimeField()
username = models.CharField()
I have attempted to use the ModelSerializer from Django's REST Framework to serialize the Loan.
class LoanSerializer(serializers.ModelSerializer):
time_of_loan = serializers.DateTimeField(
format=None, input_formats=['%Y-%m-%d %H:%M:%S',])
class Meta:
model = `Loan`
fields = ['time_of_loan', 'username']
On using the serializer.data to get the JSON format, when I save the first time, the first time the model is saved, the JSON is well-behaved.
{
'time_of_loan': '2016-06-20 00:00:00+08:00',
'username': 'doe'
}
However, when I attempt to update the model, it "misbehaves" and it appears in a python datetime format.
{
'time_of_loan': datetime.datetime(2016, 6, 20, 7, 55, tzinfo=<UTC>),
'username': 'doe'
}
What change do I need to do so that, whenever the model gets serialized, it remains as the first format that I want?
FIRST EDIT
Can you show what you're doing to update the object
The question asked was what I did to update the model. I actually am using this as an audit log and so it took from an actual Django Form. In forms.py:
id = forms.cleaned_data.get('id')
username = forms.cleaned_data.get('username')
loan = Loan.objects.filter(id=id) #Queryset with count() = 1
loan.update(username=username)
loan_obj = loan[0]
serializer = LoanSerializer(loan_obj)
print(serializer.data)
After so much finding, I finally got the answer.
from rest_framework.renderers import JSONRenderer
serializer = LoanSerializer(loan_obj)
serializer.data
json = JSONRenderer().render(serializer.data)
I have two ModelSerializers:
class VariantSerializer(serializers.ModelSerializer):
class Meta:
model = Variant
fields = (
'id',
'name',
... and so on)
and
class PictureItemSerializer(serializers.ModelSerializer):
class Meta:
model = PictureItem
fields = (
'id',
... and so forth)
What I want to do is to return a JSON including a single instance of the VariantSerializer, and multiple instances of the PictureItemSerializer.
I created another serializer as such:
class PictureItemUploadSerializer(serializers.Serializer):
variant = VariantSerializer()
pictureItems = PictureItemSerializer(many=True)
But I'm having difficulties in instantiating this "combination" of a serializer if you will.
This does not work:
p = PictureItemUploadSerializer()
p.variant = variant_serializer
p.pictureItems = picture_item_serializer
return Response(p.data, status=status.HTTP_201_CREATED)
as it yields empty data:
{
"variant": {
"name": ""
},
"pictureItems": []
}
You need to pass the data dictionary to the serializer when you are creating an instance of the serializer.
Lets say variant_data contains data for Variant model and picture_items_data contains the list of PictureItem model data.
Create a data dictionary containing them.
data = {'variant': variant_data, 'pictureItems': picture_items_data}
Then pass this data to the serializer.
p = PictureItemUploadSerializer(data=data) # pass the data dictionary
p.is_valid(raise_exception=True) # check if serializer is valid
return Response(p.data, status=status.HTTP_201_CREATED) # return response