2013-04-08 5 views
19

Ich habe ein Modell mit einem FileField, die Benutzer hochgeladenen Dateien enthält. Da ich Platz sparen möchte, möchte ich Duplikate vermeiden.Django Uploads: Verwerfen hochgeladene Duplikate, verwenden Sie vorhandene Datei (MD5 basierte Überprüfung)

Was würde Ich mag erreichen: Prüfsumme

  1. berechnen die hochgeladenen Dateien md5 mit dem Dateinamen
  2. speichern Sie die Datei auf der Grundlage seiner md5sum
  3. Wenn Eine Datei mit diesem Namen ist bereits vorhanden (die neue Datei ist Duplikat), verwerfen die hochgeladene Datei und die vorhandene Datei anstelle

und arbeitet bereits, aber , wie würde ich mich über ein hochgeladenes Duplikat vergessen und nutzen die vorhandene Datei stattdessen?

Beachten Sie, dass Ich mag würde die vorhandene Datei und -halten es nicht überschrieben werden (vor allem die geänderte Zeit zu halten, das gleiche - besser für die Sicherung).

Hinweise:

  • Ich verwende Django 1.5
  • Der Upload-Handler ist 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) 

Alle hel p wird geschätzt!

+0

Wie viel Verkehr möchten Sie erhalten? Wenn es sich um ein kleines Projekt oder ein privates Projekt handelt, können Sie die $ 0,50/Monat für Amazon S3 oder Rackspace Cloudfiles oder einen anderen günstigen Filestore ausdiskutieren. –

Antwort

25

Dank Altus Antwort, ich war in der Lage, dass ein schreiben, um herauszufinden, custom storage class der Schlüssel, und es war leichter als erwartet.

  • Ich lasse nur die Super _save Methode aufrufen, die Datei zu schreiben, wenn es schon da ist und ich wieder nur den Namen.
  • ich überschreiben get_available_name, Zahlen zu vermeiden, immer an den Dateinamen angehängt, wenn eine Datei mit dem gleichen Namen bereits
  • vorhandenen ist

Ich weiß nicht, ob dies der richtige Art und Weise, es zu tun, aber Es funktioniert soweit gut.

Hoffe, das ist nützlich!

ist die komplette Beispielcode:

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) 
+0

wirklich schöner Code: Was ist die Verwendung von h [0: 1], h [1: 2] im Pfad für? – zinking

+0

Oh, das ist nur für die Verteilung an verschiedene Verzeichnisse/0/0/-/f/f /, ich wollte nicht alle Dateien in einem einzigen gespeichert haben. – phoibos

+0

Dies wird immer noch einen Eintrag in der Datenbank mit einem neuen pk, aber den gleichen Dateinamen erstellen - wie hast du damit umgegangen? – MJP

6

AFAIK Sie können dies nicht einfach mit Speichern/Löschen Methoden implementieren Coz-Dateien werden ganz spezifisch behandelt.

Aber Sie könnten etw so versuchen.

Zuerst meine einfache md5 Datei-Hash-Funktion:

def md5_for_file(chunks): 
    md5 = hashlib.md5() 
    for data in chunks: 
     md5.update(data) 
    return md5.hexdigest() 

Weiter simple_upload_to ist smth wie das Ihre media_file_name Funktion. Sie sollten es so verwenden:

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()) 

Natürlich, es ist nur ein Beispiel so Wegerzeugungs- Logik verschiedene sein könnte.

Und der wichtigste Teil:

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) 

Wie Sie diese benutzerdefinierten Speicher sparen mit dem gleichen Namen neue Datei sehen können vor dem Speichern und dann löschen. Hier können Sie also Ihre Logik implementieren, wenn NICHT das Löschen (und somit das Aktualisieren) von Dateien wichtig ist.

Mehr über Einlagerungen können ou finden Sie hier: https://docs.djangoproject.com/en/1.5/ref/files/storage/

+0

Danke! Ihre Antwort brachte mich auf den richtigen Weg. – phoibos

0

Diese Antwort hat mir geholfen, das Problem zu lösen, wo ich eine Ausnahme erhöhen wollte, ob die Datei existiert bereits hochgeladen werden. Diese Version löst eine Ausnahme aus, wenn eine Datei mit demselben Namen bereits im Upload-Speicherort vorhanden ist.

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) 
2

Ich hatte das gleiche Problem und fand diese SO Frage. Da es sich um nichts zu selten ich das Web durchsucht und die folgende Python-Paket gefunden, die genau das zu tun, Nähte, was Sie wollen:

https://pypi.python.org/pypi/django-hashedfilenamestorage

Wenn SHA1-Hashes aus Frage sind glaube ich, eine Pull-Anforderung MD5-Hashing hinzufügen Unterstützung wäre eine großartige Idee.