Subclassing ImageField to compress images - django

I'm trying to subclass ImageField to add compression support. This is my current code.
(Right now I'm assuming that the files will be PNGs).
from django.utils.six import with_metaclass, string_types
from django.core.files.images import ImageFile
class CompressImageField(with_metaclass(models.SubfieldBase, models.ImageField)):
"""
Compress image files on upload-field
"""
def to_python(self, value):
value = super(CompressImageField, self).to_python(value)
if isinstance(value, ImageFile):
im = Image.open(value.name)
im.save(value.name, optimize=True, quality=100)
return ImageFile(value.name)
return value
However what is stored in the database is just u''.
Sometimes to_python() receives a unicode string, sometimes a ImageField. I'm guessing this is the serialization part, although super(CompressImageField, self).to_python(value) always returns the same type as value, often with an unchanged value.
Not sure what happens here. Any help is appreciated. Hopefully I've made myself clear enough.

You can use a django package that already do that.
Django-imagefit
Django-imagekit
Django-image-tools

Related

How to use validators on FileField content

In my model, I want to use a validator to analyze the content of a file, the thing I can not figure out is how to access the content of the file to parse through it as the file has not yet been saved (which is good) when the validators are running.
I'm not understanding how to get the data from the value passed to the validator into a file (I assume I should use tempfile) so I can then open it and evaluate the data.
Here's a simplified example, in my real code, I want to open the file and evaluate it with csv.
in Models.py
class ValidateFile(object):
....
def __call__(self, value):
# value is the fieldfile object but its not saved
# I believe I need to do something like:
temp_file = tempfile.TemporaryFile()
temp_file.write(value.read())
# Check the data in temp_file
....
class MyItems(models.Model):
data = models.FileField(upload_to=get_upload_path,
validators=[FileExtensionValidator(allowed_extensions=['cv']),
ValidateFile()])
Thanks for the help!
Take a look how this is done in the ImageField implementation:
So your ValidateFile class may be something like this:
from io import BytesIO
class ValidateFile(object):
def __call__(self, value):
if value is None:
#do something when None
return None
if hasattr(value, 'temporary_file_path'):
file = value.temporary_file_path()
else:
if hasattr(value, 'read'):
file = BytesIO(value.read())
else:
file = BytesIO(value['content'])
#Now validate your file
No need for tempfile:
The value passed to a FileField validator is an instance of FieldFile, as already mentioned by the OP.
Under the hood, the FieldFile instance might already use a tempfile.NamedTemporaryFile (source), or it might wrap an in-memory file, but you need not worry about that:
To "evaluate the data" you can simply treat the FieldFile instance as any Python file object.
For example, you could iterate over it:
def my_filefield_validator(value):
# note that value is a FieldFile instance
for line in value:
... # do something with line
The documentation says:
In addition to the API inherited from File such as read() and write(), FieldFile includes several methods that can be used to interact with the underlying file: ...
and the FieldFile class provides
... a wrapper around the result of the Storage.open() method, which may be a File object, or it may be a custom storage’s implementation of the File API.
An example of such an underlying file implementation is the InMemoryUploadedFile docs/source.
Also from the docs:
The File class is a thin wrapper around a Python file object with some Django-specific additions
Also note: class-based validators vs function-based validators

Default Image for Django easy_thumbnails

I have a model with an ImageField that I display using easy_thumbnails (|thumbnail_url).
My question is how do I display a default image if the ImageField is empty?
I would like this logic in the Model/View, NOT in the html/template.
e.g.:
DEFAULT_PICTURE = 'default.jpg'
def get_picture(self):
if self.picture:
return self.picture
else:
from DEFAULT_PICTURE
What object should get_picture() return that is compatible with easy_thumbnails?
I tried to create a new File object, like here, but it did not work.
Can you kindly provide a working example of returning an existing file to display with easy_thumbnails?
Chris (easy_thumbnails) answered here, and also on SO.
His suggestion to create a new ImageFieldFile is good, but easy_thumbnails stilled failed because the newly created ImageFieldFile had an empty instance.
So either set instance = self:
DEFAULT_PICTURE = 'default.jpg'
def get_picture(self):
if self.picture:
return self.picture
else:
return ImageFieldFile(instance=self, field=FileField(), name=DEFAULT_PICTURE)
or change alias.py line 116:
if not hasattr(target, 'instance'):
return None
should be...
if not hasattr(target, 'instance') or not target.instance:
return None
This is the code that I have used previously. It could probably be optimized a bit (and probably shouldn't have the hard coded media path), but it worked well for my small project.
def get_picture(self):
if self.picture:
return '<img src="%s" />' % self.picture['thumbnail'].url
return '{img src="/media/img/admin/icon-no.gif" alt="No Image"}'
With this, you just need to ensure that you have icon-no.gif in the appropriate path.

How does one use magic to verify file type in a Django form clean method?

I have written an email form class in Django with a FileField. I want to check the uploaded file for its type via checking its mimetype. Subsequently, I want to limit file types to pdfs, word, and open office documents.
To this end, I have installed python-magic and would like to check file types as follows per the specs for python-magic:
mime = magic.Magic(mime=True)
file_mime_type = mime.from_file('address/of/file.txt')
However, recently uploaded files lack addresses on my server. I also do not know of any method of the mime object akin to "from_file_content" that checks for the mime type given the content of the file.
What is an effective way to use magic to verify file types of uploaded files in Django forms?
Stan described good variant with buffer. Unfortunately the weakness of this method is reading file to the memory. Another option is using temporary stored file:
import tempfile
import magic
with tempfile.NamedTemporaryFile() as tmp:
for chunk in form.cleaned_data['file'].chunks():
tmp.write(chunk)
print(magic.from_file(tmp.name, mime=True))
Also, you might want to check the file size:
if form.cleaned_data['file'].size < ...:
print(magic.from_buffer(form.cleaned_data['file'].read()))
else:
# store to disk (the code above)
Additionally:
Whether the name can be used to open the file a second time, while the named temporary file is still open, varies across platforms (it can be so used on Unix; it cannot on Windows NT or later).
So you might want to handle it like so:
import os
tmp = tempfile.NamedTemporaryFile(delete=False)
try:
for chunk in form.cleaned_data['file'].chunks():
tmp.write(chunk)
print(magic.from_file(tmp.name, mime=True))
finally:
os.unlink(tmp.name)
tmp.close()
Also, you might want to seek(0) after read():
if hasattr(f, 'seek') and callable(f.seek):
f.seek(0)
Where uploaded data is stored
Why no trying something like that in your view :
m = magic.Magic()
m.from_buffer(request.FILES['my_file_field'].read())
Or use request.FILES in place of form.cleaned_data if django.forms.Form is really not an option.
mime = magic.Magic(mime=True)
attachment = form.cleaned_data['attachment']
if hasattr(attachment, 'temporary_file_path'):
# file is temporary on the disk, so we can get full path of it.
mime_type = mime.from_file(attachment.temporary_file_path())
else:
# file is on the memory
mime_type = mime.from_buffer(attachment.read())
Also, you might want to seek(0) after read():
if hasattr(f, 'seek') and callable(f.seek):
f.seek(0)
Example from Django code. Performed for image fields during validation.
You can use django-safe-filefield package to validate that uploaded file extension match it MIME-type.
from safe_filefield.forms import SafeFileField
class MyForm(forms.Form):
attachment = SafeFileField(
allowed_extensions=('xls', 'xlsx', 'csv')
)
In case you're handling a file upload and concerned only about images,
Django will set content_type for you (or rather for itself?):
from django.forms import ModelForm
from django.core.files import File
from django.db import models
class MyPhoto(models.Model):
photo = models.ImageField(upload_to=photo_upload_to, max_length=1000)
class MyForm(ModelForm):
class Meta:
model = MyPhoto
fields = ['photo']
photo = MyPhoto.objects.first()
photo = File(open('1.jpeg', 'rb'))
form = MyForm(files={'photo': photo})
if form.is_valid():
print(form.instance.photo.file.content_type)
It doesn't rely on content type provided by the user. But
django.db.models.fields.files.FieldFile.file is an undocumented
property.
Actually, initially content_type is set from the request, but when
the form gets validated, the value is updated.
Regarding non-images, doing request.FILES['name'].read() seems okay to me.
First, that's what Django does. Second, files larger than 2.5 Mb by default
are stored on a disk. So let me point you at the other answer
here.
For the curious, here's the stack trace that leads to updating
content_type:
django.forms.forms.BaseForm.is_valid: self.errors
django.forms.forms.BaseForm.errors: self.full_clean()
django.forms.forms.BaseForm.full_clean: self._clean_fields()
django.forms.forms.BaseForm._clean_fiels: field.clean()
django.forms.fields.FileField.clean: super().clean()
django.forms.fields.Field.clean: self.to_python()
django.forms.fields.ImageField.to_python

How to use PIL in django models

I like to resize an uploaded image(ImageField) before finally storing, I heard that python has an image library called PIL and I would like to use it do that task but I'm not sure on how to start.
Any suggestions on how to do it?
Thanks
you can override model's save function where you can open file and resize it (not recommended, as it will resize it each time you save a model), you an resize after file upload (e.g. before/during form.save())
but IMHO a far better solution is to use a dedicated app for this, my favourite is sorl-thumbnails
I've just found out on how to do it but is there a way to simplify it? I'm new in python and django so I'm not sure if this is the proper way to do it.
below is my code:
from django.db.models.signals import pre_delete, pre_save
def on_save_image(sender, **kwargs):
import PIL
obj = kwargs['instance']
if obj.file:
try:
original = sender.objects.get(pk = obj.pk)
if original.file:
#if change image then delete original file
original.file.delete()
except ObjectDoesNotExist:
pass
finally:
img = PIL.Image.open(obj.file)
img.thumbnail((500, 500))
# reset pointer to start at 0 again
obj.file.open()
img.save(obj.file)
pre_save.connect(on_save_image, sender = Image)
# delete file in memory
def on_delete_image(sender, **kwargs):
obj = kwargs['instance']
if obj.file:
obj.file.delete()
pre_delete.connect(on_delete_image, sender = Image)
Thanks

django-photologue upload_to

I have been playing around with django-photologue for a while, and find this a great alternative to all other image handlings apps out there.
One thing though, I also use django-cumulus to push my uploads to my CDN instead of running it on my local machine / server.
When I used imagekit, I could always pass a upload_to='whatever' but I cannot seem to do this with photologue as it automatically inserts the imagefield. How would I go about achieving some sort of an overwrite?
Regards
I think you can hook into the pre_save signal of the Photo model, and change the upload_to field, just before the instance is saved to the database.
Take a look at this:
http://docs.djangoproject.com/en/dev/topics/signals/
Managed to find a workaround for it, however this requires you to make the changes in photologue/models.py
if PHOTOLOGUE_PATH is not None:
if callable(PHOTOLOGUE_PATH):
get_storage_path = PHOTOLOGUE_PATH
else:
parts = PHOTOLOGUE_PATH.split('.')
module_name = '.'.join(parts[:-1])
module = __import__(module_name)
get_storage_path = getattr(module, parts[-1])
else:
def get_storage_path(instance, filename):
dirname = 'photos'
if hasattr(instance, 'upload_to'):
if callable(instance.upload_to):
dirname = instance.upload_to()
else: dirname = instance.upload_to
return os.path.join(PHOTOLOGUE_DIR,
os.path.normpath( force_unicode(datetime.now().strftime(smart_str(dirname))) ),
filename)
And then in your apps models do the following:
class MyModel(ImageModel):
text = ...
name = ...
def upload_to(self):
return 'yourdirectorynamehere'
You can use the setting PHOTOLOGUE_PATH to provide your own callable. Define a method which takes instance and filename as parameters then return whatever you want. For example in your settings.py:
import photologue
...
def PHOTOLOGUE_PATH(instance, filename):
folder = 'myphotos' # Add your logic here
return os.path.join(photologue.models.PHOTOLOGUE_DIR, folder, filename)
Presumably (although I've not tested this) you could find out more about the Photo instance (e.g. what other instances it relates to) and choose the folder accordingly.