Django REST Framework: serializing an ImageField - python

I'm having huge issues with Django REST Framework and its serializers in relation to a model with an ImageField
Here's the code for my model:
class Photo(models.Model):
id = models.AutoField(primary_key=True)
image_file = models.ImageField(upload_to=generate_image_filename_and_path,
width_field="image_width",
height_field="image_height",
null=False,
blank=False)
image_width = models.IntegerField(null=False, blank=False)
image_height = models.IntegerField(null=False, blank=False)
and here's the serializer:
class PhotoSerializer(serializers.ModelSerializer):
class Meta:
model = Photo
fields = ('image_file',)
read_only = ('id', 'image_width', 'image_height')
id, image_width and image_height are set as read_only fields, as I want them to be generated by the model class. ID is something I couldn't even provide when first creating a model instance.
I've had much trouble. In the current state, I have a test that looks like this:
class PhotoSerializerTestCase(TestCase):
def test_native_data_type_serialization(self):
img_file = create_generic_image_file('test_img.jpg',
'image/jpeg')
p = Photo(image_file=img_file)
p.save()
serialization = PhotoSerializer(p)
expected_id = p.id
expected_image_width = p.image_width
expected_image_height = p.image_height
print(serialization)
actual_id = serialization.data['id']
actual_image_width = serialization.data['image_width']
actual_image_height = serialization.data['image_height']
self.assertEqual(expected_id, actual_id)
self.assertEqual(expected_image_width, actual_image_width)
self.assertEqual(expected_image_height, actual_image_height)
Here's the function that I call to create the generic image file for the test:
def create_generic_image_file(name, content_type):
path = '\\test_files\\test_image_generic.jpg'
file_path = BASE_DIR + path
file_name = name
content = open(file_path, 'rb').read()
image_file = SimpleUploadedFile(name=file_name,
content=content,
content_type=content_type)
return image_file
In present state, I'm left with an error as follows:
Creating test database for alias 'default'...
..PhotoSerializer(<Photo: Photo object>):
image_file = ImageField()
E.
======================================================================
ERROR: test_native_data_type_serialization (photo_broker.test_serializers.PhotoSerializerTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<local_path>\test_serializers.py", line 25, in test_native_data_type_serialization
actual_id = serialization.data['id']
KeyError: 'id'
I can't even recall all the different errors that I've gotten previously, but I'm sure they'll pop up when this one's cleared.
edit:
Ok, I cleared the key error by moving all the fields back to PhotoSerializer.Meta.fields instead of PhotoSerializer.Meta.read_only. Now I'm back to one of my original questions. Let me explain with a test:
def test_create_model_from_serializer(self):
img_file = create_generic_image_file('test_img.jpg',
'image/jpeg')
data = {'image_file': img_file,
'id': 7357,
'image_width': 1337,
'image_height': 715517}
s = PhotoSerializer(data=data)
print(type(s))
print(s.is_valid())
print(s)
This yields clearly likable output and the serializer validates without a cough, but this is not what I want to be able to do. The ID and the dimensions should be calculated by the model. Not to whatever values by the serializer.
If I remove them from the fields attribute, then I can never get them into my serialization either. That's where I need them.
Or am I getting confused here and there actually is a valid use case, where I originally create the model normally, without going through the serializer?

Related

I get a Type Error when posting data using via REST. It says I may have a writable field on the serializer class that is not a valid argument

full error:
Got a `TypeError` when calling `Certificate.objects.create()`. This may be because you have a writable field on the serializer class that is not a valid argument to `Certificate.objects.create()`. You may need to make the field read-only, or override the CertificateSerializer.create() method to handle this correctly.
I have a certificate model which has the clean() and get_remote_image() methods since there are two options of posting image to my database, either with urls or file upload - so when one is already selected the other one is no longer needed. if url is the selected option, it saves to the image field.
I get the error when posting data.
models.py
class Certificate(models.Model):
certificate_name = models.CharField(max_length=50)
template_img = models.ImageField(blank=True, default='', upload_to='certificate_templates')
template_url = models.URLField(blank=True, default='')
names_file = models.FileField(blank=True, default='', upload_to='names_files')
names_csv = models.TextField(blank=True, default='')
Y_RATIO = 1.6268
def get_remote_image(self):
if self.template_url and not self.template_img:
img_result = requests.get(self.template_url)
img_name = os.path.basename(self.template_url)
with open(os.path.join(TEMP_DIR, img_name), 'wb') as img_file:
img_file.write(img_result.content)
self.template_img.save(
img_name,
open(os.path.join(TEMP_DIR, img_name), 'rb')
)
self.save()
def clean(self):
if not self.template_img and not self.template_url:
raise ValidationError({'template_img': 'Either one of template_img or template_url should have a value.'})
if not self.names_csv and not self.names_file:
raise ValidationError({'names_csv': 'Either one of names_csv or names_file should have a value.'})
def save(self):
if self.template_url and not self.template_img:
self.get_remote_image()
super(Certificate, self).save()
else:
super(Certificate, self).save()
serializers.py
class CertificateSerializer(serializers.ModelSerializer):
class Meta:
model = Certificate
fields = ('certificate_name', 'template_img',
'template_url', 'names_file', 'names_csv')
i saw at similar problem 1 & 2 that the save method shouldnt have arguments but mine already doesnt have arguments.

Django Admin: Store and display Images in Postgres Binary field (By obligation)

I have a small project of the backoffice for a pos program. actually the images are stored in binary field so, I must work with them.
As example I have the following model for one categories table:
models.py
class Categories(models.Model):
id = models.CharField(primary_key=True, max_length=20, default=createid())
name = models.CharField(unique=True, max_length=50)
parentid = models.ForeignKey('self', db_column='parentid', blank=True, null=True)
image = models.ImageField(upload_to="images", null=True)
def __unicode__(self): # __unicode__ on Python 2
return '('+self.id+')'+self.name
def save(self, *args, **kwargs):
try:
path1 = safe_join(os.path.abspath(settings.MEDIA_ROOT)+'\images', self.image)
image_file = open(path1,'rb')
file_content = image_file.read()
self.image=file_content
except:
filename = 'no_image.png'
path = safe_join(os.path.abspath(settings.MEDIA_ROOT), filename)
#if not os.path.exists(path):
# raise ObjectDoesNotExist
no_image = open(path, 'rb')
file_content = no_image.read()
super(Categories, self).save(*args, **kwargs)
def image_thumb(self):
if self.image:
file_like=cStringIO.StringIO(self.image)
return mark_safe(u'<img width="70" height="70" src="data:image/png;base64,%s" />') % file_like
else:
return '(No image)'
image_thumb.short_description = 'Thumb'
image_thumb.allow_tags = True
class Meta:
managed = False
db_table = 'categories'
To avoid the database unnecessery structure changes, in models.py I changed the image column type to models.ImageField. In Database it is a binary field. Doing this and by overiding save method, i try to resolve the problem.
1)But when Itry to save the uploaded file I receive the following message:
DjangoUnicodeDecodeError at /admin/app/categories/34/
'utf8' codec can't decode byte 0x89 in position 0: invalid start byte. You passed in '\x89PNG\r\n\x1a\n\x....<
2) I cannot retrieve the right format to display image in
file_like varible
I use python 2.7 and django 1.7
Any help is highly happreciated
To use the BinaryField, in the model do something like...
image_file = models.BinaryField(blank=True)
Then to read from a form POST...
image_file = request.FILES['image_file'].file.read()
Simple as that.
Try using Django's built-in BinaryField instead of the ImageField.

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.

django-imagekit - thumbnail field not getting serialized

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

Saving files to upload field in Django using a Python script

Building an application to list reports for inspections. The actual inspection report is going to be made available for download. Saving the reports to the database using django-db-file-storage.
Lots of records to process, so writing a script to do everything in bulk. Testing in the manage.py shell throws an error.
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from inspections.models import InspectionInformation, RestaurantInformation
file = open('/docs/Data/2011/12/12-07-11_1498.pdf', 'r').read()
InspectionInformation(
insp_rest_permit=RestaurantInformation.objects.get(rest_permit=int('1814')),
insp_date='2011-12-12',
insp_score='100',
insp_inspector='Philip',
insp_report=default_storage.save('report.pdf', ContentFile(file))
).save()
Traceback
Traceback (most recent call last):
File "<console>", line 6, in <module>
File "/venv/lib/python2.7/site-packages/django/core/files/storage.py", line 48, in save
name = self.get_available_name(name)
File "/venv/lib/python2.7/site-packages/django/core/files/storage.py", line 74, in get_available_name
while self.exists(name):
File "/venv/lib/python2.7/site-packages/db_file_storage/storage.py", line 77, in exists
model_class_path, content_field, filename_field, mimetype_field, filename = name.split('/')
ValueError: need more than 1 value to unpack
Models
from django.db import models
class RestaurantInformation(models.Model):
rest_permit = models.IntegerField(unique=True, verbose_name='Permit')
rest_name = models.CharField(max_length=200, verbose_name='Name')
rest_address = models.CharField(max_length=200, verbose_name='Address')
rest_city = models.CharField(max_length=100, verbose_name='City')
rest_zip = models.IntegerField(verbose_name='Zip Code')
rest_owner = models.CharField(max_length=200, verbose_name='Owner')
rest_latitude = models.CharField(max_length=40, verbose_name='Latitude')
rest_longitude = models.CharField(max_length=40, verbose_name='Longitude')
class Meta:
ordering = ['rest_name']
def __unicode__(self):
return self.rest_name + ', ' + self.rest_address + ', ' + self.rest_city
class InspectionInformation(models.Model):
insp_rest_permit = models.ForeignKey(RestaurantInformation, null=False, to_field='rest_permit')
insp_score = models.DecimalField(verbose_name='Score', decimal_places=2, max_digits=5)
insp_date = models.DateField(verbose_name='Date')
insp_inspector = models.CharField(max_length=200, verbose_name='Inspector')
insp_report = models.FileField(upload_to='restaurants.InspectionFile/bytes/filename/mimetype',
blank=True, null=True, verbose_name='Inspection Report')
class Meta:
unique_together = ("insp_rest_permit", "insp_score", "insp_date")
ordering = ['insp_date']
class InspectionFile(models.Model):
bytes = models.TextField()
filename = models.CharField(max_length=255)
mimetype = models.CharField(max_length=50)
1.It looks like db_file_storage.storage.save() expects their custom format specified to be used with the model plus the filename e.g. "console.ConsolePicture/bytes/filename/mimetype" + filename.
So for your example instead of
'report.pdf'
it would be
'restaurants.InspectionFile/bytes/filename/mimetype/report.pdf'
I looked at the documentation and it isn't clear why it's being done this way as this violates DRY by making you enter the same thing twice, but they use the same format throughout the DatabaseFileStorage class.
2.It also looks like there's a bug in the save method (line 60) where
mimetype = content.file.content_type
should be changed to
mimetype = content.content_type
And the file you pass it should be something with a content_type attribute so probably a Django SimpleUploadedFile:
from django.core.files.uploadedfile import SimpleUploadedFile
file_ = SimpleUploadedFile('report.pdf', open('/docs/Data/2011/12/12-07-11_1498.pdf', 'r').read())
The reason I think this is a bug is that when I tried passing in a mock object that looked like "content.file.content_type" I got a Python core library exception later on.

Categories