目次
ImageFieldを使っているとDjangoAdmin側のフォーム部分のリンクがバグる
自分が携わっているプロジェクトでは、S3に置かれている画像のURLをImageFieldにフルパスで入れているのですが、これだとAdmin側の編集画面で、画像のリンクの飛び先がバグってしまうということがありました。
原因としては、clearable_file_input.htmlのhrefにwidget.value.urlが使われていることで、その値がおかしいということが分かりました。
そもそもwidget.value.urlはどこから来ているのか?
フォーム上で表示されている画像のリンク自体は正しいものが表示されているのに、実際の飛び先であるhrefは正しくない飛び先ということで、clearable_file_input.htmlのファイルの中身を見てみると、上記のとおり、hrefの中身はwidget.value.urlでテキスト部分はwidget.valueが使われているという謎仕様でした。
そこでこのwidget.value.urlはどこから来ているのかを調べると、私の環境では、S3にファイルをアップロードする為にdjango-storagesというパッケージを使っているのですが、この中のurlが問題だということが分かり、これのサブクラスを作って、FileStorageに使ってあげれば良いという話でした。
コード的にはこんな感じで、url関数をoverrideして、name部分を弄ってやれば問題なくなりました。
from storages.backends.s3boto3 import S3Boto3Storage class S3FilesStorage(S3Boto3Storage): def url(self, name, parameters=None, expire=None, http_method=None): # Preserve the trailing slash after normalizing the path. # name = self._normalize_name(self._clean_name(name)) <-- これをコメントアウト if expire is None: expire = self.querystring_expire if self.custom_domain: url = "{}//{}/{}".format( self.url_protocol, self.custom_domain, filepath_to_uri(name)) if self.querystring_auth and self.cloudfront_signer: expiration = datetime.utcnow() + timedelta(seconds=expire) return self.cloudfront_signer.generate_presigned_url(url, date_less_than=expiration) return url params = parameters.copy() if parameters else {} params['Bucket'] = self.bucket.name params['Key'] = name url = self.bucket.meta.client.generate_presigned_url('get_object', Params=params, ExpiresIn=expire, HttpMethod=http_method) if self.querystring_auth: return url return self._strip_signing_parameters(url)
画像ファイルをtarget=”_blank”で開きたい
上記の変更により、URL自体は正常なものになったのですが、画像ファイルをtarget=”_blank”で開きたいという話も出ました。
そこで、clearable_file_input.htmlをカスタマイズする必要性があり、独自のものに置き換えました。
こちらはなかなか大変で、幾つかのステップをかまして、やっと実装出来ました。実装方法は下記のとおりです。
サブクラスを作り、テンプレートパスの変更
まずは、ClearableFileInputのサブクラスを作り、使われているテンプレートのパスを変更します。
from django import forms class CustomAdminFileWidget(forms.ClearableFileInput): template_name = 'admin/widgets/custom_clearable_file_input.html'
テンプレートの作成
上記でパス指定した箇所に、custom_clearable_file_input.htmlを作成し、target=”_blank”を入れます。
{% if widget.is_initial %}<p class="file-upload">{{ widget.initial_text }}: <a href="{{ widget.value.url }}" target="_blank">{{ widget.value }}</a>{% if not widget.required %} <span class="clearable-file-input"> <input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}> <label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label></span>{% endif %}<br> {{ widget.input_text }}:{% endif %} <input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.is_initial %}</p>{% endif %}
新しく作ったテンプレートのパスを見に行くようにする
普通の設定のままだと新しく作ったテンプレートを見に行ってくれずに、これが一番ハマりました。
設定ファイルに以下を追加する必要があります。
INSTALLED_APPS = ( 'django.forms', ... ) FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
色々と悩みましたが、こちらのstackoverflowに上記解決法が載っていました。
Adminでwidgetを上書きする
Adminファイル内でwidgetに作ったサブクラスを設定します。
from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS from django.db import models from utils.admin import CustomAdminFileWidget FORMFIELD_FOR_DBFIELD_DEFAULTS[models.ImageField] = {'widget': CustomAdminFileWidget} FORMFIELD_FOR_DBFIELD_DEFAULTS[models.FileField] = {'widget': CustomAdminFileWidget}
以上のやり方で、templateの書き換えまでを行うことが出来ました。
なかなか大変でした。
確かにDjangoAdminは便利なのですが、ところどころカスタマイズしたいところが出てくるので、あまり弄ることのない部分ですが、やってみて色々と勉強になりました。