django-dbsettingsのImageValue

こんにちは



2つの単純な理由により、settings.pyで定義できないユーザー(管理)サイト設定が必要になることがよくあります。settings.pyの設定は、サーバーを再起動しないと変更できません。 そして-最も重要なこと-プログラマーによってのみ変更できます。



django-dbsettingsモジュール (以前のdjango-values)は、これらの制限からあなたを救うように設計されています:データベースにユーザー設定を保存するメカニズムと、それらを編集するための便利なビューを提供します。



そして、すべてがうまくいくようです...しかし! 設定として画像が必要な場合はどうしますか(たとえば、サイトのロゴ)。 結局のところ、django-dbsettings このタイプの値をサポートしていません



ImageValueサポートをdjango-dbsettingsにどのように追加したかについて、お話しします。





背景



設定を行うタスクに直面したとき、django-valuesプロジェクトが見つかりましたが、それは動作していませんでした。 彼に苦しんだ後、私は彼がdjango-dbsettingsに改名され、githubに移動したことを知りました。

githabには20のフォークがありました。 それらのいくつかを試した後、私は最後に更新されたもので停止しました。 彼は労働者であることが判明し、魔法なしで初めて巻き上げられました。 唯一の問題は、設定に「ピクチャ」タイプがないことです。



2つのオプションがありました。プロジェクトに松葉杖を作成して、設定として写真を追加するか、プロジェクトを分岐してすべてを美しく行います。 選択は明らかでした。



目標



この記事の目的は次のとおりです。







プロジェクト構造



(ImageValueを実装するために変更が必要な太字ファイル)









何が欠けていますか?



パッチ形式で行われた変更を表示します: " - "-削除された行、 " + "-追加された行。



パターン


-<form method="post"> +<form enctype="multipart/form-data" method="post"> ... </form>
      
      





django-dbsettingsテンプレートのフォームの説明には、フォームがファイルを受け入れるために必要なenctypeが含まれておらず、ビューはrequest.FILESでそれらを受け取ります。





 ... if request.method == 'POST': # Populate the form with user-submitted data - form = editor(request.POST.copy()) + form = editor(request.POST.copy(), request.FILES) ...
      
      





ダウンロードしたファイルが検証に合格し、入力された他のデータとともにform.cleaned_dataに分類されるようにするには、フォームの作成時に受信したファイルをリクエストからリクエストに渡す必要があります。



設定の種類


これについて詳しく説明します。

values.pyファイルには、設定の基本クラスの説明が含まれています。 他のすべてに加えて、すべての子クラスでオーバーライドする必要がある3つのメソッドがあります。

 ... class Value(object): ... def to_python(self, value): """ native-python ,       """ return value def get_db_prep_save(self, value): """  pre-save    ,     CharField   """ return unicode(value) def to_editor(self, value): """     ,      """ return unicode(value) ...
      
      





また、Valueクラスには、それを作成するためにフォームフィールドクラス(django.forms.FileInputなど)を格納する必要があるフィールド属性が必要です。



あなたの価値を書く


 class ImageValue(Value): def __init__(self, *args, **kwargs): if 'upload_to' in kwargs: self._upload_to = kwargs['upload_to'] del kwargs['upload_to'] super(ImageValue, self).__init__(*args, **kwargs) ...
      
      





基本クラスValueから継承し、 upload_toパラメーターを処理して、ユーザーの画像がアップロードされるIMAGE_ROOTのサブ​​フォルダーを制御できるようにします。



_setting_を使用するさまざまな段階で値を表示する責任のあるメソッドを再定義します。

画像を読み込んでデータベースに保存することから始めましょう。

 from os.path import join as pjoin class ImageValue(Value): ... def get_db_prep_save(self, value): if not value: return None hashed_name = md5(unicode(time.time())).hexdigest() + value.name[-4:] image_path = pjoin(self._upload_to, hashed_name) dest_name = pjoin(settings.MEDIA_ROOT, image_path) with open(dest_name, 'wb+') as dest_file: for chunk in value.chunks(): dest_file.write(chunk) return unicode(image_path) ...
      
      





パラメーターには、 django.core.files.uploadedfileからのUploadedFileオブジェクトが含まれます。 これは、ファイルをダウンロードするときに作成され、 request.FILESに分類される標準オブジェクトです。

このメソッドは簡単なトリックを生成します。一意のファイル名を作成し、ダウンロードしたファイルをself._upload_toで指定された目的のディレクトリにコピーします。 このメソッドは、 settings.IMAGE_ROOTに関連する画像へのパスを返します。この形式では、設定もデータベースに入ります。



次に、逆変換を行いましょう。データベースのレコードから画像オブジェクトを取得します。これには次のメソッドが責任を負います:

 class ImageValue(Value): ... def to_editor(self, value): if not value: return None file_name = pjoin(settings.MEDIA_ROOT, value) try: with open(file_name, 'rb') as f: uploaded_file = SimpleUploadedFile(value, f.read(), 'image') #  ""      name uploaded_file.__dict__['_name'] = value return uploaded_file except IOError: return None ...
      
      





ここでは、すべてが逆の順序で行われます。データベースから取得した値を使用して画像へのパスを構成し、 SimpleUploadedFileオブジェクトを作成して、 そこに画像ファイルを読み取ります。

行が必要な理由を説明します。

 uploaded_file.__dict__['_name'] = value
      
      





実際、アップロードされたファイルUploadedFileの基本クラスには、name属性のセッターがあり、渡されたパスからファイル名のみを切り取り、 self._nameに格納し、ゲッターはこの値を返します。 画像へのパスを手で書くことは、フォームのウィジェットにそれを転送する最も速い方法です。



そして、比較のためにオブジェクトを返すメソッドのみが残っています。 このオブジェクトは、要求から受け取った値をデータベースからの現在の値と比較して、ファイルを再度上書きしないようにするときに必要です。 ここではすべてが簡単です:

 class ImageValue(Value): ... def to_python(self, value): return unicode(value) ...
      
      







最後にもう1つ、ウィジェットがあります。ウィジェットは、標準のファイルアップロードボタンの隣に、データベースの現在の画像を表示します。

 class ImageValue(Value): ... class field(forms.ImageField): class widget(forms.FileInput): "Widget with preview" def __init__(self, attrs={}): forms.FileInput.__init__(self, attrs) def render(self, name, value, attrs=None): output = [] try: if not value: raise IOError('No value') Image.open(value.file) file_name = pjoin(settings.MEDIA_URL, value.name) output.append(u'<p><img src="{}" width="100" /></p>'.format(file_name)) except IOError: pass output.append(forms.FileInput.render(self, name, value, attrs)) return mark_safe(''.join(output)) ...
      
      





独自のフィールドクラスを作成し、その中に標準のFileInputから継承したウィジェットを作成します 。 入力を表示し、対応するhtmlを返すrenderメソッドをオーバーライドします



 Image.open(value.file)
      
      





この行は、指定されたファイルが存在するかどうかとイメージであるかどうかの2つの必要なチェックを同時に実行します。どちらの場合もIOError例外をスローできます。

mark_safe()関数は、html出力に対して安全な行をマークします(これがないと、ウィジェットのコードは単にページ上の行として表示されます)。



最終結果は次のようになります。







参照資料



github.comのdjango-dbsettings



ご清聴ありがとうございました。







PS



私はこのプロジェクトをサポートし続けるつもりですので、ビジネスでそれをテストした人々が彼らの希望を表明するか、バグについて不平を言うのは素晴らしいことです。




All Articles