16π
I have never used SVGAndImageFormField
so I cannot really comment on that. Personally I would have opted for a simple application of FileField
, but that clearly depends on the project requirements. I will expand on that below:
As mentioned in the comment, the basic difference between an ImageField and a FileField is that the first checks if a file is an image using Pillow:
Inherits all attributes and methods from FileField, but also validates that the uploaded object is a valid image.
Reference: Django docs, Django source code
It also offers a couple of attributes possibly irrelevant to the SVG case (height, width).
Therefore, the model field could be:
svg = models.FileField(upload_to=..., validators=[validate_svg])
You can use a function like is_svg
as provided in the relevant question:
How can I say a file is SVG without using a magic number?
Then a function to validate SVG:
def validate_svg(file, valid):
if not is_svg(file):
raise ValidationError("File not svg")
7π
It turns out that SVGAndImageFormField
has no dependencies on DRFβs ImageField
, it only adds to the validation done by django.forms.ImageField
.
So to accept SVGs in the Django Admin I changed the modelβs specified an override as follows:ImageField
to a FileField
and
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = []
field_classes = {
'image_field': SVGAndImageFormField,
}
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
admin.site.register(MyModel, MyModelAdmin)
It now accepts all previous image formats along with SVG.
EDIT: Just found out that this works even if you donβt switch from models.ImageField
to models.FileField
. The height
and width
attributes of models.ImageField
will still work for raster image types, and will be set to None
for SVG.
- [Django]-OSError: [Errno 18] Invalid cross-device link
- [Django]-Django rest framework serializing many to many field
- [Django]-Django: When to customize save vs using post-save signal
4π
Here is a solution that works as a simple model field, that you can put instead of models.ImageField
:
class Icon(models.Model):
image_file = SVGAndImageField()
You need to define following classes and functions somewhere in your code:
from django.db import models
class SVGAndImageField(models.ImageField):
def formfield(self, **kwargs):
defaults = {'form_class': SVGAndImageFieldForm}
defaults.update(kwargs)
return super().formfield(**defaults)
And here is how SVGAndImageFieldForm
looks like:
from django import forms
from django.core.exceptions import ValidationError
class SVGAndImageFieldForm(forms.ImageField):
def to_python(self, data):
try:
f = super().to_python(data)
except ValidationError:
return validate_svg(data)
return f
Function validate_svg
I took from other solutions:
import xml.etree.cElementTree as et
def validate_svg(f):
# Find "start" word in file and get "tag" from there
f.seek(0)
tag = None
try:
for event, el in et.iterparse(f, ('start',)):
tag = el.tag
break
except et.ParseError:
pass
# Check that this "tag" is correct
if tag != '{http://www.w3.org/2000/svg}svg':
raise ValidationError('Uploaded file is not an image or SVG file.')
# Do not forget to "reset" file
f.seek(0)
return f
Also if you want to use SVG files only model field β you can do it more simple.
Just create class, inherited from models.FileField
, and in __init__
method you can add validate_svg
function to kwargs['validators']
.
Or just add this validator to models.FileField
and be happy π
- [Django]-Per-transaction isolation level in Django ORM
- [Django]-GeoDjango GEOSException error
- [Django]-Install mysqlclient for Django Python on Mac OS X Sierra
2π
As stated in the comments, validation for SVGAndImageFormField
will fail because extensions are checked using django.core.validators.validate_image_file_extension
, which is the default validator for an ImageField
.
A workaround for this would be creating a custom validator adding "svg"
to the accepted extensions.
Edited: Thanks @Ilya Semenov for your comment
from django.core.validators import (
get_available_image_extensions,
FileExtensionValidator,
)
def validate_image_and_svg_file_extension(value):
allowed_extensions = get_available_image_extensions() + ["svg"]
return FileExtensionValidator(allowed_extensions=allowed_extensions)(value)
Then, override the default_validators
attribute in the SvgAndImageFormField
:
class SVGAndImageFormField(DjangoImageField):
default_validators = [validate_image_and_svg_file_extension]
# ...
- [Django]-Django β Create user profile on user creation
- [Django]-Django setting : psycopg2.OperationalError: FATAL: Peer authentication failed for user "indivo"
- [Django]-Accessing Primary Key from URL in Django View Class
1π
This is the solution that works for me with Django4.2:
Also I make use of defusedxml here as suggested per Python docs:
Warning
The XML modules are not secure against erroneous or maliciously constructed data. If you need to parse untrusted or unauthenticated data see the XML vulnerabilities and The defusedxml Package sections.
# form_fields.py
import defusedxml.cElementTree as et
from django.core import validators
from django.core.exceptions import ValidationError
from django.forms import ImageField
def validate_image_file_extension(value):
return validators.FileExtensionValidator(
allowed_extensions=validators.get_available_image_extensions()+['svg']
)(value)
class ImageAndSvgField(ImageField):
default_validators = [validate_image_file_extension]
def to_python(self, data):
try:
f = super().to_python(data)
except ValidationError as e:
if e.code != 'invalid_image':
raise
# Give it a chance - maybe its SVG!
f = data
if not self.is_svg(f):
# Nope it is not.
raise
f.content_type = 'image/svg+xml'
if hasattr(f, "seek") and callable(f.seek):
f.seek(0)
return f
def is_svg(self, f):
if hasattr(f, "seek") and callable(f.seek):
f.seek(0)
try:
doc = et.parse(f)
root = doc.getroot()
return root.tag == '{http://www.w3.org/2000/svg}svg'
except et.ParseError:
return False
# model_fields.py
from django.db.models.fields.files import ImageField
from . import form_fields
class ImageAndSvgField(ImageField):
def formfield(self, **kwargs):
return super().formfield(
**{
"form_class": form_fields.ImageAndSvgField,
**kwargs,
}
)
# modesl.py
from django.db import models
from .model_fields import ImageAndSvgField
class MyModel(models.Model):
...
image = ImageAndSvgField(upload_to='mymodel_images/', blank=True)
...
- [Django]-How to serve media files on Django production environment?
- [Django]-Is get_or_create() thread safe
- [Django]-Django 1.4 Many-to-Many bulk add
-1π
from django.forms import ModelForm, FileField
class TemplatesModelForm(ModelForm):
class Meta:
model = Templates
exclude = []
field_classes = {
'image': FileField,
}
@admin.register(Templates)
class TemplatesAdmin(admin.ModelAdmin):
form = TemplatesModelForm
its work
- [Django]-Django Rest Framework Token Authentication
- [Django]-Upgrading from Django 1.6 (with south) to 1.8 doesn't modify 'last_login' on the user table
- [Django]-How do you skip a unit test in Django?