Django uploads: ignorer les doublons téléchargés, utiliser le fichier existant (vérification basée sur md5)
J'ai un modèle avec un FileField
, qui contient les fichiers téléchargés par l'utilisateur. Puisque je veux économiser de l'espace, je voudrais éviter les doublons.
Ce que je voudrais réaliser:
- Calculer les fichiers téléchargés somme de contrôle md5
- Stockez le fichier avec le nom de fichier basé sur son md5sum
- Si un fichier portant ce nom existe déjà (le nouveau fichier est un double), jetez le fichier téléchargé et utiliser le fichier existant au lieu de cela
1 et 2 fonctionne déjà, mais Comment pourrais-je oublier un doublon téléchargé et utiliser le fichier existant à la place?
Notez que je voudrais garder le fichier existant et pas l'écraser (principalement pour garder le temps modifié le même-mieux pour la sauvegarde).
Notes:
- J'utilise Django 1.5
- le gestionnaire de téléchargement est
django.core.files.uploadhandler.TemporaryFileUploadHandler
Code:
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)
Toute aide est appréciée!
4 réponses
Grâce à la réponse d'alTus, j'ai pu comprendre que l'écriture d'un classe de stockage personnalisée est la clé, et il était plus facile que prévu.
- j'Omets simplement d'appeler la méthode superclasses
_save
pour écrire le fichier s'il est déjà là et je retourne juste le nom. - j'écrase
get_available_name
, pour éviter d'ajouter des numéros au nom du fichier si un fichier portant le même nom existe déjà
Je ne sais pas si c'est le bon chemin de le faire, mais cela fonctionne très bien jusqu'à présent.
J'espère que c'est utile!
Voici l'exemple complet de code:
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)
AFAIK vous ne pouvez pas facilement implémenter cela en utilisant les méthodes de sauvegarde/suppression.
Mais vous pourriez essayer smth comme ça.
Tout d'abord, ma Simple Fonction de hachage de fichier md5:
def md5_for_file(chunks):
md5 = hashlib.md5()
for data in chunks:
md5.update(data)
return md5.hexdigest()
Suivant {[3] } est smth comme le vôtre media_file_name fonction. Vous devriez l'utiliser comme ça:
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())
Bien sûr, ce n'est qu'un exemple, donc la logique de génération de chemin pourrait être différente.
Et la partie la plus 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)
Comme vous pouvez le voir, cette coutume stockage supprime le fichier avant d'enregistrer, puis enregistre un nouveau avec le même nom. Donc, ici, vous pouvez implémenter votre logique si la suppression (et donc la mise à jour) des fichiers n'est pas importante.
Plus d'informations sur les stockages que vous pouvez trouver ici: https://docs.djangoproject.com/en/1.5/ref/files/storage/
J'ai eu le même problème et j'ai trouvé cette question SO. Comme ce n'est rien de trop rare, j'ai cherché sur le web et trouvé le paquet Python suivant qui coutures pour faire exactement ce que vous voulez:
Https://pypi.python.org/pypi/django-hashedfilenamestorage
Si les hachages SHA1 sont hors de question, je pense qu'une demande d'extraction pour ajouter le support de hachage MD5 serait une excellente idée.
Cette réponse m'a aidé à résoudre le problème où je voulais soulever une exception si le fichier en cours de téléchargement existait déjà. Cette version déclenche une exception si un fichier portant le même nom existe déjà dans l'emplacement de téléchargement.
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)