django-imagekit - thumbnail field not getting serialized - python

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

Related

Django - Sending post request using a nested serializer with many to many relationship. Getting a [400 error code] "This field may not be null] error"

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

Django forms inheritance doesn't read value from field

I have 2 similars forms so I decide to inherite one form from another and it looks like:
class EditModuleForm(forms.Form):
category_name = forms.CharField(label='Name', max_length=100)
image = forms.ImageField(label='Icon', required=False)
def clean_image(self):
image = self.cleaned_data.get('image', None)
if image:
check_image_size(image)
class NewCategoryForm(EditModuleForm):
category_slug = forms.CharField(label="Slug", max_length=10)
field_order = ['category_name', 'category_slug', 'image']
def clean_image(self):
super(NewCategoryForm, self).clean_image()
and during using NewCategoryForm image is not validated and value is None. Looks like this form can't get value from image field. Could somebody give me a hint what I'm doing wrong?
You aren't returning the cleaned data from your clean_image method, the clean_<field> methods allow us to change the data for the field and hence we must return the data from the method. Also you don't need to override the method inside your subclass:
class EditModuleForm(forms.Form):
category_name = forms.CharField(label='Name', max_length=100)
image = forms.ImageField(label='Icon', required=False)
def clean_image(self):
image = self.cleaned_data.get('image', None)
if image:
check_image_size(image)
return image
class NewCategoryForm(EditModuleForm):
category_slug = forms.CharField(label="Slug", max_length=10)
field_order = ['category_name', 'category_slug', 'image']

How to serialize a Wagtail Orderable model?

I came across a challenge which I also intent to blog about. I found a solution but, since I'm going to write about it, I wonder if there is a better way to serialize an Orderable model.
Context: My LessonPage(Page) model has a LessonPageDocuments(Orderable) model that will allow users to add multiple documents to a particular LessonPage:
class LessonPageDocuments(Orderable):
page = ParentalKey(LessonPage, on_delete=models.CASCADE,
related_name='documents')
document = models.ForeignKey(
'wagtaildocs.Document', on_delete=models.CASCADE, related_name='+'
)
panels = [
DocumentChooserPanel('document'),
]
Now, due to this projects needs and business requirements, we're creating a custom REST API instead of using Wagtail's API.
And the way I found to serialize the documents field was the following:
class LessonDetailsSerializer(serializers.ModelSerializer):
content = RichTextSerializer()
documents = serializers.SerializerMethodField()
def to_representation(self, instance):
ret = super().to_representation(instance)
video_url = ret['video_url']
ret['video_url'] = get_embed(video_url).html if video_url else ''
return ret
def get_documents(self, lesson):
"""Return serialized document fields and file URL"""
request = self.context.get('request')
doc_list = []
for doc_cluster in lesson.documents.all():
doc_list.append({
"url": request.build_absolute_uri(doc_cluster.document.file.url),
"title": doc_cluster.document.title,
"id": doc_cluster.document.pk,
})
return doc_list
class Meta:
model = LessonPage
fields = ['id', 'title', 'slug', 'description',
'video_url', 'content', 'documents']
Is there a better approach to serialize this field?
Thank you so much in advance!
I think the only way (that I know of) to do it better is to make it reusable:
wagtail_api/serializers.py
from wagtail.documents.api.v2.serializers import DocumentDownloadUrlField
from wagtail.documents.models import Document
class DocumentSerializer(serializers.ModelSerializer):
_detail_url = serializers.HyperlinkedIdentityField(view_name='api:wagtail:documents:detail')
download_url = DocumentDownloadUrlField()
class Meta:
model = Document
fields = (
'_detail_url',
'id', 'title', 'download_url',
)
my_app/serializers.py
class SomePageDocumentSerializer(serializers.ModelSerializer):
description = RichTextAPIField()
document = DocumentSerializer()
class Meta:
model = SomePage
fields = ('description', 'document')
There is also probably a way to overwrite wagtails DocumentSerializer
from ee_wagtail.apps.wagtail_api.serializers

Django nested serializer doesn't save as expected

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.

How do I set a field values of a django/mezzanine model based on the title of its foreignKey?

I would like to configure the mezzanine fork of django-filebrowser to create a subfolder when uploading an image, based on the title of a particular post within my mezzanine app.
The file field of that model requires setting "upload_to=", but I don't understand how I can make it point to a field value of its parent/foreignKey instance, rather than just a static value. I have tried defining a callable which points to exhibPost.title, as well as using it directly in the field as shown below.
I'd love to hear an explanation, I'm sure I'm misunderstanding something quite major about django here... Thanks
models.py - (imports omitted)
class exhibPost(Displayable, RichText,):
"""
An exhib post.
"""
def __unicode__(self):
return u'%s' % (self.id)
showstart = models.DateField("Show Starts")
showend = models.DateField("Show Ends")
start_time = models.TimeField(null=True, blank=True)
end_time = models.TimeField(null=True, blank=True)
summary = models.CharField(max_length=200,null=True,default=get_val)
class Meta:
verbose_name = _("exhib post")
verbose_name_plural = _("exhib posts")
ordering = ("-publish_date",)
class exhibImage(Orderable):
'''
An image for an exhib
'''
exhibPostKey = models.ForeignKey(exhibPost, related_name="images")
file = FileField(_("File"), max_length=200, format="Image",
upload_to=upload_to(
"theme.exhibImage.file",
----> exhibPost.title
)
)
class Meta:
verbose_name = _("Image")
verbose_name_plural = _("Images")
EDIT
#Anzel
The function I'm referring to is defined in my models as
def get_upload_path(instance, filename):
return os.path.join(
"user_%d" % instance.owner.id, "car_%s" % instance.slug, filename)
...and I call it in the same place that I arrowed originally.

Categories