はじめに
Djangoの Class-Based-View でフォームバリデーションの仕組みを構築していたときに沼にハマったのでメモ
urls
http://www.henojiya.net/ と入力されたら IndexView を呼び出せ
app_name = 'shp'
urlpatterns = [
path('', IndexView.as_view(), name='index'),
views
テンプレートと関連付けるデータを引き連れ、index.htmlを表示させる
class IndexView(TemplateView):
# 表示するテンプレート: index.html
template_name = 'shopping/index.html'
# テンプレートに引き連れていく各種データやフォーム
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['products'] = Products.objects.all()
context['form_single'] = SingleRegistrationForm()
context['form_csv'] = UploadCSVForm()
context['editablelist'] = Products.objects.order_by('id')[:5]
context['editableform'] = EditForm()
return context
CSVをアップロードするためだけのhtml
index.html にはCSVをアップロードするためだけのフォームを作成する
formがsubmitされると regist_bulk に飛べと書いてある。
...省略...
<h3>CSV登録</h3>
<form action="{% url 'shp:regist_bulk' %}" method="POST" class="registration" enctype="multipart/form-data">
{% csrf_token %}
{{ form_csv.as_p }}
<input type="submit" value="UPLOAD">
</form>
...省略...
urls
regist_bulk が呼ばれたら UploadBulkView を呼び出せ
app_name = 'shp'
urlpatterns = [
path('regist/bulk/', UploadBulkView.as_view(), name='regist_bulk'),
views
UploadBulkView が呼ばれると、バリデーションがうまくいったときとうまくいかなかったときの処理を上書きできる仕組みが用意されている。今回はバリデーションでハジかれたときにフォーカスする。この form_invalid に行く前に、フォームのフィールドごとのバリデーションが走る。
class UploadBulkView(FormView):
"""UploadBulkView"""
form_class = UploadCSVForm
success_url = reverse_lazy('shp:index')
# バリデーションがうまくいったとき
def form_valid(self, form):
...省略...
# バリデーションがうまくいかなかったとき
def form_invalid(self, form):
messages.add_message(self.request, messages.WARNING, form.errors)
return redirect('shp:index')
フォームのフィールドごとのバリデーション
clean_(field_name)というメソッドをつくるとフィールドごとのバリデーションができる。今回は csv 以外をアップロードしていないかをチェックした。
class UploadCSVForm(forms.Form):
""" formのname 属性が 'file' になる """
file = forms.FileField(required=True, label='')
def clean_file(self):
"""csvファイル要件を満たすかどうかをチェックします"""
file = self.cleaned_data['file']
if not file.name.endswith('.csv'):
raise forms.ValidationError('拡張子はcsvのみです')
return file
ValidationError にメッセージを仕込んで raise するとサーバーサイドでのprintをしたときにこんなhtmlが入っていた。
<ul class="errorlist">
<li>file
<ul class="errorlist">
<li>拡張子はcsvのみです</li>
</ul>
</li>
</ul>」
views
さて、バリデーションが終わると、form_invalidの処理に流れる。バリデーションが失敗したら、エラーメッセージとともに元のページにリダイレクトするっていうのはよくありそうな処理じゃん?でもリダイレクト時にエラーメッセージを持っていけないのよwwwゴーギャーン!って感じやん。だから、「form」の引数の「form.errors」に入っているhtmlを messages に渡して、その後にシンプルなリダイレクトという手段を取った。
class UploadBulkView(FormView):
"""UploadBulkView"""
form_class = UploadCSVForm
success_url = reverse_lazy('shp:index')
# バリデーションがうまくいったとき
def form_valid(self, form):
...省略...
# バリデーションがうまくいかなかったとき
def form_invalid(self, form):
messages.add_message(self.request, messages.WARNING, form.errors)
return redirect('shp:index')
Tips: 元のページにもどる
class UploadBulkView(FormView):
"""UploadBulkView"""
form_class = UploadCSVForm
success_url = reverse_lazy('shp:index')
# バリデーションがうまくいったとき
def form_valid(self, form):
+ return super().form_valid(form) # あれ、こっちうまくいかねーな...redirectしか無いかな?
# バリデーションがうまくいかなかったとき
def form_invalid(self, form):
messages.add_message(self.request, messages.WARNING, form.errors)
+ return super().form_invalid(form)
最終結果
htmlにエラーhtmlを表示する記述を追加。とりあえずオッケー!
{% if messages %}
{% for message in messages %}
{{ message }}
{% endfor %}
{% endif %}