2013-04-08 27 views
19

Tôi có một mô hình với FileField, chứa các tệp do người dùng tải lên. Vì tôi muốn tiết kiệm không gian, tôi muốn tránh trùng lặp.Tải lên Django: Hủy các bản sao được tải lên, sử dụng tệp hiện có (kiểm tra dựa trên md5)

Những gì tôi muốn đạt được:

  1. Tính các tập tin được tải lên md5 checksum
  2. Store tập tin với tên tập tin dựa trên md5sum của nó
  3. Nếu một tệp có tên đó đã có (tệp mới là trùng lặp), loại bỏ các tập tin được tải lên và sử dụng các tập tin hiện tại thay vì

và đã được làm việc, nhưng thế nào tôi sẽ quên đi một trùng lặp được tải lên và sử dụng các tập tin hiện có để thay thế?

Lưu ý rằng tôi muốn giữ các tập tin hiện cókhông ghi đè lên nó (chủ yếu là để giữ cho thời gian sửa đổi giống nhau - tốt hơn để sao lưu).

Ghi chú:

  • Tôi đang sử dụng Django 1,5
  • Việc xử lý tải lên là 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) 

Bất kỳ hel p được đánh giá cao!

+0

Bạn dự định nhận bao nhiêu lưu lượng truy cập? Nếu đó là một dự án nhỏ hoặc một dự án riêng tư, bạn có thể chia rẽ trên $ 0,50/tháng cho Amazon S3 hoặc Cloudspace của Rackspace hoặc bất kỳ bộ phim rẻ tiền nào khác ngoài kia. –

Trả lời

25

Nhờ Altus câu trả lời, tôi đã có thể hình dung ra rằng viết một custom storage class là chìa khóa, và nó đã được dễ dàng hơn mong đợi.

  • Tôi chỉ bỏ qua việc gọi các siêu lớp _save để ghi tệp nếu nó đã có và tôi chỉ trả lại tên.
  • tôi ghi đè lên get_available_name, để tránh bị số gắn vào tên tập tin nếu một tập tin có cùng tên đã được hiện

Tôi không biết nếu điều này là đúng đắn cách để làm việc đó, nhưng nó hoạt động tốt cho đến nay.

Hy vọng điều này hữu ích!

Dưới đây là các mẫu mã hoàn chỉnh:

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

mã thực sự tốt đẹp: việc sử dụng h [0: 1], h [1: 2] trong đường dẫn là gì? – zinking

+0

Ồ, đó chỉ là để phân phối cho các thư mục khác nhau/0/0/-/f/f /, tôi không muốn có tất cả các tệp được lưu trữ trong một thư mục duy nhất. – phoibos

+0

điều này sẽ vẫn tạo ra một mục trong cơ sở dữ liệu với một pk mới nhưng cùng tên tập tin - làm thế nào bạn xử lý này? – MJP

6

AFAIK bạn không thể dễ dàng thực hiện việc này bằng cách sử dụng các phương pháp lưu/xóa tập tin coz được xử lý khá cụ thể.

Nhưng bạn có thể thử smth như thế.

Thứ nhất, đơn giản chức năng tập tin md5 hash của tôi:

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

Tiếp simple_upload_to là là smth như của bạn media_file_name chức năng. Bạn nên sử dụng nó như sau:

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

Tất nhiên, đây chỉ là ví dụ để logic tạo đường dẫn có thể khác nhau.

Và phần quan trọng nhất:

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) 

Như bạn có thể thấy dung lượng được tùy chỉnh này xóa tập tin trước khi lưu và sau đó tiết kiệm mới có cùng tên. Vì vậy, ở đây bạn có thể thực hiện logic của bạn nếu không xóa (và do đó việc cập nhật) các tệp quan trọng.

Thông tin thêm về kho ou có thể tìm thấy ở đây: https://docs.djangoproject.com/en/1.5/ref/files/storage/

+0

Cảm ơn! Câu trả lời của bạn đưa tôi đi đúng hướng. – phoibos

0

Câu trả lời này đã giúp tôi giải quyết vấn đề mà tôi muốn nâng cao một ngoại lệ nếu các tập tin được tải lên đã tồn tại. Phiên bản này làm tăng ngoại lệ nếu tệp có cùng tên đã tồn tại ở vị trí tải lên.

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

Tôi gặp vấn đề tương tự và tìm thấy câu hỏi SO này. Vì đây là không có gì quá bất thường Tôi đã tìm kiếm web và tìm thấy các gói sau Python mà vỉa để làm chính xác những gì bạn muốn:

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

Nếu băm SHA1 ra khỏi câu hỏi tôi nghĩ rằng một yêu cầu kéo thêm MD5 băm hỗ trợ sẽ là một ý tưởng tuyệt vời.

Các vấn đề liên quan