こんにちは Djangoの複雑なフォームについてお話します。 それはすべて、私の卒業証書が他のフォームで構成されるフォームを作成する必要があるときに始まりました。 結局のところ、使用する2つのフォームがあり、その2つのコンテナだけである別のフォームが必要な場合、新しいフィールドを作成せず、古いフィールドをすべてそこにコピーします。これは非常に愚かなことです。 したがって、何らかの方法でそれらを組み合わせる必要があります。 かつてDjangoにはFormWizardがありましたが、非常に不便だったため、新しいバージョンではWizardView用にやり直しました。 DjangoはもちろんMVCですが、記事では可能な限り詳細にすべてをデモンストレーションしようとします。そして、ModelFormとテンプレートのループを使用してすべてを圧縮することはすでに可能です。
私たちのモデルを見てみましょう、特別なものはありませんが、より明確にするために、デモンストレーションします。
class Citizenship(models.Model): name = models.CharField(max_length = 50,verbose_name = u'') class CertificateType(models.Model): name = models.CharField(max_length = 50,verbose_name = u'') class Student(models.Model): SEX_CHOICES = ( ('m',u""), ('w',u""), ) sex = models.CharField(max_length=1,verbose_name=u"",choices=SEX_CHOICES) citizenship = models.ForeignKey(Citizenship, verbose_name = u"") doc = models.CharField(max_length = 240,verbose_name = u"doc") student_document_type = models.ForeignKey(CertificateType, related_name = 'student_document',verbose_name = u" ") parent_document_type = models.ForeignKey(CertificateType, related_name = 'parent_document', verbose_name = u" ") def __unicode__(self): try: return unicode(self.fiochange_set.latest('event_date').fio) except FioChange.DoesNotExist: return u'No name' class Contract(models.Model): student = models.ForeignKey(Student,verbose_name = u'') number = models.CharField(max_length=24,verbose_name = u" ") student_home_phone = models.CharField(max_length = 180, verbose_name = u" ") class FioChange(models.Model): event_date = models.DateField(verbose_name = u' ', null = True, blank = True) student = models.ForeignKey(Student,verbose_name = u"") fio = models.CharField(max_length = 120, verbose_name = u"") def __unicode__(self): return unicode(self.fio)
彼らが言うように、ポイントに近づきました。 フォームを見てみましょう。
フォーム(forms.py)
class NameModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): return "%s"%obj.name class StudentForm(forms.Form): SEX_CHOICES = ( ('m',u""), ('w',u""), ) sex = forms.ChoiceField(label=u'', choices = SEX_CHOICES) citizenship = NameModelChoiceField(label = u'',queryset = Citizenship.objects.order_by('-name'),initial = Citizenship.objects.get(id=1)) doc = forms.CharField(label = u'',max_length = 50) student_document_type = NameModelChoiceField(label = u' ', queryset = CertificateType.objects.order_by('-name'),initial = CertificateType.objects.get(id = 1)) parent_document_type = NameModelChoiceField(label = u' ', queryset = CertificateType.objects.order_by('-name'), initial = CertificateType.objects.get(id = 1)) event_date = forms.DateTimeField(required = False, label = u' : ', initial = datetime.date.today,help_text = u' ') fio = forms.CharField(label = u' ', max_length = 60) class ContractForm(forms.Form): number = forms.CharField(label = u' ', max_length = 5) phone = forms.CharField(label = u' ', max_length = 7)
2つのフォーム:学生データを記入するためのフォームと、学生同意書データを記入するためのフォーム。 それらは1つに接続されます:N、つまり、1人の学生がN個の契約を結べます したがって、学生を追加して彼との契約を締結するためのフォームをすぐに用意する必要があります(そう言ってみましょう)。 すぐにそうするように頼みます:
class AddStudentForm(StudentForm,ContractForm): pass
ただし、この継承では、すべてのStudentForm関数は名前とパラメーターが同じであるため(同じforms.Formクラスから継承されるため)、ContractFormによってほつれます。
これには、WizardViewを使用します。 SessionWizardViewを使用したより複雑なケースについて説明します。 中間フォームデータを保存して、段階的にデータを入力することができます-これは非常に優れていますが、フォームの個々の検証を失うことはありません。 ジャンゴの文書を見た人は同意するでしょう、ある種の例は一般に薄っぺらであり、あまり明確ではありません。 だから、私たちは何を必要とします:すべてのフォームに記入した後、2つのフォームを表示し、生徒と彼の契約を正しく作成し、楽しみのために、前のフォームが正しく記入されているというメッセージを次のフォームに送信する必要があります 本質的に、ビューはフォームのリストを保存し、別のフォームに切り替えるときに検証メソッドを呼び出し、フォームが検証に合格しなかった場合、ユーザーを無効なフォームに戻し、正しく入力するよう要求します。 私たちの見解を説明しましょう。
表示(view.py)
FORMS = [ ("student", StudentForm), ("contract", ContractForm) ] TEMPLATES = { "student" : "student.html", "contract" : "contract.html" } class AddStudentWizard(SessionWizardView): def get_template_names(self): return [TEMPLATES[self.steps.current]] def get_context_data(self, form, **kwargs): context = super(AddStudentWizard, self).get_context_data(form=form, **kwargs) if self.steps.current == 'contract': context.update({'ok': 'True'}) return context def done(self, form_list, **kwargs): student_form = form_list[0].cleaned_data contract_form = form_list[1].cleaned_data s = Student.objects.create( sex = student_form['sex'], citizenship = student_form['citizenship'], doc = student_form['doc'], student_document_type = student_form['student_document_type'], parent_document_type = student_form['parent_document_type'] ) f = FioChange.objects.create( student = s, event_date = student_form['event_date'], fio = student_form['fio'] ) c = Contract.objects.create( student = s, number = contract_form['number'], student_home_phone = contract_form['phone'] ) return HttpResponseRedirect(reverse('liststudent'))
FORMS = [ ("student", StudentForm), ("contract", ContractForm) ]
フォームのリストに名前を付けて説明するだけです; [StudentForm、ContractForm]を渡すと、キー '0'または '1'からフォームにアクセスできます。
TEMPLATES = { "student" : "student.html", "contract" : "contract.html" }
キーを介してフォームに目的のテンプレートを取得する方法の説明。私は妄想的であり、フォームを介してテンプレートに送信されたすべてのデータを手動で説明することを好むため、デザインのために他の何か(ブートストラップを除く)に行くため簡単になります。
関数を見てみましょう。
def get_template_names(self): return [TEMPLATES[self.steps.current]]
遷移時またはフォームの最初の表示時にテンプレートを返します。 ご覧のとおり、self.stepsはステップのオプションを取得します。self.steps.currentの最初の表示では「student」が返され、FORMSを記述しない場合は「0」が返されます。
def get_context_data(self, form, **kwargs): context = super(AddStudentWizard, self).get_context_data(form=form, **kwargs) if self.steps.current == 'contract': context.update({'ok': 'True'}) return context
テンプレートのフォームのコンテキストデータを返します。 したがって、タスクでは、以前のフォームが正しく記入されていることを表示する必要があります。契約テンプレートのデータに値okを追加してみましょう。 はい、okは文字列「True」とまったく同じです。これは、NoneオプションなどのブーリアンのようにTrueのあいまいさが発生したことがあるため、今では常にあいまいでないマッチングオプションを記述しています。
def done(self, form_list, **kwargs)
すべてのフォームが正しく入力されたときに呼び出される関数。この段階では、正しいフォームデータで何かを実行し、さらにユーザーを送信する必要があります。
そこでここで、学生とその名前と契約を作成します。 そして、学生の名前のページにリダイレクトします。 次に、フォームを表示するためのテンプレートについて説明します。 ベースラインから始めましょう。
base.html
<!DOCTYPE html> {% load static %} <html> <head> <script type="text/javascript" src="{% static 'bootstrap/js/jquery.js'%}"></script> <link href="{% static 'bootstrap/css/bootstrap.css'%}" rel="stylesheet"> <script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.js'%}"></script> <style type="text/css"> #main-conteiter { padding-top: 5%; } </style> {% block head %} <title>{% block title %}Example Wizard{% endblock %}</title> {% endblock %} </head> <body> <div class="container" id="main-conteiner"> {% block content %} <!-- body --> {% endblock %} </div> </body> </html>
ここで具体的に何かを描くことはあまり意味がありません。
複雑なフォームを表示するための基本的なテンプレートについて説明します。
wizard_template.html
{% extends "base.html" %} {% block head %} {{ block.super }} {{ wizard.form.media }} {% endblock %} {% block content %} <p class="text-info"> , {{ wizard.steps.step1 }} {{ wizard.steps.count }}</p> <h3>{% block title_wizard %}{% endblock %}</h3> <form class="well form-horizontal" action="." method="POST">{% csrf_token %} {{ wizard.management_form }} <div class="control-group"> {% block form_wizard %} {% endblock %} </div> <div class="form-actions" style="padding-left: 50%"> {% block button_wizard %} {% endblock %} </div> </form> {% endblock %}
wizard.management_formは、WizardViewで作業するときに常にこのことを示すために、フォームが機能する必要があります。
<div class="control-group">
ここで、フォームについて説明します。
<div class="form-actions" style="padding-left: 50%">
アクションを管理するためのボタンがあります。 はい、はい、ここにスタイルを貼り付けました。ファイルに入れるのが面倒でした。
学生データを入力するためのフォームの説明を含むテンプレートを見てみましょう。
student.html
{% extends "wizard_template.html" %} {% load i18n %} {% block title_wizard %} {% endblock %} {% block form_wizard %} {% include "input_field.html" with f=wizard.form.sex %} {% include "input_field.html" with f=wizard.form.citizenship %} {% include "input_field.html" with f=wizard.form.doc %} {% include "input_field.html" with f=wizard.form.student_document_type %} {% include "input_field.html" with f=wizard.form.parent_document_type %} {% include "input_field.html" with f=wizard.form.event_date %} {% include "input_field.html" with f=wizard.form.fio %} {% endblock %} {% block button_wizard %} <button type="submit" class="btn btn-primary"> <i class="icon-user icon-white"></i> <i class="icon-arrow-right icon-white"></i> </button> {% endblock %}
ここでは、すべてのフォームフィールドを手作業で説明します。 ご覧のとおり、フォームにはwizard.formからアクセスできるため、フォームのすべてのフィールドをバイパスできます。 フィールドのより完全な説明のために、別のテンプレート-フォームフィールドの説明を使用します。
input_field.html
<div class="control-group {% if f.errors %}error{% endif %}"> <label class="control-label" for="{{f.id_for_label}}">{{ f.label|capfirst }}</label> <div class="controls"> {{f}} <span class="help-inline"> {% for error in f.errors %} {{ error|escape }} {% endfor %} </span> </div> </div>
このテンプレートを使用して、フィールドのエラーメッセージを記述します。
契約の形式を説明するテンプレートを見てみましょう。ここではほぼ同じです。生徒のデータと保存ボタンに戻るボタンのみが追加され、生徒とその契約が作成され、生徒のリストを含むページに転送されます。
contract.html
{% extends "wizard_template.html" %} {% block title_wizard %} {% endblock %} {% block form_wizard %} {% if ok == 'True' %} <div class="alert alert-success"> <button type="button" class="close" data-dismiss="alert">×</button> <strong>!</strong> . </div> {% endif %} {% include "input_field.html" with f=wizard.form.number %} {% include "input_field.html" with f=wizard.form.phone %} {% endblock %} {% block button_wizard %} <button name="wizard_goto_step" class="btn btn-primary" type="submit" value="{{ wizard.steps.prev }}"> <i class="icon-user icon-white"></i> <i class="icon-arrow-left icon-white"></i> </button> <input type="submit" class="btn btn-primary" value=""/> {% endblock %}
ええと、すべてが記述されているようです。次に、このすべてをURLにフックしてプロジェクトを実行する必要があります。
url(r'^addstudent/$',AddStudentWizard.as_view(FORMS),name='addstudent'), url(r'^liststudent$',StudentsView.as_view(),name='liststudent'),
そうそう、学生のリストの別のビューについて説明します。
class StudentsView(TemplateView): template_name = "list.html" def get_context_data(self, **kwargs): context = super(StudentsView, self).get_context_data(**kwargs) context.update({ 'students' : Student.objects.all() }) return context
このビューのテンプレートについて説明しましょう。
{% extends "base.html" %} {% block content %} {% for s in students %} {{ s }}<br> {% endfor %} <br> <a href="{% url addstudent %}" class="btn btn-primary"> </a> {% endblock %}
これですべてです。 今すぐ練習します。
フォームの初期フォーム。
誤った入力の後。
前のフォームに正しく記入するときに、契約のあるフォームに移行します。
誤った入力の後。
すべてが正しく記入され、「保存」をクリックすると、生徒のいるページに転送されます。
以上です。 ご清聴ありがとうございました。