10👍
Override the create()
method of the serializer as below,
class MovieSerializer(serializers.ModelSerializer):
genre = serializers.ListSerializer(child=serializers.CharField())
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
def create(self, validated_data):
genre = validated_data.pop('genre',[])
movie = super().create(validated_data)
genre_qs = Genre.objects.filter(name__in=genre)
movie.genre.add(*genre_qs)
return movie
17👍
Assuming that you used StringRelatedField
in your MovieSerializer
like this:
class MovieSerializer(serializers.ModelSerializer):
genre = serializers.StringRelatedField(many=True)
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
the result would look like this when retrieving a list of movies:
[
{
"popularity": 83.0,
"director": "Victor Fleming",
"genre": [
"Adventure",
"Family",
"Fantasy",
"Musical"
],
"imdb_score": 8.3,
"name": "The Wizard of Oz"
}
]
But if you want to create a new movie, then it won’t work because StringRelatedField
is read-only.
You can however create your custom related field.
This is the complete serializers.py
:
from rest_framework import serializers
from .models import Genre, Movie
class GenreRelatedField(serializers.RelatedField):
def display_value(self, instance):
return instance
def to_representation(self, value):
return str(value)
def to_internal_value(self, data):
return Genre.objects.get(name=data)
class MovieSerializer(serializers.ModelSerializer):
genre = GenreRelatedField(
queryset=Genre.objects.all(),
many=True
)
class Meta:
model = Movie
fields = (
'popularity',
'director',
'genre',
'imdb_score',
'name',
)
This is a simple example that can be highly customized in many ways.
The method display_value
defines how the object Genre is displayed, for example in the form. Here it just returns the object Genre i.e. the output of __str__
.
The method to_representation
defines how the object Genre is displayed in the output (JSON or XML). It’s very similar to the previous method, but here we explicitly have to convert Genre to string. Certainly you can create a more complex output according to your requirements.
The method to_internal_value
solves your actual problem by getting an object Genre for the given value. If you have a more complex method to_representation
here you would need expanded logics to get the appropriate object.
Using this approach you can post a JSON in your desired form, specifying the genre names instead of their ids.
I hope this example helps other people too.
- Pycharm Django Debugging is really slow
- X-editable inline editing in Django – how to get CSRF protection?
- Django queryset __contains case sensitive?
- Django template img src not working
4👍
On solution is to override genre
field in your serializer, to accept a list of strings like this:
class MovieSerializer(serializers.ModelSerializer):
genre = serializer.ListField(child=serializers.CharField())
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
def validate(self, data):
genre = data.get('genre', [])
genre_obj_list = [Genre.objects.get(name=name) for name in genre.all()]
data.update({'genre': genre_obj_list})
return data
And on validate method try to fetch each object by their names and put in a new list and update data
result with new list of objects. (I know it’s not the cleanest solutions, but it works fine)
you could also try to use MethodSerializer
or define a GenreSerializer
and fetch objects in that by their names and use that in the parent serializer as an input.
- Django: Natural Sort QuerySet
- Django: why are Django model fields class attributes?
- Fix Conflicting migrations detected in Django1.9
2👍
The easy solution would be to change the Genre
model to use name
as primary key, like so:
class Genre(models.Model):
name = models.CharField(max_length=30, primary_key=True)
Not that it matters much here, but this will also save a column in the database, since the auto generated id
column will disappear 🙂
Update
After some discussion in the comments to this answer, I find it important to mentions that using ANY type as primary key, you should also avoid changing that field afterwards.
This is because a change to the primary key, also necessitates an update to all the foreign keys pointing to that primary key, and (to put it in terms of this question) even though your table with genres may be relatively small, you may have a significant amount of movies pointing to each genre.
- Django form with ManyToMany field with 500,000 objects times out
- Django: test failing on a view with @login_required
1👍
If you always pass name to your serializer, you can add foreign-key field in Model defination. link For you case
class Movie(models.Model):
popularity = models.FloatField(max_length=10)
director = models.CharField(max_length=30)
genre = models.ManyToManyField(Genre, db_column='name')
imdb_score = models.FloatField(max_length=10)
name = models.CharField(max_length=30)
- How do I memoize expensive calculations on Django model objects?
- TimeField format in Django template
- Django Channels Error – Cannot import BACKEND 'asgi_redis.RedisChannelLayer'
- Django: Generic views based 'as_view()' method
- Django datetime field – convert to timezone in view
- Django AttributeError: 'DatabaseOperations' object has no attribute 'select'
- Nullable TextField or empty string in Django model?
0👍
You can use the serializer field method. This will preserve the PK declaration on the model and you can exclude the field of the ForeignKey.
Example:
class ModelSerializer(serializers.ModelSerializer):
"""Example Serializer."""
foreignkey_name: Union[SerializerMethodField, str] = SerializerMethodField()
@staticmethod
def get_foreignkey_name(instance: Product) -> str:
"""
Get the foreignkey.name value.
Parameters
----------
instance: Model
Returns
-------
str
"""
try:
return instance.product_type.name
except AttributeError:
return "Product Type field name has no value"
class Meta:
"""
Set the foreignkey model and fields.
"""
model: Type[ForeignKeyModel] = ForeignKeyModel
exclude: tuple[str, str] = foreignkey_field # this will exclude the default foreignkey field in the serializer that contains the FK pk.
- Django queryset exclude() with multiple related field clauses
- Django: IntegrityError during Many To Many add()
- Allow a task execution if it's not already scheduled using celery
- Django can't access raw_post_data
- Installing PIL to use with Django on Mac OS X