Dynamically create an inner class in a class in Python 3? - python-3.x

To automate a Django API I'd like to dynamically generate ModelSerializer classes, such as:
class AppleSerializer(serializers.ModelSerializer):
class Meta:
model = Apple
fields = '__all__'
class BananaSerializer(serializers.ModelSerializer):
class Meta:
model = Banana
fields = '__all__'
Creating a regular class is fairly simple (see below). How do you dynamically create the inner Meta class as well??
for serialmodel in ['Apple', 'Banaba']:
class_name = serialmodel + 'Serializer'
globals()[class_name] = type(class_name, (serializers.ModelSerializer,), {})

Found the answer:
for serialmodel in ['Apple', 'Banana']:
class_name = serialmodel + 'Serializer'
Meta = type('Meta', (object, ), {'model': globals()[serialmodel], 'fields': '__all__'})
globals()[class_name] = type(class_name, (serializers.ModelSerializer,), {'Meta':Meta, })
Very useful if you have a lot of generic models in Django :)
Or, when you want to do this for all models automatically:
models = dict(apps.all_models['your_django_app'])
for name, model in models.items():
class_name = name[:1].upper() + name[1:] + 'Serializer'
Meta = type('Meta', (object, ), {'model': model, 'fields': '__all__'})
globals()[class_name] = type(class_name, (serializers.ModelSerializer,), {'Meta':Meta, })

Related

Providing custom field in serializer using model data

In Problem model I'm storing a list of users who have done it. In Problem serializer when user will request his data I want to show him if he has done certain problem. So I created custom field in serializer class and want to fill it using models data.
this is what I have done.
from rest_framework import serializers
from dsa.models import Problem
class ProblemSerializer(serializers.ModelSerializer):
isDoneByUser = serializers.BooleanField(default=False)
class Meta:
model = Problem
fields = ['id', 'topic', 'title', 'link', 'isDoneByUser']
def perform_create(self, serializer):
user = self.request.user
userlist = serializer.data.get('isDone')
print(userlist)
if user in userlist:
self.isDoneByUser = True
here is the model
from django.db import models
from django.contrib.auth.models import User
class Problem(models.Model):
topic = models.CharField(max_length=100)
title = models.CharField(max_length=100)
link = models.URLField()
isDone = models.ManyToManyField(User)
class Meta:
ordering = ['topic']
But this always setting isDoneByUser = False(default value)
please help!
You cannot set isDoneByUser to True as it does not exist in your model.
You can however get the boolean with a SerializerMethodField:
class ProblemSerializer(serializers.ModelSerializer):
isDoneByUser = serializers.SerializerMethodField()
def get_isDoneByUser(self, instance):
user = self.context['request'].user
return instance.isDone.filter(pk=user.pk).exists()
class Meta:
model = Problem
fields = ['id', 'topic', 'title', 'link', 'isDoneByUser']

DRF use nested serializer to create key object instead of array

Context
Say we take this example from the DRF relations guide.
# models.py
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ('album', 'order')
ordering = ['order']
def __str__(self):
return '%d: %s' % (self.order, self.title)
Using a serializer will get us this output
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
Will get us this output:
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
]
}
Question
How can i use the serializer to get the output like this:
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': {
1: {order: 1, title: 'Sunflower'},
2: {order:2, title: 'Whitetail'},
3: {order:3, title: 'Dinosaur Act'},
...
}
}
This way we have an object with tracks instead of a numeric array. So i can do this.props.album.tracks[2].title this instead of this.props.album.tracks.find(track => track.order == 2}).title in javascript.
``I have an use case in where this seems to be more convenient in Reactjs.
What i have tried
I thought about overriding the to_representation method. But i see that this will get me an recursive loop.
class TrackSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
print(self)
return '%s: { %s }' % (instance.order, self.to_representation(instance))
class Meta:
fields = '__all__'
model = Track
Furthermore i have searched and read the docs pretty well. But didn't find any solution for what i think should be a pretty logical solution to have out of the box. Making me think that i am wrong and missing something.
Thanks in advance.
Define a new TrackSerializer and use it in AlbumSerializer class as,
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('id', 'order', 'title')
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['tracks'] = {track['id']: track for track in representation['tracks']}
return representation

marshmallow-mongoengine:Output dump value missing 'None' field

My project uses flask+mongoengine+marshmallow,When I used marshmallow to serialize the model, the returned value lacked field, and the missing field value was None.When using Django to serialize fields, the None value is still output
model
class Author(db.Document):
name = db.StringField()
gender = db.IntField()
books = db.ListField(db.ReferenceField('Book'))
def __repr__(self):
return '<Author(name={self.name!r})>'.format(self=self)
class Book(db.Document):
title = db.StringField()
serializers
class AuthorSchema(ModelSchema):
class Meta:
model = Author
class BookSchema(ModelSchema):
class Meta:
model = Book
author_schema = AuthorSchema()
When I do this:
author = Author(name="test1")
>>> author.save()
<Author(name='test1')>
>>> author_schema.dump(author)
MarshalResult(data={'id': '5c80a029fe985e42fb4e6299', 'name': 'test1'}, errors={})
>>>
not return the books field
I hope to return
{
"name":"test1",
"books": None
}
what should I do?
When I looked at the source code of the marshmallow-mongoengine library, I found the solution model_skip_values=() in the tests file.
def test_disable_skip_none_field(self):
class Doc(me.Document):
field_empty = me.StringField()
list_empty = me.ListField(me.StringField())
class DocSchema(ModelSchema):
class Meta:
model = Doc
model_skip_values = ()
doc = Doc()
data, errors = DocSchema().dump(doc)
assert not errors
assert data == {'field_empty': None, 'list_empty': []}

instance expected, got OrderedDict Django Rest Framework writable nested serializers

I am creating a survey kind of app, so i have three models Form, Questiosn, Choices[for multiple choice questions]
I followed this tutorial http://www.django-rest-framework.org/api-guide/relations/#nested-relationships
It works fine for 1 level nested relations, but for 2 levels it gives
TypeError: 'Choice' instance expected, got OrderedDict([(u'title', u'option1')])
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ['title']
class QuestionSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True, required=False)
class Meta:
model = Question
fields = ['title', 'type', 'required','order','choices']
def create(self, validated_data):
choices_data = validated_data.pop("choices")
question = Question.objects.create(**validated_data)
for choice_data in choices_data:
Choice.objects.create(question=question, **choice_data)
return question
class FormSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Form
fields = ['title', 'description', 'created', 'active', 'hash','questions']
read_only_fields = ['active','hash']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
Question.objects.create(form=form, **question_data)
return form
EDIT
Solved using the manual way, In FormSerializer override the create method,
#transaction.atomic
def create(self, validated_data):
try:
with transaction.atomic():
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
question = Question.objects.create(form=form,
title=question_data['title'],
type=question_data['type'],
required=question_data['required'])
if question.type == Question.RADIO or question.type == Question.CHECKBOX:
choices_data = question_data.pop('choices')
for choice_data in choices_data:
choice = Choice.objects.create(question=question, title=choice_data['title'])
return form
except Exception, e:
raise serializers.ValidationError("Cannot Save Form %s" % e)
I also struggled with this and I believe the proper way to handle this is:
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ['title']
class QuestionSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True, required=False)
class Meta:
model = Question
fields = ['title', 'type', 'required','order','choices']
class FormSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Form
fields = ['title', 'description', 'created', 'active',
'hash','questions']
read_only_fields = ['active','hash']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
choices_data = question_data.pop('choices')
Question.objects.create(form=form, **question_data)
for choice_data in choices_data:
choice = Choice.objects.create(question=question, **choice_data)
return form
An easy way to screw this up is to not pop choices before creating the Question object. When you do that, you'll get an instance expected, got OrderedDict( 500 error.
Note also that you do not need to define create() on the QuestionSerializer. All child processing is done at the top level.

Working with reference fields in marshmallow-mongoengine

How does one dereference ReferenceFields in marshmallow_mongoengine? For example, dump_data = author_schema.dump(author).data results in '5578726b7a58012298a5a7e2' instead of the more useful response {title='Fight Club', author=author}.
from marshmallow_mongoengine import ModelSchema
class AuthorSchema(ModelSchema):
class Meta:
model = Author
class BookSchema(ModelSchema):
class Meta:
model = Book
author_schema = AuthorSchema()
author = Author(name='Chuck Paluhniuk').save()
book = Book(title='Fight Club', author=author).save()
dump_data = author_schema.dump(author).data
# {'id': 1, 'name': 'Chuck Paluhniuk', 'books': ['5578726b7a58012298a5a7e2']}
author_schema.load(dump_data).data
# <Author(name='Chuck Paluhniuk')>
Nesting schemas
from marshmallow.fields import Nested
class AuthorSchema(ModelSchema):
class Meta:
model = Author
books = Nested("BookSchema",many=True)

Resources