web-dev-qa-db-ja.com

Djangoアップロード:アップロードされた重複を破棄し、既存のファイルを使用します(md5ベースのチェック)

ユーザーがアップロードしたファイルを保持するFileFieldのモデルがあります。スペースを節約したいので、重複を避けたいと思います。

達成したいこと:

  1. 計算アップロードされたファイルmd5チェックサム
  2. md5sumに基づくファイル名でファイルを保存します
  3. その名前のファイルがすでに存在する場合(新しいファイルは重複)、アップロードされたファイルを破棄し、代わりに既存のファイルを使用します

1および2はすでに機能していますが、アップロードされた重複を忘れて、代わりに既存のファイルを使用するにはどうすればよいですか?

既存のファイルを保持し上書きしないことに注意してくださいそれ(主に変更された時間を同じに保つために-バックアップに適しています)。

注:

  • 私はDjango 1.5を使用しています
  • アップロードハンドラはDjango.core.files.uploadhandler.TemporaryFileUploadHandler

コード:

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)

どんな助けでも大歓迎です!

26
phoibos

AlTusの回答のおかげで、カスタムストレージクラスを書くことが重要であり、それが簡単であることがわかりました。予想以上に。

  • ファイルがすでに存在する場合は、スーパークラス_saveメソッドを呼び出してファイルを書き込むことを省略し、名前を返すだけです。
  • 同じ名前のファイルがすでに存在する場合にファイル名に番号が追加されないように、get_available_nameを上書きします

これが適切な方法であるかどうかはわかりませんが、これまでのところ正常に機能しています。

これがお役に立てば幸いです。

完全なサンプルコードは次のとおりです:

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)
28
phoibos

保存/削除メソッドを使用してこれを簡単に実装することはできません。cozファイルは非常に具体的に処理されます。

しかし、あなたはそのようなsmthを試すことができます。

まず、私の単純なmd5ファイルハッシュ関数:

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

simple_upload_toはあなたのmedia_file_name関数のようなsmthです。次のように使用する必要があります。

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

もちろん、これは単なる例であるため、パス生成ロジックはさまざまです。

そして最も重要な部分:

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)

ご覧のとおり、このカスタムストレージは保存する前にファイルを削除してから、同じ名前で新しいファイルを保存します。したがって、ファイルを削除しない(したがって更新しない)ことが重要な場合は、ここでロジックを実装できます。

ストレージの詳細については、こちらをご覧ください: https://docs.djangoproject.com/en/1.5/ref/files/storage/

7
alTus

私は同じ問題を抱えていて、これを見つけましたSO質問。これはあまり珍しいことではないので、私はWebを検索し、次のPythonパッケージを見つけました。あなたが欲しいもの:

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

SHA1ハッシュが問題にならない場合は、MD5ハッシュサポートを追加するプルリクエストが最適だと思います。

2
bikeshedder

データは、テンプレート->フォーム->ビュー-> db(モデル)から取得されます。複製を最初のステップで停止することは理にかなっています。この場合、forms.pyです。

# scripts.py
import hashlib
from .models import *
def generate_sha(file):
    sha = hashlib.sha1()
    file.seek(0)
    while True:
        buf = file.read(104857600)
        if not buf:
            break
        sha.update(buf)
    sha1 = sha.hexdigest()
    file.seek(0)
    return sha1

# models.py
class images(models.Model):
    label = models.CharField(max_length=21, blank=False, null=False)
    image = models.ImageField(upload_to='images/')
    image_sha1 = models.CharField(max_length=40, blank=False, null=False)
    create_time = models.DateTimeField(auto_now=True)

# forms.py
class imageForm(forms.Form):
    Label = forms.CharField(max_length=21, required=True)
    Image = forms.ImageField(required=True)

    def clean(self):
        cleaned_data = super(imageForm, self).clean()
        Label = cleaned_data.get('Label')
        Image = cleaned_data.get('Image')
        sha1 = generate_sha(Image)
        if images.objects.filter(image_sha1=sha1).exists():
            raise forms.ValidationError('This already exists')
        if not Label:
            raise forms.ValidationError('No Label')
        if not Image:
            raise forms.ValidationError('No Image')

# views.py
from .scripts import *
from .models import *
from .forms import *

def image(request):
    if request.method == 'POST':
        form = imageForm(request.POST, request.FILES)
        if form.is_valid():
            photo = images (
                payee=request.user,
                image=request.FILES['Image'],
                image_sha1=generate_sha(request.FILES['Image'],),
                label=form.cleaned_data.get('Label'),
                )
            photo.save()
            return render(request, 'stars/image_form.html', {'form' : form})
    else:
        form = imageForm()
    context = {'form': form,}
    return render(request, 'stars/image_form.html', context)

# image_form.html
{% extends "base.html" %}
{% load static %}
{% load staticfiles %}

{% block content %}

 <div class="card mx-auto shadow p-3 mb-5 bg-white rounded text-left" style="max-width: 50rem;">
    <div class="container">
        <form action="{% url 'wallet' %}" method="post" enctype="multipart/form-data">
            {% csrf_token %}
            {{ form  }}
            <input type="submit" value="Upload" class="btn btn-outlined-primary">
        </form>

        {% if form.errors %}
            {% for field in form %}
                {% for error in field.errors %}
                    <p> {{ error }} </p>
                {% endfor %}
            {% endfor %}
        {% endif %}

    </div>
</div>

{% endblock content %}  

参照: http://josephmosby.com/2015/05/13/preventing-file-dupes-in-Django.html

0
Gajendra D Ambi

この回答は、アップロードされているファイルがすでに存在する場合に例外を発生させたいという問題を解決するのに役立ちました。このバージョンでは、同じ名前のファイルがアップロード場所にすでに存在する場合、例外が発生します。

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
rgacote