2013-04-08 16 views
19

Ho un modello con un FileField, che contiene i file caricati dall'utente. Dal momento che voglio risparmiare spazio, vorrei evitare i duplicati.Caricamenti Django: Elimina i duplicati caricati, utilizza il file esistente (controllo basato su MD5)

Quello che mi piacerebbe raggiungere:

  1. Calcolare i file caricati checksum MD5
  2. Conservare il file con il nome file basandosi sul suo md5sum
  3. Se un file con questo nome è già presente (il nuovo file è un duplicato), eliminare il file caricato e utilizzare il file esistente invece

e sta già lavorando, ma come potrei dimenticare un duplicato caricato e utilizzare il file esistente, invece?

noti che mi piacerebbe mantenere il file esistente e non sovrascrivere (soprattutto per mantenere l'ora di modifica lo stesso - meglio per il backup).

Note:

  • sto usando Django 1.5
  • Il gestore di upload è django.core.files.uploadhandler.TemporaryFileUploadHandler

Codice:

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) 

Qualsiasi hel p è apprezzato!

+0

Quanto traffico pensi di ottenere? Se si tratta di un piccolo progetto o di un progetto privato, è possibile sborsare $ 0,50/mese per Amazon S3, o Rackspace Cloudfiles, o qualsiasi altro archivio a basso costo là fuori. –

risposta

25

Grazie a Altus risposta, sono stato in grado di capire che scrivere un custom storage class è la chiave, ed è stato più facile del previsto.

  • Ho appena omettere chiamando le superclassi _save metodo per scrivere il file se è già lì e ho appena restituire il nome.
  • I sovrascrivere get_available_name, per evitare di ottenere i numeri aggiunti al nome del file se un file con lo stesso nome è già esistente

Non so se questo è il corretta modo di farlo, ma funziona bene finora.

Spero che questo sia utile!

Ecco il codice di esempio 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) 
+0

codice veramente bello: a cosa serve h [0: 1], h [1: 2] nel percorso? – zinking

+0

Oh, è solo per la distribuzione in directory diverse/0/0/-/f/f /, Non volevo che tutti i file fossero memorizzati in uno solo. – phoibos

+0

questo creerà comunque una voce nel database con un nuovo pk ma lo stesso nome di file - come hai gestito questo? – MJP

6

AFAIK non è possibile implementarlo facilmente utilizzando i metodi di salvataggio/cancellazione i file coz vengono gestiti in modo specifico.

Ma si potrebbe provare smth in questo modo.

In primo luogo, il mio semplice funzione di file hash MD5:

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

Successivo simple_upload_to è smth è come la tua media_file_name funzione. Si dovrebbe usare in quel modo:

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

Naturalmente, è solo un esempio così logica di generazione percorso potrebbe essere diverso.

E la parte più 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) 

Come si può vedere questo un'archiviazione personalizzata cancella file prima di salvare e quindi salva uno nuovo con lo stesso nome. Quindi qui puoi implementare la tua logica se NON è necessario cancellare (e quindi aggiornare) i file.

più su depositi ou può trovare qui: https://docs.djangoproject.com/en/1.5/ref/files/storage/

+0

Grazie! La tua risposta mi ha portato sulla strada giusta. – phoibos

0

Questa risposta mi ha aiutato a risolvere il problema in cui ho voluto sollevare un'eccezione se il file caricato già esisteva. Questa versione solleva un'eccezione se esiste già un file con lo stesso nome nel percorso di caricamento.

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

Ho avuto lo stesso problema e ho trovato questa domanda SO. Poiché si tratta di niente di troppo raro ho cercato nel web e ho trovato il seguente pacchetto di Python che cuciture a fare esattamente quello che vuoi:

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

Se gli hash SHA1 sono fuori discussione penso che una richiesta di pull per aggiungere MD5 hash il supporto sarebbe una grande idea.

Problemi correlati