2つの単純な理由により、settings.pyで定義できないユーザー(管理)サイト設定が必要になることがよくあります。settings.pyの設定は、サーバーを再起動しないと変更できません。 そして-最も重要なこと-プログラマーによってのみ変更できます。
django-dbsettingsモジュール (以前のdjango-values)は、これらの制限からあなたを救うように設計されています:データベースにユーザー設定を保存するメカニズムと、それらを編集するための便利なビューを提供します。
そして、すべてがうまくいくようです...しかし! 設定として画像が必要な場合はどうしますか(たとえば、サイトのロゴ)。 結局のところ、django-dbsettings はこのタイプの値をサポートしていません 。
ImageValueサポートをdjango-dbsettingsにどのように追加したかについて、お話しします。
背景
設定を行うタスクに直面したとき、django-valuesプロジェクトが見つかりましたが、それは動作していませんでした。 彼に苦しんだ後、私は彼がdjango-dbsettingsに改名され、githubに移動したことを知りました。
githabには20のフォークがありました。 それらのいくつかを試した後、私は最後に更新されたもので停止しました。 彼は労働者であることが判明し、魔法なしで初めて巻き上げられました。 唯一の問題は、設定に「ピクチャ」タイプがないことです。
2つのオプションがありました。プロジェクトに松葉杖を作成して、設定として写真を追加するか、プロジェクトを分岐してすべてを美しく行います。 選択は明らかでした。
目標
この記事の目的は次のとおりです。
- まず、django-dbsettingsがどのように機能するかを示して、_settings_タイプを追加する必要がある人がこのモジュールのコードを読むのに余分な日を費やさないようにしたかったのです。
- 第二に、読者に(むしろ、「コードライター」の場合でも)私の願いを伝えたかったのです。 自分の「頭脳」に誇りと美的満足感を感じるまで、コードの確認と修正を続けます(ただし、狂信的な表現はしないでください:「シンプルに保つ」©)。
- この記事は、オープンソースプロジェクトに変更を加える例であり、具体的には、作業がスタイルとロジックでプロジェクトにうまく適合し、他のシステムモジュールの動作を妨げない方法を示しています。 私の決定は、ここで提示されたものではありませんでした。まず、他のファイルが影響を受けました。 いくつかの余分なifや継承などがありましたが、前のポイントに続いて、コードを最小化およびローカライズすることができました。
プロジェクト構造
(ImageValueを実装するために変更が必要な太字ファイル)
- テンプレート/ -2つのテンプレートを含む:サイト全体の設定と個々のアプリケーションの設定を表示および編集するため
- テスト/- テストを含む
- __init__.py
- dbsettings.txt- モジュールの使用に関するヘルプ(使用方法について詳しく説明しています)
- forms.py- 設定を編集するためのフォームコンストラクター
- group.py- 設定グループのクラスを定義し、アクセス権を制御します
- loading.py- データベースを操作するための関数が含まれています(設定の追加、保存、読み取り)
- models.py- 設定を保存するための独自のモデルが含まれています
- urls.py- 設定ページを表示および編集するためのURLが含まれています
- utils.py- syncdbの実行時にデフォルト値を設定する機能が含まれています
- values.py- 設定の種類の説明が含まれています
- views.py- ビュー/編集の説明が含まれています
何が欠けていますか?
パッチ形式で行われた変更を表示します: " - "-削除された行、 " + "-追加された行。
パターン
-<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
私はこのプロジェクトをサポートし続けるつもりですので、ビジネスでそれをテストした人々が彼らの希望を表明するか、バグについて不平を言うのは素晴らしいことです。