I want to tag users in an image and save it, I used nested serializer since you can tag more than one user in an image.
The problem is that the image is saved without the tags(they are none).
Here is the codes:
models.py
class TagUsername(models.Model):
# the tag is person
tag = models.ManyToManyField(User, related_name='tag_username')
image = models.ForeignKey(Image)
# # De tection Rectangle specs(width,height, coordinate x & coordinate y)
# width = models.FloatField()
# length = models.FloatField()
# xCoordinate = models.FloatField()
# yCoordinate = models.FloatField()
# who added this tag
user = models.ForeignKey(User, on_delete=models.CASCADE)
serializers.py
class TagUsernameSerializer(serializers.ModelSerializer):
tag = UsernameTagSerializer(read_only=True, many=True)
user = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field="username")
image = serializers.CharField(source='image_id')
class Meta:
model = TagUsername
fields = ('tag', 'user', 'image')
UsernameTagSerializer:
class UsernameTagSerializer(serializers.ModelSerializer):
# username = serializers.SlugRelatedField(queryset=User.objects.all(), slug_field="username")
class Meta:
model = User
# fields I want only
fields = ('username', )
Any idea whats wrong !
You need to override create method to save nested objects. Try this:
def create(self, validated_data):
tag_username = super().create(validated_data)
for tag in validated_data['tag']:
user = User.objects.get(username=tag['username']
tag_username.tag.add(user)
return tag_username
You can find more details about writable nested serialization in docs.
Related
I'm fairly new to Django and I'm trying to make a POST request with nested objects. This is the data that I'm sending:
{
"id":null,
"deleted":false,
"publishedOn":2022-11-28,
"decoratedThumbnail":"https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg",
"rawThumbnail":"https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg",
"videoUrl":"https://www.youtube.com/watch?v=jNQXAC9IVRw",
"title":"Video with tags",
"duration":120,
"visibility":1,
"tags":[
{
"id":null,
"videoId":null,
"videoTagId":42
}
]
}
Here's a brief diagram of the relationship of these objects on the database
I want to create a video and pass in an array of nested data so that I can create multiple tags that can be associated to a video in a many to many relationship. Because of that, the 'id' field of the video will be null and the 'videoId' inside of the tag object will also be null when the data is being sent. However I keep getting a 400 (Bad request) error saying {tags: [{videoId: [This field may not be null.]}]}
I'm trying to override the create method inside VideoManageSerializer so that I can extract the tags and after creating the video I can use that video to create those tags. I don't think I'm even getting to the create method part inside VideoManageSerializer as the video is not created on the database. I've been stuck on this issue for a few days. If anybody could point me in the right direction I would really appreciate it.
I'm using the following serializers:
class VideoManageSerializer(serializers.ModelSerializer):
tags = VideoVideoTagSerializer(many=True)
class Meta:
model = Video
fields = ('__all__')
# POST
def create(self, validated_data):
tags = validated_data.pop('tags')
video_instance = Video.objects.create(**validated_data)
for tag in tags:
VideoVideoTag.objects.create(video=video_instance, **tag)
return video_instance
class VideoVideoTagSerializer(serializers.ModelSerializer):
class Meta:
model = VideoVideoTag
fields = ('__all__')
This is the view which uses VideoManageSerializer
class VideoManageViewSet(GenericViewSet, # generic view functionality
CreateModelMixin, # handles POSTs
RetrieveModelMixin, # handles GETs
UpdateModelMixin, # handles PUTs and PATCHes
ListModelMixin):
serializer_class = VideoManageSerializer
queryset = Video.objects.all()
These are all the models that I'm using:
class Video(models.Model):
decoratedThumbnail = models.CharField(max_length=500, blank=True, null=True)
rawThumbnail = models.CharField(max_length=500, blank=True, null=True)
videoUrl = models.CharField(max_length=500, blank=True, null=True)
title = models.CharField(max_length=255, blank=True, null=True)
duration = models.PositiveIntegerField()
visibility = models.ForeignKey(VisibilityType, models.DO_NOTHING, related_name='visibility')
publishedOn = models.DateField()
deleted = models.BooleanField(default=0)
class Meta:
managed = True
db_table = 'video'
class VideoTag(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
deleted = models.BooleanField(default=0)
class Meta:
managed = True
db_table = 'video_tag'
class VideoVideoTag(models.Model):
videoId = models.ForeignKey(Video, models.DO_NOTHING, related_name='tags', db_column='videoId')
videoTagId = models.ForeignKey(VideoTag, models.DO_NOTHING, related_name='video_tag', db_column='videoTagId')
class Meta:
managed = True
db_table = 'video_video_tag'
I would consider changing the serializer as below,
class VideoManageSerializer(serializers.ModelSerializer):
video_tag_id = serializers.PrimaryKeyRelatedField(
many=True,
queryset=VideoTag.objects.all(),
write_only=True,
)
tags = VideoVideoTagSerializer(many=True, read_only=True)
class Meta:
model = Video
fields = "__all__"
# POST
def create(self, validated_data):
tags = validated_data.pop("video_tag_id")
video_instance = Video.objects.create(**validated_data)
for tag in tags:
VideoVideoTag.objects.create(videoId=video_instance, videoTagId=tag)
return video_instance
Things that have changed -
Added a new write_only field named video_tag_id that supposed to accept "list of PKs of VideoTag".
Changed the tags field to read_only so that it won't take part in the validation process, but you'll get the "nested serialized output".
Changed create(...) method to cooperate with the new changes.
The POST payload has been changed as below (note that tags has been removed and video_tag_id has been introduced)
{
"deleted":false,
"publishedOn":"2022-11-28",
"decoratedThumbnail":"https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg",
"rawThumbnail":"https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg",
"videoUrl":"https://www.youtube.com/watch?v=jNQXAC9IVRw",
"title":"Video with tags",
"duration":120,
"visibility":1,
"video_tag_id":[1,2,3]
}
Refs
DRF: Simple foreign key assignment with nested serializer?
DRF - write_only
DRF - read_only
I've been bugging on this issue for some time now. I have two models : Acquisitions and RawDatas.
Each RawData have one Acquisition, but many RawDatas can have the same Acquisition.
I want to create or get the instance of Acquisition automatically when I create my RawDatas. And I want to be able to have all informations using the serializer.
class Acquisitions(models.Model):
class Meta:
unique_together = (('implant', 'beg_acq', 'duration_acq'),)
id = models.AutoField(primary_key=True)
implant = models.ForeignKey("Patients", on_delete=models.CASCADE)
beg_acq = models.DateTimeField("Beggining date of the acquisition")
duration_acq = models.DurationField("Duration of the acquisition")
class RawDatas(models.Model):
class Meta:
unique_together = (('acq', 'data_type'),)
id = models.AutoField(primary_key=True)
acq = models.ForeignKey("Acquisitions", on_delete=models.CASCADE)
data_type = models.CharField(max_length=3)
sampling_freq = models.PositiveIntegerField("Sampling frequency")
bin_file = models.FileField(db_index=True, upload_to='media')
And my serializers are these :
class AcquisitionSerializer(serializers.ModelSerializer):
class Meta:
model = Acquisitions
fields = ('id', 'implant', 'beg_acq', 'duration_acq')
class RawDatasSerializer(serializers.ModelSerializer):
acq = AcquisitionSerializer()
class Meta:
model = RawDatas
fields = ('id', 'data_type', 'sampling_freq', 'bin_file', 'acq')
def create(self, validated_data):
acq_data = validated_data.pop('acq')
acq = Acquisitions.objects.get_or_create(**acq_data)
RawDatas.objects.create(acq=acq[0], **validated_data)
return rawdatas
My problem is that, using this, if my instance of Acquisitions already exists, I get a non_field_errors or another constraint validation error.
I would like to know what is the correct way to handle this please ?
So I can automatically create this using the nested serializer, and when I only want to have informations (such as a GET request), I can have all the field I need (every field of the two models).
Thanks in advance for your help !
Try this:
class AcquisitionSerializer(serializers.ModelSerializer):
class Meta:
model = Acquisitions
fields = ('id', 'implant', 'beg_acq', 'duration_acq')
class RawDatasSerializer(serializers.ModelSerializer):
class Meta:
model = RawDatas
fields = ('id', 'data_type', 'sampling_freq', 'bin_file', 'acq')
def create(self, validated_data):
acq_data = validated_data.pop('acq')
acq = Acquisitions.objects.filter(id=acq_data.get('id')).first()
if not acq:
acq = AcquisitionSerializer.create(AcquisitionSerializer(), **acq_data)
rawdata = RawDatas.objects.create(acq=acq, **validated_data)
return rawdata
I'm trying to write a "def create" method to perform nested serialization for multiple objects.
def create(self, validated_data):
suggested_songs_data = validated_data.pop('suggested_songs')
suggest_song_list = list()
for song_data in suggested_songs_data:
song = Song.objects.create(**song_data)
suggest_song_list.append(song)
message = Messages.objects.create(suggested_songs=suggest_song_list, **validated_data)
return message
Here is my schema:
class MessagesSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
suggested_songs = SongSerializer(many=True)
class Meta:
model = Messages
fields = ('id','owner','url','suggested_songs',)
#fields = ('id','url','suggested_songs',)
class SongSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Song
fields =('id','title','artist','album','albumId','num_votes','cleared')
read_only_fields = ('song_id')
But I am getting this error
Cannot assign "[<Song: Song object>, <Song: Song object>]": "Messages.suggested_songs" must be a "Song" instance.
Any advice?
EDIT:
Here is the model.
class Messages(models.Model):
owner = models.OneToOneField(User, primary_key=True, related_name='user_messages', editable=False) #TODO, change owner to 'To'
#suggested_songs = models.ForeignKey(Song, null=True, blank=True)
suggested_songs = models.ManyToManyField(Song, related_name='suggested_songs')
You can't create manyToMany relations without the objects already created. You must first create the objects and then make the relation.
Something like:
def create(self, validated_data):
suggested_songs_data = validated_data.pop('suggested_songs')
message = Messages.objects.create(**validated_data)
for song_data in suggested_songs_data:
song = Song.objects.create(**song_data)
message.suggested_songs.add(song)
return message
How to create an object for a Django model with a many to many field?
From above question i come to know we can save Many to Many field later only.
models.py
class Store(models.Model):
name = models.CharField(max_length=100)
class Foo(models.Model):
file = models.FileField(upload_to='')
store = models.ManyToManyField(Store, null=True, blank=True)
views.py
new_track.file = request.FILES['file']
new_track.save()
And file uploading working fine then later i modify my code to add store then i am here...
Now i am sure db return id's here. Then i tried with my below code but that's given me error only
x = new_track.id
new = Foo.objects.filter(id=x)
new.store.id = request.POST['store']
new.save()
ok so the error here is 'QuerySet' object has no attribute 'store'
And also i tried with add that's now working either.
So the question is how to save()
the right way of saving objects with manytomany relations would be:
...
new_track.file = request.FILES['file']
new_track.save()
new_store = Store.objects.get(id=int(request.POST['store']))
new_track.store.add(new_store)
As of 2020, here's my approach to saving ManyToMany Field to a given object.
Short Answer
class HostingRequestView(View):
def post(self, request, *args, **kwargs):
form = VideoGameForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.updated_by = request.user
obj.save()
selected_categories = form.cleaned_data.get('category') #returns list of all selected categories e.g. ['Sports','Adventure']
#Now saving the ManyToManyField, can only work after saving the form
for title in selected_categories:
category_obj = Category.objects.get(title=title) #get object by title i.e I declared unique for title under Category model
obj.category.add(category_obj) #now add each category object to the saved form object
return redirect('confirmation', id=obj.pk)
Full Answer
models.py
class Category(models.Model):
title = models.CharField(max_length=100, null=True, unique=True)
class VideoGame(models.Model):
game_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50, blank=False, null=False)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, on_delete=models.CASCADE)
category = models.ManyToManyField(Category) #ManyToMany Category field
date_added = models.DateTimeField(auto_now_add=True, verbose_name="date added")
forms.py ModelForm
class VideoGameForm(forms.ModelForm):
CATEGORIES = (
('Detective', 'Detective'),
('Sports', 'Sports'),
('Action', 'Action'),
('Adventure', 'Adventure'),
)
category = forms.MultipleChoiceField(choices=CATEGORIES, widget=forms.SelectMultiple())
class Meta:
model = VideoGame
fields = ['name', 'category', 'date_added']
views.py on POST
class HostingRequestView(View):
def post(self, request, *args, **kwargs):
form = VideoGameForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.updated_by = request.user
obj.save()
selected_categories = form.cleaned_data.get('category') #returns list of all selected categories e.g. ['Sports','Adventure']
#Now saving the ManyToManyField, can only work after saving the form
for title in selected_categories:
category_obj = Category.objects.get(title=title) #get object by title i.e I declared unique for title under Category model
obj.category.add(category_obj) #now add each category object to the saved form object
return redirect('confirmation', id=obj.pk)
URL path for redirect
urlpatterns = [
path('confirmation/<int:id>/', Confirmation.as_view(), name='confirmation'),
]
I hope this can be helpful. Regards
new.stores.all()
returns all stores linked to the object.
Maybe:
Change Foo to Tracks
Tracks.objects.filter(id=x) to Tracks.objects.get(id=x)
Let me know how it goes
why this confusion so much.. you are getting the id there then, call the store like
new_track.save()
new_track.store.add(request.POST['store'])
I have following problem:
I'm writing an AJAX view in django that serves JSON data about image list from a model that uses ImageSpecField from django-imagekit extension:
class Image(models.Model):
title = models.CharField(max_length=120)
img = models.ImageField(upload_to="images")
thumb = ImageSpecField(source="img",
id="core:image:image_thumbnail"
)
objects = models.Manager()
json_data = JSONConvertibleManager()
The model uses custom manager for conversion into JSON (JSONConvertibleManager) using built-in Django serializer (instance of django.core.serializers).
My problem is that all the fields are properly serialized except for the ImageSpecField which is getting completely ommited. Is it possible to return instance.thumb.url value during serialization?
Just for info I was using Django Rest Framework and so used the serializer class from that library.
My model:
class Photo(models.Model):
""" Photograph """
title = models.CharField(max_length=100)
slug = models.SlugField(max_length=255)
original_image = models.ImageField(upload_to='boxes')
formatted_image = ImageSpecField(source='original_image', format='JPEG',
options={'quality': 90})
thumbnail = ImageSpecField([Adjust(contrast=1.2, sharpness=1.1),
ResizeToFill(200, 115)], source='original_image',
format='JPEG', options={'quality': 90})
num_views = models.PositiveIntegerField(editable=False, default=0)
My serializer:
class PhotoSerializer(serializers.ModelSerializer):
original_image = serializers.Field('original_image.url')
thumbnail = serializers.Field('thumbnail.url')
class Meta:
model = Photo
fields = ('id', 'title', 'original_image', 'thumbnail',)
Unfortunately, the accepted answer does not work anymore due to changes in DRF (prob. v2.x). Substitute this line and it will work with current versions (3.5.3):
thumbnail = serializers.ReadOnlyField(source="thumbnail.url")
Another solution to have more control (e.g. url modifications) would be:
class PhotoSerializer(serializers.ModelSerializer):
original_image = serializers.SerializerMethodField()
class Meta:
model = Photo
fields = ('id', 'title', 'original_image')
def get_original_image(self, obj):
# some processing
return obj.original_image.url
A little improvement based on the nice solution given by #Insa ...
class PhotoSerializer(serializers.ModelSerializer):
original_image = serializers.SerializerMethodField()
class Meta:
model = Photo
fields = ('id', 'title', 'original_image')
def get_original_image(self, obj):
if bool(obj.original_image):
return self.context['request'].build_absolute_uri(obj.original_image.url)
return ''
to obtain the absolute url for the thumbnail, as happens by default for all ImageFields