python - form - Cargas de Django: descarte los duplicados cargados, use el archivo existente(verificación basada en md5)
upload image django example (4)
AFAIK, no puede implementar esto fácilmente utilizando los métodos de guardar / eliminar porque los archivos se manejan de manera bastante específica.
Pero podrías intentar algo así.
Primero, mi simple función hash de archivos md5:
def md5_for_file(chunks):
md5 = hashlib.md5()
for data in chunks:
md5.update(data)
return md5.hexdigest()
La siguiente simple_upload_to
is es algo así como la función media_file_name de tuyo. Deberías usarlo así:
def simple_upload_to(field_name, path=''files''):
def upload_to(instance, filename):
name = md5_for_file(getattr(instance, field_name).chunks())
dot_pos = filename.rfind(''.'')
ext = filename[dot_pos:][:10].lower() if dot_pos > -1 else ''.unknown''
name += ext
return os.path.join(path, name[:2], name)
return upload_to
class Media(models.Model):
# see info about storage below
orig_file = models.FileField(upload_to=simple_upload_to(''orig_file''), storage=MyCustomStorage())
Por supuesto, es solo un ejemplo, por lo que la lógica de generación de rutas podría ser diferente.
Y la parte más importante:
from django.core.files.storage import FileSystemStorage
class MyCustomStorage(FileSystemStorage):
def get_available_name(self, name):
return name
def _save(self, name, content):
if self.exists(name):
self.delete(name)
return super(MyCustomStorage, self)._save(name, content)
Como puede ver, este almacenamiento personalizado elimina el archivo antes de guardar y luego guarda uno nuevo con el mismo nombre. Por lo tanto, aquí puede implementar su lógica si NO eliminar (y, por lo tanto, actualizar) los archivos es importante.
Puede encontrar más información sobre los almacenes aquí: https://docs.djangoproject.com/en/1.5/ref/files/storage/
Tengo un modelo con un FileField
, que contiene los archivos cargados por el usuario. Como quiero ahorrar espacio, me gustaría evitar duplicados.
Lo que me gustaría lograr:
- Calcula los archivos subidos md5 checksum
- Almacene el archivo con el nombre del archivo basado en su md5sum
- Si ya existe un archivo con ese nombre (el nuevo archivo es un duplicado ), descarte el archivo cargado y use el archivo existente en su lugar.
1 y 2 ya están funcionando, pero ¿cómo podría olvidarme de un duplicado cargado y utilizar el archivo existente en su lugar?
Tenga en cuenta que me gustaría mantener el archivo existente y no sobrescribirlo (principalmente para mantener el tiempo modificado igual - mejor para la copia de seguridad).
Notas:
- Estoy usando Django 1.5
- El controlador de carga es
django.core.files.uploadhandler.TemporaryFileUploadHandler
Código:
def media_file_name(instance, filename):
h = instance.md5sum
basename, ext = os.path.splitext(filename)
return os.path.join(''mediafiles'', h[0:1], h[1:2], h + ext.lower())
class Media(models.Model):
orig_file = models.FileField(upload_to=media_file_name)
md5sum = models.CharField(max_length=36)
...
def save(self, *args, **kwargs):
if not self.pk: # file is new
md5 = hashlib.md5()
for chunk in self.orig_file.chunks():
md5.update(chunk)
self.md5sum = md5.hexdigest()
super(Media, self).save(*args, **kwargs)
Cualquier ayuda es apreciada!
Esta respuesta me ayudó a resolver el problema en el que quería provocar una excepción si el archivo que se estaba cargando ya existía. Esta versión genera una excepción si ya existe un archivo con el mismo nombre en la ubicación de carga.
from django.core.files.storage import FileSystemStorage
class FailOnDuplicateFileSystemStorage(FileSystemStorage):
def get_available_name(self, name):
return name
def _save(self, name, content):
if self.exists(name):
raise ValidationError(''File already exists: %s'' % name)
return super(
FailOnDuplicateFileSystemStorage, self)._save(name, content)
Gracias a alTus answer, pude averiguar que escribir una clase de almacenamiento personalizada es la clave, y fue más fácil de lo esperado.
- Simplemente
_save
llamar a las superclases_save
método para escribir el archivo si ya está allí y simplemente devuelvo el nombre. -
get_available_name
para evitar que seget_available_name
números al nombre del archivo si ya existe un archivo con el mismo nombre
No sé si esta es la forma correcta de hacerlo, pero hasta ahora funciona bien.
Espero que esto sea útil!
Aquí está el código de ejemplo completo:
import hashlib
import os
from django.core.files.storage import FileSystemStorage
from django.db import models
class MediaFileSystemStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
if max_length and len(name) > max_length:
raise(Exception("name''s length is greater than max_length"))
return name
def _save(self, name, content):
if self.exists(name):
# if the file exists, do not call the superclasses _save method
return name
# if the file is new, DO call it
return super(MediaFileSystemStorage, self)._save(name, content)
def media_file_name(instance, filename):
h = instance.md5sum
basename, ext = os.path.splitext(filename)
return os.path.join(''mediafiles'', h[0:1], h[1:2], h + ext.lower())
class Media(models.Model):
# use the custom storage class fo the FileField
orig_file = models.FileField(
upload_to=media_file_name, storage=MediaFileSystemStorage())
md5sum = models.CharField(max_length=36)
# ...
def save(self, *args, **kwargs):
if not self.pk: # file is new
md5 = hashlib.md5()
for chunk in self.orig_file.chunks():
md5.update(chunk)
self.md5sum = md5.hexdigest()
super(Media, self).save(*args, **kwargs)
Tuve el mismo problema y encontré esta pregunta SO. Como esto no es nada raro, busqué en la web y encontré el siguiente paquete de Python, que parece hacer exactamente lo que quieres:
https://pypi.python.org/pypi/django-hashedfilenamestorage
Si los hashes SHA1 están fuera de cuestión, creo que una solicitud de extracción para agregar el soporte de hashing MD5 sería una gran idea.