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:

  1. Calculer les fichiers téléchargés somme de contrôle md5
  2. Stockez le fichier avec le nom de fichier basé sur son md5sum
  3. 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!

21
demandé sur phoibos 2013-04-08 21:22:04

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)
27
répondu phoibos 2016-12-20 17:31:11

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/

6
répondu alTus 2013-04-08 21:39:38

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.

2
répondu bikeshedder 2014-02-25 19:53:36

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)
0
répondu rgacote 2014-02-15 02:15:48