ManyToManyField does not show - python

Want to use REST API to populate my tables but my field does not display on the API page.
Models (Series, Roster):
class Series(models.Model):
(...)
def __str__(self):
return self.title
class Roster(models.Model):
(...)
series = models.ManyToManyField(Series)
(...)
def __str__(self):
return self.name
Serializers:
class SeriesSerializer(serializers.ModelSerializer):
class Meta:
model = Series
fields = ('id', 'title', 'icon')
read_only_fields = ('slug',)
class RosterSerializer(serializers.ModelSerializer):
series = SeriesSerializer(many=True, read_only=True)
class Meta:
model = Roster
fields = ('id', 'name', 'number', 'primary_color', 'secondary_color', 'image', 'series')
Views:
class SeriesView(viewsets.ModelViewSet):
serializer_class = SeriesSerializer
queryset = Series.objects.all()
class RosterView(viewsets.ModelViewSet):
serializer_class = RosterSerializer
queryset = Roster.objects.all()
Unsure where I am mistepping here.

So it turns out that all I needed to do was remove
series = SeriesSerializer(many=True, read_only=True)
and adjust my series field to
series = models.ForeignKey(Series, on_delete=models.CASCADE, blank=True, null=True)
No idea why this ended up working though so an explanation would still be cool.

Related

Django rest framework - using SerializerMethodField with ModelSerializer

I have the following models.
models.py
class Language(models.Model):
name = models.CharField(max_length=255, unique=True)
class Subject(models.Model):
name = models.CharField(max_length=255, unique=True)
class Term(models.Model):
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
definition = models.TextField()
image = models.ImageField(default='', blank=True)
I want to write a Serializer that will return a list of subjects. Each subject has a term field, which contains None or a single object, depending on the condition.
serializers.py
class TermSerializer(serializers.ModelSerializer):
language = serializers.CharField(source='language.name')
def to_representation(self, data):
data = data.get(language=self.context['language'])
return super(TermSerializer, self).to_representation(data)
class Meta:
model = Term
fields = ('id', 'language', 'name', 'definition', 'image')
class FilteredSubjectSerializer(serializers.ListSerializer):
def to_representation(self, data):
if self.context['condition']:
terms = Term.objects.filter(language=self.context['language'])
data = data.filter(term__in=terms)
return super(FilteredSubjectSerializer, self).to_representation(data)
class SubjectSerializer(serializers.ModelSerializer):
term = serializers.SerializerMethodField()
def get_term(self, term):
if self.context['condition']:
return TermSerializer(many=False, source='term_set').data
return None
class Meta:
model = Term
list_serializer_class = FilteredSubjectSerializer
fields = ('id', 'name', 'definition', 'image', 'term')
The problem is that when condition == True, the ViewSet returns incorrect data. All fields inside term have a default value.
It works fine if I write SubjectSerializer like this:
class SubjectSerializer(serializers.ModelSerializer):
term = serializers.TermSerializer(many=False, source='term_set')
class Meta:
model = Term
list_serializer_class = FilteredSubjectSerializer
fields = ('id', 'name', 'definition', 'image', 'term')
But the case when condition == False doesn't work.
Try passing the serializer the instance and the context.
Like this:
def get_term(self, instance):
if self.context['condition']:
return TermSerializer(many=False, source='term_set', instance=instance, context=self.context).data
return None

django custom serializer field not working

I am trying to add an additional field in the serializer but at the moment of adding it I get the following error
error view
During handling of the above exception ('MovieSerializer' object has no attribute 'len_name')
I have seen many posts and all of them are working, what could it be?
this is my code
Model:
from django.db import models
class Movie(models.Model):
title = models.CharField(max_length=255, unique=True)
description = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(default=None, null=True)
def __str__(self):
return self.title
serializer:
from rest_framework import serializers
from watchlist_app.models import Movie
from watchlist_app.api.validations import MovieValidations
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField(method_name='len_name')
class Meta:
model = Movie
fields = "__all__"
read_only_fields = ['id', 'len_name', 'created_at']
required_fields = ['title', 'description']
#staticmethod
def create(self, validated_data):
return Movie.objects.create(**validated_data)
#staticmethod
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get('description', instance.description)
instance.active = validated_data.get('active', instance.active)
instance.save()
return instance
#staticmethod
def get_len_name(self, obj):
return len(obj.title)
validators = [MovieValidations.validate_title, MovieValidations.validate_description,
MovieValidations.validate_equals]
everything works fine until I add the serializerMethodField
I think you set the function in the wrong place. The function needs to belong to the MovieSerializer.
from rest_framework import serializers
from watchlist_app.models import Movie
from watchlist_app.api.validations import MovieValidations
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField()
class Meta:
model = Movie
fields = "__all__"
...
def get_len_name(self, obj):
return len(obj.title)
use extra_fields in meta class
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField(method_name='len_name')
class Meta:
model = Movie
fields = "__all__"
extra_fields = ['len_name',]
read_only_fields = ['id', 'len_name', 'created_at']
required_fields = ['title', 'description']
*** your serializer methods in wrong indent,fix it -> Go back one indent and remode staticmethod decorator ***

DRF Add annotated field to nested serializer

I have two serializers that represent comments and their nested comments. I'm provide a queryset to viewset with annotated field likes. But my problem is that field only working in parent serializer. When i add this field to nested serializer i got error
Got AttributeError when attempting to get a value for field likes on serializer CommentChildrenSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Comment instance.
Original exception text was: 'Comment' object has no attribute 'likes'.
Here is some my code. Thanks
Models.py
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
slug = models.SlugField(blank=True)
body = models.TextField()
tags = TaggableManager(blank=True)
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-pub_date']
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE,
related_name='comments')
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
text = models.TextField(max_length=500)
pub_date = models.DateTimeField(auto_now=True)
parent = models.ForeignKey('self', blank=True, null=True,
on_delete=models.CASCADE, related_name='children')
class Meta:
ordering = ['-pub_date']
class Vote(models.Model):
comment = models.ForeignKey(Comment, on_delete=models.CASCADE,
related_name='votes')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
choice = models.BooleanField(null=True)
Serializers.py
class PostRetrieveSerializer(PostSerializer):
comments = CommentSerializer(read_only=True, many=True)
author = AuthorInfoSerializer(serializers.ModelSerializer)
class Meta:
model = Post
fields = ['id', 'author', 'slug', 'title', 'body', 'tags', 'pub_date', 'comments']
class CommentChildrenSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
likes = serializers.IntegerField()
class Meta:
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'parent', 'likes']
class CommentSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
children = CommentChildrenSerializer(many=True)
likes = serializers.IntegerField()
class Meta:
ordering = ['pub_date']
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'children', 'likes']
Views.py
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().prefetch_related(
Prefetch('comments', queryset=Comment.objects.filter(parent__isnull=True)
.annotate(likes=Count('votes__choice'))))
serializer_class = PostSerializer
permission_classes = [IsOwnerOrAdminOrReadOnly]
pagination_class = PostPagination
lookup_field = 'slug'
def get_serializer_class(self):
""""
Attach related comments
when get post detail
"""
if self.action == 'retrieve':
return PostRetrieveSerializer
return self.serializer_class
def perform_create(self, serializer):
serializer.save(author=self.request.user)
maybe you can do something like this, adding the like field in each child comment.
queryset = Post.objects.all()\
.prefetch_related(
Prefetch(
'comments',
queryset=Comment.objects\
.filter(parent__isnull=True)\
.annotate(likes=Count('votes__choice'))\
.prefetch_related(
'children',
queryset=Comments.objects.all()\
.annotate(likes=Count('votes__choice'))
)
)
)
I hope this help you.
Regards!
On your model level, add a custom property like the below.
class Comment(models.Model):
...
class Meta:
ordering = ['-pub_date']
#property
def likes(self):
return self.votes.count()
On your serializer add SerializerMethodField
class CommentChildrenSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
likes = serializers.SerializerMethodField() # Change here
class Meta:
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'parent', 'likes']
# method for the SerializerMethodField
def get_likes(self, obj):
return obj.likes
Update both Comment related serializers. I believe this approach is simpler than the current implementation.

How to deal with nested serializer fields in Django Rest Framework?

I have nested serializer (AmountSerializer). I need a field meal_name in one ViewSet. But when this field is nested, I don't need it to be seen in endpoint(in MealSerializer). How to exclude field from nested serializer when is it actually nested?
models.py:
class MealType(models.Model):
name = models.TextField()
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.TextField()
def __str__(self):
return self.name
class Meal(models.Model):
name = models.TextField()
type = models.ForeignKey(MealType, on_delete=models.CASCADE, default=None)
recipe = models.TextField()
photo = models.ImageField(null=True, height_field=None, width_field=None, max_length=None,upload_to='media/')
ingredients = models.ManyToManyField(Ingredient)
def __str__(self):
return self.name
class IngredientAmount(models.Model):
ingredient_name = models.ForeignKey(Ingredient, on_delete=models.CASCADE, default=None)
amount = models.FloatField(default=None)
meal = models.ForeignKey(Meal, on_delete=models.CASCADE, default=None, related_name='meal_id')
class Meta:
ordering = ['meal']
def __str__(self):
return self.ingredient_name
serializers.py:
class AmountSerializer(serializers.ModelSerializer):
ingredient_name= serializers.ReadOnlyField(source='ingredient_name.name')
-->#meal_name = serializers.ReadOnlyField(source='meal.name')
#I CAN'T use ReadOnlyField( #with write_only=True)
#i trired use PrimaryKeyRelatedField
# butgot AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.
class Meta:
model = IngredientAmount
fields = ('ingredient_name','amount','meal_name')
class MealSerializer(serializers.ModelSerializer):
type_name= serializers.ReadOnlyField(source='type.name')
ingredients = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
amount = AmountSerializer(read_only=True, many=True,source='meal_id')
class Meta:
model = Meal
fields = ('id', 'name', 'type_name', 'recipe', 'photo', 'ingredients','amount')
I'd rather use a trick to exclude some of the fields that are not needed in certain situations. You can inherit your serializer from ExcludeFieldsModelSerializer, and exclude any fields that you want so that the serializer will not serialize that field.
class ExcludeFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `exclude_fields` argument that
controls which fields should be excluded from the serializer.
Plagiarised from https://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'exclude_fields' arg up to the superclass
exclude_fields = kwargs.pop('exclude_fields', None)
# Instantiate the superclass normally
super(ExcludeFieldsModelSerializer, self).__init__(*args, **kwargs)
if exclude_fields is not None:
# Drop any fields that are specified in the `exclude_fields` argument.
drop = set(exclude_fields)
for field_name in drop:
self.fields.pop(field_name)
class AmountSerializer(ExcludeFieldsModelSerializer):
ingredient_name= serializers.ReadOnlyField(source='ingredient_name.name')
meal_name = serializers.CharField(read_only=True, source='meal.name')
class Meta:
model = IngredientAmount
fields = ('ingredient_name','amount','meal_name')
class MealSerializer(serializers.ModelSerializer):
type_name= serializers.ReadOnlyField(source='type.name')
ingredients = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
amount = AmountSerializer(read_only=True, many=True, source='meal_id', exclude_fields={'meal_name'})
class Meta:
model = Meal
fields = ('id', 'name', 'type_name', 'recipe', 'photo', 'ingredients','amount')

Django call 'id' expected a number but got string

Django errors with django-import-export libraries.
I want to import data from excel to db via django admin. I use for it django-import-export, but i got Field 'id' expected a number but got 'HPI'.
Excel file contains
I found answer, that I have to add exclude = ('id',), but it didn't help. Also i did migrations, it didn't help too.
How to fix it and have ability to import 6 columns data from excel to db via django admin?
models.py
from django_mysql.models import JSONField, Model
from django.db import models
class Category(Model):
title = models.CharField(max_length=100)
class Meta:
ordering = ('-id',)
verbose_name = 'Category'
verbose_name_plural = 'Categories'
def __str__(self):
return self.title
class Tag(Model):
title = models.CharField(max_length=100)
class Meta:
ordering = ('-id',)
def __str__(self):
return self.title
class Type(Model):
title = models.CharField(max_length=100)
class Meta:
ordering = ('-id',)
verbose_name = 'Type'
verbose_name_plural = 'Types'
def __str__(self):
return self.title
class Macro(Model):
type = models.ForeignKey(
Type,
max_length=100,
null=True,
blank=True,
on_delete=models.SET_NULL)
tags = models.ManyToManyField(Tag, blank=True)
category = models.ForeignKey(
Category, null=True, blank=True, on_delete=models.SET_NULL)
abbreviation = models.CharField(max_length=100, unique=True)
title = models.CharField(max_length=100, verbose_name='Title')
content = models.TextField(max_length=1000, null=True, blank=True)
class Meta:
ordering = ('-id',)
def __str__(self):
return self.title
admin.py
from django.contrib import admin
from import_export import resources
from import_export.admin import ImportExportModelAdmin
from .models import Category, Tag, Type, Macro
class MacroResource(resources.ModelResource):
class Meta:
model = Macro
skip_unchanged = True
report_skipped = True
exclude = ('id', )
export_order = ('type', 'tags', 'category', 'abbreviation', 'title', 'content')
#admin.register(Macro)
class MacroAdmin(ImportExportModelAdmin):
resource_class = MacroResource
list_display = ('id', 'type', 'tags_list', 'category', 'abbreviation', 'title', 'content')
search_fields = ('title', 'category__title', 'type__title', 'abbreviation', 'content', )
def tags_list(self, obj):
tags = [t for t in obj.tags.all()]
return ' '.join(str(tags)) if tags else '-'
#admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('id', 'title')
#admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ('id', 'title')
def __str__(self):
return self.title
#admin.register(Type)
class TypeAdmin(admin.ModelAdmin):
list_display = ('id', 'title')
The problem was with ForeignKey and ManyToMany fields of a database model. So django-import-export library need to get widgets for this fields.
More about it here: https://django-import-export.readthedocs.io/en/latest/api_widgets.html#import_export.widgets.ForeignKeyWidget
Solution:
admin.py
class MacroResource(resources.ModelResource):
type = fields.Field(
column_name='type',
attribute='type',
widget=ForeignKeyWidget(Type, 'title'))
category = fields.Field(
column_name='category',
attribute='category',
widget=ForeignKeyWidget(Category, 'title'))
tags = fields.Field(
column_name='tags',
attribute='tags',
widget=ManyToManyWidget(Tag, field='title'))
class Meta:
model = Macro
skip_unchanged = True
report_skipped = True
exclude = ('id', )
import_id_fields = ('title',)
fields = ('type', 'tags', 'category', 'abbreviation', 'title', 'content')
instead of
class MacroResource(resources.ModelResource):
class Meta:
model = Macro
skip_unchanged = True
report_skipped = True
exclude = ('id', )
export_order = ('type', 'tags', 'category', 'abbreviation', 'title', 'content')
Django-import-export expects the first column to be id.
If these are new objects, simply leave the id column blank. Otherwise, put the database id of the object in that field.
If you're not able to modify the file, or don't want to, and you will always be adding new rows to the database (not modifying existing ones), you can create an id field dynamically in your resource class by overriding the method before_import and forcing get_instance to always return False.
class MacroResource(resources.ModelResource):
def before_import(self, dataset, using_transactions, dry_run, **kwargs):
dataset.insert_col(0, col=["",]*dataset.height, header="id")
def get_instance(self, instance_loader, row):
return False
class Meta:
model = Macro
skip_unchanged = True
report_skipped = True
export_order = ('type', 'tags', 'category', 'abbreviation', 'title', 'content')
The attributes where you are using a foreign key in the model,
you need to specify the id of the parent model and not the value in the xlsx/csv file.

Categories