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."
)
Related
I have a PDF model in my Django project. I want to save these files to my C: directory instead of my project directory. ( Save to C:\otc )
I don't have to file it as in the model (customer_customer_name), I just want to save it in a file named otc in C:
And How can I add a timestamp to the uploaded PDF file's name?
models.py
def customer_directory_path(self, filename):
return 'customer_{0}/{1}'.format(self.owner.customer_name, filename)
class Pdf(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=200)
pdf = models.FileField(upload_to=customer_directory_path)
type = models.CharField(max_length=200, default='Select', choices=CHOICES)
year = models.CharField(max_length=200, default='Select')
created_date = models.DateTimeField(default=datetime.now())
If you use FileSystemStorage as default file storage then upload_to is appended to MEDIA_ROOT. You need to change MEDIA_ROOT to C: if you want all upload be there or create new Storage for that field. More in doc.
created_date field should probably looks like this:
created_date = models.DateTimeField(default=datetime.now)
otherwise all rows will have same value.
then you can try:
def customer_directory_path(self, filename):
filename = "{}_filename".format(self.created_date)
return 'customer_{0}/{1}'.format(self.owner.customer_name, filename)
BTW: not sure where you got owner in that function.
EDIT:
you should be able to use also this approach if you don't want to create new Storage.
pdf = models.FileField(upload_to=customer_directory_path, storage=FileSystemStorage(location="C:"))
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')
How do I save an Image in a specific folder, depending of whom is Loggedin?
My program creates a folder for every user that registers
after they login, they are able to upload some images.
The problem is when I try to save my image, I need to specify the path where is going to be Uploaded (with the userID)
Example:
If Bob is Loggedin, you will upload the images here: space/users/bob123/example.png
Model
class StudentPhotos(models.Model):
image = models.ImageField(upload_to= ??? , default="", null=True)
image_id = models.CharField(max_length = 15, null=False, editable=False, default=id_generator, primary_key=True)
user_id = models.CharField(max_length = 15, null=False, default="")
The main reason to save it that way is to be able to bring in my admin site all images from an user
Lets say you have your model like as follows(Demonstration only):
class StudentPhotos(models.Model):
image = models.ImageField(upload_to=name)
user = models.ForeignKey(User)
Here name (upload_to=name) can be function as follows:
def name(instance, fname):
return '/'.join(instance.user.get_full_name(), fname)
I personally make use of easy-thumbnails for the purpose of having image fields in my models and I am able to use name function in the way I have given above.
Hope, you can infer some relative information and be able to come up with a better solution for your needs.
my model.py as follows,
class Employee(models.Model):
id = models.IntegerField(max_length = 6,primary_key=True,verbose_name="Employee ID")
name = models.CharField(max_length = 100,verbose_name="Name")
image = models.ImageField(upload_to = ".",blank=True,null=True)
When i try to add image in django admin, it uploads the image to /site_media/media. Here i want to replace the uploaded image name with my primary key field ID.jpeg, so that i can retrieve and show that in html pages .
Even now i can retrieve the image name because its stored in db. However for simplifying i need to customize the image name before upload. Also if the user uploads another image for same person it should overwrite the old one. At present it just changes the new name in db. But still old image is present in the media folder
You can use a callable for upload_to as follows:
def file(self, filename):
url = "./%d.JPG" % (self.id,)
return url
class Employee(models.Model):
id = models.IntegerField(max_length = 6,primary_key=True,verbose_name="Employee ID")
name = models.CharField(max_length = 100,verbose_name="Name")
image = models.ImageField(upload_to = file,blank=True,null=True)
But you will face the problem of Django not overwriting the file which can be solved by a custom storage or deleting the file.
You can use a function for upload_to;
def sample_upload_to_function(instance, filename):
extension = filename.split('.')[-1]
return "%s.%s" %(instance.id, extension)
class Employee(models.Model):
.....
image = models.ImageField(upload_to =sample_upload_to_function, blank=True, null=True)
But i don't know how to overwrite existing file. I think before save, you must delete the existing file.
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.