trying to do some nested model creation with drf/create serizlizer.
what i'm trying to serialize is 'TradePost' model, which has post, and ProductItem in it.
i already have 'ProductItemSerializer', and 'PostSerializer' by using this.. how can i serialize them?? with creation? not by telling existing record with pk value.
models.py
class ProductItem(models.Model):
baseProduct = models.ForeignKey(Product, related_name='baseProduct')
seller = models.ForeignKey(User)
price = models.DecimalField(max_digits=6, decimal_places=2)
isOrderClosed = models.BooleanField()
isTradeCompleted = models.BooleanField()
def __str__(self):
return '[seller = '+self.seller.username+']' + '[product = '+(str)(self.baseProduct)+']' + '[id = '+(str)(self.id)+']'
class TradePost(models.Model):
basePost = models.OneToOneField(Post)
baseProductItem = models.OneToOneField(ProductItem)
def __str__(self):
return '[post = ' + (str)(self.basePost) + ']' + '[product = ' + (str)(self.baseProductItem) + ']' + '[id = ' + (str)(self.id) + ']'
in serialziers.py
class ProductItemCreateSerializer(serializers.ModelSerializer):
class Meta:
model = ProductItem
fields = ('baseProduct', 'price')
#???
class TradePostCreateSerializer(serializers.ModelSerializer):
class Meta:
model = TradePost
fields = ('basePost', 'baseProductItem',)
def create(self, validated_data):
post =
Similar to the writable nested serializer, you can try something like this:
class TradePostCreateSerializer(serializers.ModelSerializer):
basePost = PostCreateSerializer()
baseProductItem = ProductItemCreateSerializer()
class Meta:
model = TradePost
fields = ('basePost', 'baseProductItem',)
def create(self, validated_data):
# pop out the dict to create post and item, depend on whether you want to create post or not
post = validated_data.get('basePost')
product = validated_data.get('baseProductItem')
# create post first
trade_post = None
post_obj = Post.objects.create(**post)
if post_obj:
# create product item
prod_item = ProductItem.objects.create(basePost=post_obj, **product)
trade_post = TradePost.objects.create(baseProduct=prod_item, **validated_data)
return trade_post
For python, please follow naming convention and use lower_case_with_underscores like base_product, base_post, it will be much easier to read
You can use serializers to represent field, For eg,
class TradePostCreateSerializer(serializers.ModelSerializer):
basePost = PostSerializer()
baseProductItem = ProductItemSerializer()
class Meta:
model = TradePost
fields = ('basePost', 'baseProductItem',)
Reference:
DRF Nested relationships
If you're looking for writable nested serializers
Writable nested serializers
Related
I'm using Django 3.0 and I have a serializer using django-rest-framework. Let's say that for example I have a Forum object. Each forum has an owner that is a user.
In my GET /forums/ endpoint, I'd like to just have the owner_id. However, in my GET /forums/<forum_id>/ endpoint I'd like to return the entire embedded object.
Is there any way to have one serializer support both of these scenarios? If not, I would hate to have to make two serializers just to support this.
class ForumSerializer(serializers.ModelSerializer, compact=True):
if self.compact is False:
owner = UserSerializer(source='owner', read_only=True)
else:
owner_id = serializers.UUIDField(source='owner_id')
...
How can I achieve this compact thing?
class Meta:
fields = [...]
read_only_fields = ['owner', 'owner_id']
You can add a SerializerMethodField like this:
class ForumSerializer(serializers.ModelSerializer):
owner = serializer.SerializerMethodField()
def get_owner(self, obj):
if self.context['is_compact'] == True:
return obj.owner.pk
else:
return UserSerializer(obj.owner).data
class Meta:
model = YourModel
fields = '__all__'
# Usage in view
serializer = ForumSerializer(context={'is_compact':True})
I am passing is_compact value through serializer's extra context.
create two serializer classes
class ForumSerializerId(ModelSerializer):
class Meta:
model = Forum
fields = ['forum_id']
class ForumSerializerDetail(ModelSerializer):
class Meta:
model = Forum
on your view.py
forums(request):
forum_list = Forum.objects.all()
forum_serializer = ForumSerializerId(forum_list,many=True)
return Response({"form":forum_serializer.data})
forum_detail(request,pk):
forum = get_object_or_404(Forum,pk)
forum_serializer = ForumSerializerDetail(forum)
return Response({"form":forum_serializer.data})
I am trying to create an API with Artists and Songs, with a ManyToMany relationship between the two. Using the API to create a Song with an Artist that is not in the database works fine. The problem arises when I attempt to use the POST method to create a new Song with an Artist that already exists in the database. I tried overwriting the SongSerializer create() method using get_or_create() based on another post here, but I kept getting Bad Request errors when the Artist already exists in the database. The relevant code snippets:
models.py
class Artist(models.Model):
artist_name = models.CharField(max_length=200, unique=True)
class Meta:
ordering = ['artist_name']
def __str__(self):
return self.artist_name
class Song(models.Model):
song_title = models.CharField(max_length=200)
artists = models.ManyToManyField(Artist, related_name='songs')
class Meta:
ordering = ['song_title']
def __str__(self):
return self.song_title
serializers.py
class ArtistNameSerializer(serializers.ModelSerializer):
class Meta:
model = Artist
fields = ('artist_name',)
def to_representation(self, value):
return value.artist_name
class SongTitleSerializer(serializers.ModelSerializer):
songs = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
def to_representation(self, value):
return value.song_title
class Meta:
model = Song
fields = ('songs',)
class ArtistSerializer(serializers.HyperlinkedModelSerializer):
songs = SongTitleSerializer(read_only=True, many=True)
class Meta:
model = Artist
fields = ('id', 'artist_name', 'songs')
class SongSerializer(serializers.HyperlinkedModelSerializer):
artists = ArtistNameSerializer(many=True)
class Meta:
model = Song
fields = ('id', 'song_title', 'artists',)
def create(self, validated_data):
artist_data = validated_data.pop('artists')
song = Song.objects.create(**validated_data)
song.save()
for artist_item in artist_data:
a, created = Artist.objects.get_or_create(artist_name=artist_item['artist_name'])
song.artists.add(a)
return song
I've done some tests and it looks like the program doesn't even go into the create() method I'm using, going straight to showing me the Bad Request error. What am I missing? Thanks in advance!
On you Artist model you have a constrain on the artist_model field (unique=True)
if you print the serializer in question with:
print(SongSerializer())
you get something like this:
SongSerializer():
id = IntegerField(label='ID', read_only=True)
song_title = CharField(max_length=200)
artists = ArtistNameSerializer(many=True):
artist_name = CharField(max_length=200, validators=[<UniqueValidator(queryset=Artist.objects.all())>])
under the artist_name field is a Validator "UniqueValidator"
so in case of a write operation you can disable the validator in the serializer with:
class ArtistNameSerializer(serializers.ModelSerializer):
class Meta:
model = models.Artist
fields = ('artist_name',)
extra_kwargs = {
'artist_name': {
'validators': [],
}
}
hope this help..
I am designing an API for listing stores and create store. I could list store but while designing for creating store I am not getting product and store category field inspite of calling all Product and Store category serializer in Store Serializer.
My shortened models look like
class Merchant(models.Model):
user = models.ForeignKey(User)
phone = models.PositiveIntegerField(null=True,blank=True)
class Store(models.Model):
merchant = models.ForeignKey(Merchant)
name_of_legal_entity = models.CharField(max_length=250)
class Product(models.Model):
store = models.ForeignKey(Store)
image = models.ForeignKey('ProductImage',blank=True,null=True)
name_of_product = models.CharField(max_length=120)
class ProductImage(models.Model):
image = models.ImageField(upload_to='products/images/')
class StoreCategory(models.Model):
product = models.ForeignKey(Product,null=True, on_delete=models.CASCADE,related_name="store_category")
store_category = models.CharField(choices=STORE_CATEGORIES, default='GROCERY', max_length=10)
Serializer.py
class ProductImageSerializer(ModelSerializer):
class Meta:
model = ProductImage
fields = ( 'id','imageName', )
class ProductSerializers(ModelSerializer):
image = ProductImageSerializer(many=False,read_only=True)
class Meta:
model = Product
fields=('id','image','name_of_product','description','price','active',)
class StoreCategorySerializer(ModelSerializer):
product = ProductSerializers(read_only=True)
class Meta:
model = StoreCategory
class StoreSerializer(ModelSerializer):
# url = HyperlinkedIdentityField(view_name='stores_detail_api')
store_categories = StoreCategorySerializer(many=True)
merchant = MerchantSerializer(read_only=True)
class Meta:
model = Store
fields=("id",
"merchant",
"store_categories",
"name_of_legal_entity",
"pan_number",
"registered_office_address",
"name_of_store",
)
Views.py
class StoreCreateAPIView(CreateAPIView):
queryset = Store.objects.all()
serializer_class = StoreSerializer
parser_classes = (FormParser,MultiPartParser,)
def put(self, request, filename, format=None):
print('first put works')
file_obj = request.data['file']
print ('file_obj',file_obj)
return Response(status=204)
def perform_create(self, serializer):
print('then perform works')
serializer.save(user=self.request.user)
Here is the screenshot of how it looks
Why it is not showing Merchant, Product and Store Category in the form?
Remove read_only=True from the serializers that you wanna create entry of.
Like:
product = ProductSerializers(read_only=True)
should be
product = ProductSerializers()
read_only will prevent it from written therefore i wont be in the outcome.
I'm trying to create a custom serializer method that counts the number of passed and failed quizzes from my QuizResults model. A failed quiz is under .7 and a passed quiz is .7 or over.
I want to be able to look into the Users QuizResult and count the number of passed quizzes(.7 or over). I would then duplicate the method to count the failed quizzes (under .7).
So far I don't have much idea on how to do so. I want to be able to grab the percent_correct field of the model and do a calculation and add it to a field in the serializer called "quiz_passed".
Here is my QuizResult model:
class QuizResult(models.Model):
quiz = models.ForeignKey(Quiz)
user = models.ForeignKey(User, related_name='quiz_parent')
percent_correct = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(1.0)])
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return 'Quiz Results for : ' + self.quiz.title
Here is my serializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(source='todo_parent.count', read_only=True)
discussion_count = serializers.IntegerField(source='comment_parent.count', read_only=True)
quiz_passed = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('todo_count', 'discussion_count', 'quiz_passed', 'username', )
def get_quiz_passed(self, obj):
return passed
Any help is appreciated.
Edit:
I extended the User model and added a model method like you suggested.
class Profile(User):
def get_quizzes_passed_count(self):
return self.quiz_parent.filter(percent_correct__gte=0.8).count()
I then added your suggestion to my ProfileSerializer.
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(source='todo_parent.count', read_only=True)
discussion_count = serializers.IntegerField(source='comment_parent.count', read_only=True)
num_quizzes_passed = serializers.ReadOnlyField(source="get_quizzes_passed_count")
class Meta:
model = Profile
fields = ('todo_count', 'discussion_count', 'num_quizzes_passed', 'username')
Unfortunately when I add this nothing appears in the framework once these have been added. Any suggestions? Thank you.
You can use a model method on the user model to count that user's number of passed quizzes:
class User(models.model):
# rest of your User attributes
def get_quizzes_passed_count(self):
return self.quiz_parent.filter(percent_correct__gte=0.7).count()
Then add that to your serializer using a DRF ReadOnlyField to serialize that method:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(
source='todo_parent.count', read_only=True
)
discussion_count = serializers.IntegerField(
source='comment_parent.count', read_only=True
)
quiz_passed = serializers.SerializerMethodField()
num_quizzes_passed = serializers.ReadOnlyField(source="get_quizzes_passed_count")
class Meta:
model = User
fields = ('todo_count', 'discussion_count', 'quiz_passed', 'username', )
def get_quiz_passed(self, obj):
return passed
You can duplicate this for the number of failed quizzes.
I have model with generic foreign key and I want to serialize that model.
model.py:
class AddressType(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type','object_id')
def __unicode__(self):
return u'%s' % str(self.content_type)
class AddressBook(TimeStampedModel):
class Meta:
db_table = 'address_book'
uuid = UUIDField(auto=True)
address_tag = models.CharField(null=True, blank=True, max_length=20)
# should be a generic foreign key
address_object_type = GenericRelation(AddressType)
address1 = models.CharField(
verbose_name='Address1',
max_length=200,
)
address2 = models.CharField(
verbose_name='Address2',
max_length=200,
)
serializer.py:
class AddressBookSerializer(serializers.ModelSerializer):
class Meta:
model = AddressBook
fields = ('id','uuid','address_tag','address_object_type','address1','address2')
How can I serialize JSON on above model?
This case is perfectly described in the documentation.
So if you want to serialize AddressType you will need to implement something like this:
class ContentObjectRelatedField(serializers.RelatedField):
"""
A custom field to use for the `content_object` generic relationship.
"""
def to_representation(self, value):
"""
Serialize tagged objects to a simple textual representation.
"""
if isinstance(value, Bookmark):
return 'Bookmark: ' + value.url
elif isinstance(value, Note):
return 'Note: ' + value.text
raise Exception('Unexpected type of tagged object')
Where Bookmark and Note are objects which may be have associated contents.
If you want to serialize AddressBook you can try doing something like this:
class AddressBookSerializer(serializers.ModelSerializer):
address_object_type = ContentObjectRelatedField()
class Meta:
model = AddressBook
fields = ('id','uuid','address_tag','address_object_type','address1','address2')
I'd prefer use this third party (rest-framework-generic-relations) as also described in documentation.
from generic_relations.relations import GenericRelatedField
class TagSerializer(serializers.ModelSerializer):
"""
A `TaggedItem` serializer with a `GenericRelatedField` mapping all possible
models to their respective serializers.
"""
tagged_object = GenericRelatedField({
Bookmark: BookmarkSerializer(),
Note: NoteSerializer()
})
class Meta:
model = TaggedItem
fields = ('tag_name', 'tagged_object')
If your code is more structured, i.e. each app has its serializers.py, and serializer naming follows a convention (i.e. [ModelName]Serializer) you could use dynamic importing to avoid if ... elif ... else ... logic with a benefit of loose coupling:
from django.utils.module_loading import import_string
class ContentObjectRelatedField(serializers.RelatedField):
"""
A custom field to serialize generic relations
"""
def to_representation(self, object):
object_app = object._meta.app_label
object_name = object._meta.object_name
serializer_module_path = f'{object_app}.serializers.{object_name}Serializer'
serializer_class = import_string(serializer_module_path)
return serializer_class(object).data