Override Djange ImageField extension validation - django

Understand I can add a validator to Django's ImageField validators to restrict file extension types like below. But in terms of the error messages which are displayed via upload on Admin -- I'm still seeing the standard file type list (via PIL allowed types), if I upload a non-image type. If I upload an image type which is not in my custom allowed_extensions below, I see my custom message. How can I override Django's default ImageField handling, and show my custom error message no matter what type of file is uploaded (e.g. when any file other than .png is uploaded per below example)?
class MM(models.Model):
file_extension_validator = FileExtensionValidator(
allowed_extensions=['png'],
message='File extension not allowed. Allowed extensions include .png'
)
image = models.ImageField(
help_text='Upload images only (.png).',
validators=[file_extension_validator],
max_length=255,
blank=False,
null=False
)

The problem is not the model field, but the form field. The form field has a default validator that lists all the extensions PIL supports.
You can make a special form field ModifiedImageField and specify that for the ModelForm that will be used by the MyModelAdmin in this case:
from django.contrib import admin
from django.core.validators import FileExtensionValidator
from django import forms
image_validator = FileExtensionValidator(
allowed_extensions=['png'],
message='File extension not allowed. Allowed extensions include .png'
)
class ModifiedImageField(forms.ImageField):
default_validators = [image_validator]
class MyModelAdminForm(forms.ModelForm):
imagefield = ModifiedImageField()
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
where imagefield is the name of the ImageField for which you want to replace the validator.

Related

Image Events on Froala Django Editor

I'm using the Froala Django Editor in some forms in my Django REST Framework backend, such as this
# resources/forms.py
from django import forms
from froala_editor.widgets import FroalaEditor
from .models import Resource
class ResourceForm(forms.ModelForm):
class Meta:
model = Resource
fields = '__all__'
widgets = {
'content': FroalaEditor(
options={
'heightMin': 256
}
)
}
When I try to upload an image (or a video, or any file, but one thing at a time) in the Froala editor I get an error:
In the console I have:
GET https://{some-id}.cloudfront.net/uploads/froala_editor/images/Nights.of.Cabiria.jpg [HTTP/2 403 Forbidden 15ms]
The error above made me wonder that perhaps the image is being uploaded correctly, but the Froala editor can't get it after uploading in order to display it.
The application is being hosted in AWS and the uploaded files stored in S3 buckets.
And in fact, I checked in the S3 dashboard, and the images are there, so they have uploaded correctly.
Even though I'm using all default FROALA_EDITOR_OPTIONS. I'm aware there are specific options for S3 storage (I've tried them) but I'm not using them and it is uploading fine.
Still looking at that error, I remembered that in other models in the project I have ImageFields, for example
# users/models.py
class User(AbstractUser):
name = models.CharField(_('First name'), db_index=True, max_length=255)
image = models.ImageField(upload_to=user_image_bucket, verbose_name=_('Image'))
def signed_image_url(self):
try:
return sign_cloudfront_url(self.image.url)
except ValueError:
return None
and that the serializers for these models always return the signed url, not the original url of the image
# users/serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id',
'name',
'signed_image_url',
)
I don't understand much about AWS S3, but I suppose that the images stored there are not publicly accessible, and providing a signed url to an image grants access to it.
Knowing this, I believe that what I need to do is to apply the same sign_cloudfront_url function to the images uploaded via the Froala Editor.
According to the Froala docs, you can set some events listeners on the Froala Editor options, such as image.uploaded, and I think this is where I should get the url of the uploaded image and return a signed url, but I'm not being able to set these events.
If I do this:
# resources/forms.py
from django import forms
from froala_editor.widgets import FroalaEditor
from .models import Resource
class ResourceForm(forms.ModelForm):
class Meta:
model = Resource
fields = '__all__'
def uploaded():
print("hello world")
widgets = {
'content': FroalaEditor(
options={
'heightMin': 256,
'events': {
'image.uploaded': uploaded,
}
}
)
}
I get a very expected Object of type function is not JSON serializable error.
Any idea how I should handle this?

Validate uploaded file format and show error if it's not intact in Django admin page

I have my own text data file format to my Django application.
After uploading one file to Django through admin page, how can I show error to admin uploader if file contents is not in proper format?
Is there common way to handle this situation?
You can use simple form validation, as everywhere in Django.
Pseudocode below
admin.py
class YourModelAdminForm(forms.ModelForm):
def clean_your_field(self):
if format_is_not_valid:
raise forms.ValidationError('Format is not valid')
class YourModelAdmin(admin.ModelAdmin):
form = YourModelAdminForm
admin.site.register(YourModel, YourModelAdmin)
or you can create your custom validator and use it on model field
models.py
class YourModel(models.Model):
your_field = models.FileField(validators=[your_validator])

How to upload multiple images in a Django field

I have checked many uploaders for django image field. But I am unable to get a simple clear way of doing multiple image uploads in Django.
My requirement is
Class Foo(models.Model):
images = SomeImageField(upload_to = "/path/")
This should allow me to upload multiple images. Now django-photologue allows Gallery upload, but this is only in zip format. I want something similar. Is there any such app available ?
django-filer will allow you to upload multiple images via a separate interface (not via a model field, but via the django admin), but you will only be able to select one of those uploaded image per image field. What you need to do is implement a django admin StackedInline or something similar
# models.py
from django.db import models
from filer.fields.imagefields import FilerImageField
class MyObject(models.Model):
name = models.CharField(...)
class Image(models.Model):
image_file = FilerImageField()
obj = models.ForeignKey(MyObject, ...)
# admin.py
from django.contrib import admin
from models import Image, MyObject
class ImageInline(admin.StackedInline):
model = Image
class MyObjectAdmin(admin.ModelAdmin):
inlines = [ImageInline, ]
...
now you will be able to easily attach multiple images to a single instance of your object via the admin.
I don't know of any apps that allow for a single field to manage multiple images.

Django Multiple File Field

Is there a model field that can handle multiple files or multiple images for django? Or is it better to make a ManyToManyField to a separate model containing Images or Files?
I need a solution complete with upload interface in django-admin.
For guys from 2017 and later, there is a special section in Django docs. My personal solution was this (successfully works in admin):
class ProductImageForm(forms.ModelForm):
# this will return only first saved image on save()
image = forms.ImageField(widget=forms.FileInput(attrs={'multiple': True}), required=True)
class Meta:
model = ProductImage
fields = ['image', 'position']
def save(self, *args, **kwargs):
# multiple file upload
# NB: does not respect 'commit' kwarg
file_list = natsorted(self.files.getlist('{}-image'.format(self.prefix)), key=lambda file: file.name)
self.instance.image = file_list[0]
for file in file_list[1:]:
ProductImage.objects.create(
product=self.cleaned_data['product'],
image=file,
position=self.cleaned_data['position'],
)
return super().save(*args, **kwargs)
No there isn't a single field that knows how to store multiple images shipped with Django. Uploaded files are stored as file path strings in the model, so it's essentially a CharField that knows how to be converted to python.
The typical multiple image relationship is built as a separate Image model with an FK pointing to its relevant model, such as ProductImage -> Product.
This setup makes it very easy to add into the django admin as an Inline.
An M2M field would make sense if you it's truly a many to many relationship where say GalleryImages are referenced from 1 or more Gallery objects.
I had to change from having a single file to multiple files in an existing system and after a bit of research ended up using this: https://github.com/bartTC/django-attachments
It should be easy to subclass the model if you want custom methods.
FilerFileField and FilerImageField in one model:
They are subclasses of django.db.models.ForeignKey, so the same rules apply. The only difference is, that there is no need to declare what model we are referencing (it is always filer.models.File for the FilerFileField and filer.models.Image for the FilerImageField).
Simple example models.py:
from django.db import models
from filer.fields.image import FilerImageField
from filer.fields.file import FilerFileField
class Company(models.Model):
name = models.CharField(max_length=255)
logo = FilerImageField(null=True, blank=True)
disclaimer = FilerFileField(null=True, blank=True)
Multiple image file fields on the same model in models.py:
Note: related_name attribute required, it is just like defining a foreign key relationship.
from django.db import models
from filer.fields.image import FilerImageField
class Book(models.Model):
title = models.CharField(max_length=255)
cover = FilerImageField(related_name="book_covers")
back = FilerImageField(related_name="book_backs")
This answer code taken from django-filer document

Extend flatpages for a specific application

I have a portfolio application that lists projects and their detail pages. Every project generally has the same information (gallery, about etc. ), but sometimes the user might want to add extra information for a particularly large project, maybe an extra page about the funding of that page for example.
Would it be possible to create an overwritten flatpages model within my portfolio app that forces any flatpages created within that application to always begin with /portfolio/project-name/flat-page. I could then simply pass the links to those flatpages associated with the project to the template so any flatpage the user generates will automatically be linked to from the project page.
EDIT
I have it somewhat working now
So I overwrite the FlatPage model in my portfolio app as described:
from django.contrib.flatpages.models import FlatPage
from project import Project
from django.db import models
from django.contrib.sites.models import Site
class ProjectFlatPage(FlatPage):
prefix = models.CharField(max_length=100)
project = models.ForeignKey(Project)
which allows me to associate this flatpage with a particular project,
Then I overwrite the save method to write all the extra information when a user saves (needs to be tidied up):
def save(self, force_insert=False, force_update=False):
self.url = u"%s%s/" % (self.project.get_absolute_url(),self.prefix)
self.enable_comments = False
self.registration_required = False
self.template_name = 'project/project_flatpage.html'
super(FlatPage, self).save(force_insert, force_update)
and I scaleback the admin to just allow the important stuff:
class ProjectFlatPageForm(forms.ModelForm):
prefix = forms.RegexField(label=_("Prefix"), max_length=100, regex=r'^[a-z0-9-]+$'),
class Meta:
model = ProjectFlatPage
class ProjectnFlatPageAdmin(admin.ModelAdmin):
form = ProjectFlatPageForm
so now the user can add a flat page inside my app and associate it with a particular project.
In the admin, they just enter a slug for the page and it automatically gets appended through the save() method like: /projects/project-name/flat-page-name/
The remaining problem is on the template end. I can access the normal flatpage information through the given template tags {{ flatpage.title }} and {{ flatpage.content }} put I have no access to the extra fields of the inherited model (i.e. the project field)
Is there anyway around this?
EDIT2
Having thought about it, the easiest way is to write a template tag to find the projectFlatPage associated with the flatpage and get access to it from there. A bit like overwritting the default flatpages template tags
re the 'Edit 2' part of your question... since your custom flatpage model inherits from FlatPage there is an automatic one-to-one field relation created between them by Django.
So in your template, if you know that the flatpage object passed in is one of your customised ones you can access your extra attributes by:
{{ flatpage.projectflatpage.project }}
Of course. Just write your own flatpage-model, for example:
from django.contrib.flatpages.models import FlatPage
class MyFlatPage(FlatPage):
prefix = models.CharField(max_length=100, editable=False)
url = models.CharField(_('URL'), max_length=100, db_index=True, editable=False)
Then add a proper MyFlatPageAdmin to your admin.py file (if you want to, you can import the flatpageadmin from django.contrib.flatpages.admin and inherit from it). After all, you're using the flatpage-model, but overwrite the urls-field. Now add a signal, which concats the prefix and a automatically generated url suffix to the url (like the object id). You can now add the custom flatpage like:
flatpage = MyFlatPage(prefix='/portfolio/my-super-project/')
Everything clear now?
edit 1
As you can read in the documentation, every flatpage should be a projectflatpage and vice versa.