I have a Post model with a filefield which is used to upload files. How can I validate the file type (pdf for now, or any other types if I change to later). Preferably i'd like to validate the content, but if not I guess suffix would do too. I tried to look up online but most of the solutions I found are from way back and as the Django document get updated they don't work any more. Please if anyone can help. Thanks.
class Post(models.Model):
author = models.ForeignKey('auth.User',default='')
title = models.CharField(max_length=200)
text = models.TextField()
PDF = models.FileField(null=True, blank=True)
created_date = models.DateTimeField(
default=timezone.now)
published_date = models.DateTimeField(
blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
With Django 1.11 you can use FileExtensionValidator. With earlier versions, or for extra validation, you can build your own validator based on it. And you should probably create a validator either way because of this warning:
Don’t rely on validation of the file extension to determine a file’s type. Files can be renamed to have any extension no matter what data they contain.
Here's a sample code with the existing validator:
from django.core.validators import FileExtensionValidator
class Post(models.Model):
PDF = models.FileField(null=True, blank=True, validators=[FileExtensionValidator(['pdf'])])
Source code is also available so you can easily create your own:
https://docs.djangoproject.com/en/1.11/_modules/django/core/validators/#FileExtensionValidator
Think of validation in terms of:
Name/extension
Metadata (content type, size)
Actual content (is it really a PNG as the content-type says, or is it a malicious PDF?)
The first two are mostly cosmetic - pretty easy to spoof/fake that information. By adding content validation (via file magic - https://pypi.python.org/pypi/filemagic) you add a little bit of additional protection
Here is a good, related answer: Django: Validate file type of uploaded file It may be old, but the core idea should be easily adapted.
Firstly, I'd advise you change 'PDF' to 'pdf', then
to validate in older versions of Django, you could do this
forms.py
class PostForm(forms.ModelForm):
# fields here
class Meta:
model = Post
fields = ["title", "text", "pdf"]
def clean(self):
cd = self.cleaned_data
pdf = cd.get('pdf', None)
if pdf is not None:
main, sub = pdf.content_type.split('/')
# main here would most likely be application, as pdf mime type is application/pdf,
# but I'd like to be on a safer side should in case it returns octet-stream/pdf
if not (main in ["application", "octet-stream"] and sub == "pdf"):
raise forms.ValidationError(u'Please use a PDF file')
return cd
Here is a simple example for a form with file type validation based on Django 1.11 FileExtensionValidator
class ImageForm(ModelForm):
ALLOWED_TYPES = ['jpg', 'jpeg', 'png', 'gif']
class Meta:
model = Profile
fields = ['image', ]
def clean_avatar(self):
image = self.cleaned_data.get('image', None)
if not avatar:
raise forms.ValidationError('Missing image file')
try:
extension = os.path.splitext(image.name)[1][1:].lower()
if extension in self.ALLOWED_TYPES:
return avatar
else:
raise forms.ValidationError('File types is not allowed')
except Exception as e:
raise forms.ValidationError('Can not identify file type')
Related
I am working on a project where I want to create a slug for each post based on its title. Is it possible to generate a slug in such a way that it will be unique to the post, but will not change even if the title of the post is changed? I am using the model provided in the file 'model.py'. Can you provide guidance on how to accomplish this?
class Post(models.Model):
username = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
description = models.CharField(('Description'),max_length=250)
title = models.CharField(('Content Title'), max_length=250)
create_date = models.DateTimeField(default = timezone.now)
image_data = models.ImageField(upload_to='User_Posts', height_field=None, width_field=None, max_length=None)
slug = (title)
def __str__(self):
return self.title
I recommend checking out the Django documentation for slugify. You will need to override the save method of your model to do this, so your new code will most likely look something like this:
from django.utils.text import slugify
slug=models.SlugField()
def save(self,*args,**kwargs):
self.slug=slugify(self.title)
super(Post,self).save(*args,**kwargs)
I would keep in mind the unique parameter that you can set to either true or false in your slugfield.
I am trying to validate upload file type functionality in Django. The allowed extension would be xml only. The admin will upload a xml file and then the table would be populated with the data from xml file. The model has no filefield but the form has.
accounts/models.py --
class Coupling(models.Model):
coupling_name = models.CharField(max_length=150, blank=False, null=True, default="")
module_name = models.TextField(blank=False, null=True)
def __str__(self):
return self.coupling_name
class Meta:
verbose_name_plural = "Couplings"
accounts/forms.py --
class CouplingUploadForm(forms.ModelForm):
coupling_file = forms.FileField(label='XML File Upload:', required=True)
class Meta:
model = models.Coupling
exclude = ['coupling_name', 'module_name']
settings.py
UPLOAD_PATH = os.path.join(BASE_DIR, "static", "uploads")
CONTENT_TYPES = ['xml']
MAX_UPLOAD_SIZE = "2621440"
accounts/admin.py
class couplingAdmin(admin.ModelAdmin):
list_display = ('coupling_name','module_name')
form = CouplingUploadForm
admin.site.register(Coupling, couplingAdmin)
I have gone through some SOF references and most of them have model.FileField but in my case I do not want to save the file in model.
I tried with using magic -- https://djangosnippets.org/snippets/3039/ but I got an python-magic installation error -- Unable to find libmagic. So I would like to do it without magic.
Any help/suggestion/link is highly appreciated. Thanks in advance.
You can create a custom validator
def validate_file_extension(value):
import os
from django.core.exceptions import ValidationError
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.xml']
if not ext.lower() in valid_extensions:
raise ValidationError(u'Unsupported file extension.')
Then in your form field
coupling_file = forms.FileField(label='XML File Upload:',
required=True, validators=[validate_file_extension])
Simply write an clean method to your forms.py
import os
def clean_coupling_file(self):
file = self.cleaned_data['coupling_file']
extension = os.path.splitext(file.name)[1] # [0] returns path+filename
VALID_EXTENSION = '.xml'
if extension != VALID_EXTENSION:
self.add_error(
'coupling_file',
_('Only files with ".xml" extension are supported, '
'received: "%s" file.' % extension)
)
return file
I just installed this django-csvimport package. Now I want to override the default values in the Admin area form. I found the code here, which defines the models, and contains the current default text:
class CSVImport(models.Model):
""" Logging model for importing files """
model_choice = []
model_name = models.CharField(max_length=255, blank=False,
default='csvimport.Item',
help_text='Please specify the app_label.model_name',
choices=get_models())
field_list = models.TextField(blank=True,
help_text='''Enter list of fields in order only if
you dont have a header row with matching field names, eg.
"column1=shared_code,column2=org(Organisation|name)"''')
upload_file = models.FileField(upload_to='csv', storage=fs)
file_name = models.CharField(max_length=255, blank=True)
encoding = models.CharField(max_length=32, blank=True)
upload_method = models.CharField(blank=False, max_length=50,
default='manual', choices=CHOICES)
error_log = models.TextField(help_text='Each line is an import error')
import_date = models.DateField(auto_now=True)
import_user = models.CharField(max_length=255, default='anonymous',
help_text='User id as text', blank=True)
def error_log_html(self):
return re.sub('\n', '<br/>', self.error_log)
error_log_html.allow_tags = True
def __unicode__(self):
return self.upload_file.name
So for example I would like override the model_name field default csvimport.Item with something else. I am a bit at a loss how to override this as I do not have an app folder for csvimport, as its a 3rd part installation. It will be my first time overriding a 3rd party installed app model.
Now that I look into it a bit more, not sure if I should override this model or perhaps better the ModelAdmin class of the admin.py file?
Thanks!
"""Your admin.py"""
from csvimport.models import CSVImport
from csvimport.admin import CSVImportAdmin
class MyCSVImportAdmin(CSVImportAdmin):
"""Override some of the form's field properties:
clean, creation_counter, default_error_messages,
default_validators, disabled, empty_value, empty_values .. etc
"""
def get_form(self, request, obj=None, **kwargs):
form = super(MyCSVImportAdmin, self).get_form(request, obj, **kwargs)
form.base_fields["model_name"].initial = 'What you want'
form.base_fields["model_name"].help_text = 'Please customize the fields however you like'
return form
admin.site.unregister(CSVImport)
admin.site.register(CSVImport, MyCSVImportAdmin)
I saw the whole code and django-csvimport package does not provide you the functionality to override anything from their code so its not possible to override without copying app to your project. Below is an example of another app django-oauth-toolkit which uses a user settings param to provide the functionality of modifications.
USER_SETTINGS = getattr(settings, "OAUTH2_PROVIDER", None)
Now the solution would be only to copy the app and then modify the app for your own usage.
In my current project I have my images stored on a s3 bucket.
I have a pre_save signal receiver to delete the actually image from the s3 bucket on the Image class.
class Image(models.Model):
name = models.CharField(max_length = 255)
caption = models.CharField(max_length = 255)
image = models.ImageField(upload_to='uploads/',blank=True,null=True)
rent_property = models.ForeignKey(RentProperty, related_name='Images')
is_main_image = models.BooleanField(default=False)
#receiver(models.signals.pre_save, sender=Image)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""Deletes file from filesystem
when corresponding `MediaFile` object is changed.
"""
if not instance.pk:
return False
try:
old_file = Image.objects.get(pk=instance.pk).image
except Image.DoesNotExist:
return False
new_file = instance.image
if not old_file == new_file:
old_file.delete(save=False)
My problem is, I am using django-rest-framework, and I want to get the PATCH to work. but if I try to patch an Image description for example, it would delete the image itself. My question is, how do I write an IF that would differentiate weather or not there is a new image in the patch that needs changing , and if not, do nothing?
For models with an ImageField or FileField, I include an additional field to store the SHA-1 hash string. I've found this useful to have for many reasons:
Reducing unneeded transfers for the same file for updates (your case)
Preventing users from uploading duplicate files as new instances
Providing users with an SHA-1 hash when downloading files so they can verify the download
Doing data integrity checks on the back-end file system to verify the files have not changed
I also save the original file name in order to reproduce it for user facing views/downloads. In this way the back-end names do not matter to the users.
Here's a basic implementation based on your model:
import hashlib
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import models
class Image(models.Model):
name = models.CharField(max_length = 255)
caption = models.CharField(max_length = 255)
image = models.ImageField(upload_to='uploads/',blank=True,null=True)
original_filename = models.CharField(
unique=False,
null=False,
blank=False,
editable=False,
max_length=256)
sha1 = models.CharField(
unique=True,
null=False,
blank=False,
editable=False,
max_length=40)
rent_property = models.ForeignKey(RentProperty, related_name='Images')
is_main_image = models.BooleanField(default=False)
def clean(self):
"""
Overriding clean to do the following:
- Save original file name, since it may already exist on our side.
- Save SHA-1 hash and check for duplicate files.
"""
self.original_filename = self.image.name.split('/')[-1]
# get the hash
file_hash = hashlib.sha1(self.image.read())
self.sha1 = file_hash.hexdigest()
# Check if this file has already been uploaded,
# if so delete the temp file and raise ValidationError
duplicate_hashes = Image.objects.all().exclude(
id=self.id).values_list('sha1', flat=True)
if self.sha1 in duplicate_hashes:
if hasattr(self.image.file, 'temporary_file_path'):
temp_file_path = self.image.file.temporary_file_path()
os.unlink(temp_file_path)
raise ValidationError(
"This image already exists."
)
I have different categories like English,French and I've applied key language = models.ForeignKey(Category) I want to validate the file before it upload to disk.
I want if category is english then file upload to english/album_name if category is french then file upload to french/album_name. I've written forms.py file. But no idea. Thanks in advance
Models.py
class Artist(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique = True,max_length=100,help_text="Suggested value automatically generated from name. Must be unique.")
class Album(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique = True,max_length=100,help_text="Suggested value automatically generated from name. Must be unique.")
path = models.CharField(max_length=100,null=True, blank=True)
language = models.ForeignKey(Category)
albumid = models.CharField(max_length=100)
class Song(models.Model):
title = models.CharField(max_length=100)
artist = models.ManyToManyField(Artist)
music = models.ForeignKey(Music)
album = models.ForeignKey(Album)
file = models.FileField(upload_to='media/mp3_files')
forms.py
from django import forms
from db.song.models import Song
class SongAdminForm(forms.ModelForm):
class Meta:
model = Song
# No idea what to do next :-?
def clean_file(self):
file = self.cleaned_data["file"]
if file:
if file._size > 10*1024*1024:
raise ValidationError("Audio file too large ( > 10mb )")
if not file.content-type in ["audio/mpeg","audio/..."]:
raise ValidationError("Content-Type is not mpeg")
I think what you really want is to validate it after upload, but before saving it really.
When files are uploaded, they are in a temporary folder, so you can do all checks you want before saving it to the appropriate folder.
However, if you really mean pre-upload checks, you have to use Javascript (because it is client-side). But, don't forget that you should "never trust user input", and that includes what Javascript does, because user can change the Javascript code.