⬆️ ⬇️

Difficult forms in Django

image

Good day. I will try to talk about complex forms in Django. It all started when in my diploma it took to make a form that would consist of other forms. After all, if you have two forms that you use, and then another one is needed, which is just a container of those two, you will not create a new one by copying all the fields from the old ones into it, this is very stupid. Therefore, we must somehow combine them. At one time, FormWizard was in Django, but it was extremely inconvenient, so in the new version it was redone on WizardView. Django is of course MVC, but I will try to demonstrate everything in the article as thoroughly as possible, and then you can compress everything using the ModelForm and the cycles in the templates.

Let's look at our models, nothing special, but to make it clearer, we will demonstrate.





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) 




Now more to the point, as they say. Let's look at our forms.

')

Forms (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) 


Two forms: one to fill out student data, and another form - student agreement data. They are connected 1: N, that is, 1 student can have N contracts. Therefore, we need to have a form right away to add a student and conclude a contract with him (let's say that). Immediately begs to do so:



 class AddStudentForm(StudentForm,ContractForm): pass 




But this is fundamentally wrong, because with such inheritance all StudentForm functions will contract Contract, because they are identical in names and parameters (because they are inherited from the same forms.Form class).



For this and use WizardView. I will describe a more complicated case with SessionWizardView. It allows you to fill in the data step by step, keeping the intermediate form data - this is very cool, and it does not lose individual form validation. Who watched the documentation of the django, agree, an example of what is generally flimsy and not very clear, not much. So, what we need: we need to display 2 forms, after filling in all the forms, correctly create the student and his contract and, say, for the sake of prikalyukh, transmit a message to the subsequent form that the previous one is correctly filled. In essence, the view keeps a list of forms and when moving to another form, calls validation methods, and if the form has not passed validation, returns the user to an incorrect form and asks to fill in correctly. We describe our view.



View (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) ] 




Describes a simple list of forms with names, if you pass [StudentForm, ContractForm], then the form will be accessible via the key '0' or '1'.



 TEMPLATES = { "student" : "student.html", "contract" : "contract.html" } 




Description of how you can get the necessary template to the form through the key, because I’m paranoid and prefer that all the data transferred to the template through the form be described manually, because then go to something else (except Bootstrap) for registration It will be easier.



Go through the functions.



 def get_template_names(self): return [TEMPLATES[self.steps.current]] 




Returns the pattern to us when moving or first displaying the form. As you can see, self.steps we get the options for steps, in the very first display of the form self.steps.current will return “student”, and if we did not describe FORMS, would return '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 




Returns the context data of the form for the template. So, in the task we have to display that the previous form is filled in correctly, let's add the data for the contract template with the value ok. Yes, ok is exactly the string 'True', because I once encountered the ambiguity True, like booelean under the option None, etc., so now I always write unambiguous match options.



 def done(self, form_list, **kwargs) 




The function that is called when all forms are filled in correctly, at this stage we have to do something with the correct form data and send the user further.

So we do here, create a student, his full name and contract. And redirected to the page with the name of the students. We now describe the templates for displaying forms. Let's start with the base.



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> 




It does not make much sense to paint something specifically here.

We describe our basic template for displaying complex shapes.



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 need our form to work, to indicate this thing always when working with WizardView.



 <div class="control-group"> 


Our form will be described here.



 <div class="form-actions" style="padding-left: 50%"> 


There are buttons for managing actions. Yes, yes, I put the style here, it was too lazy to put it into the file.

Let's look at the template with the description of the form for entering student data.



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 %} 




Here we describe all the form fields manually. As you can see, our form is available through wizard.form, and so we can bypass all the fields of the form. For a more complete description of the fields we use another template - the description of the form field.



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> 




I use this template to describe error messages to fields.



Let's look at the template for describing the form of a contract, it's almost the same here, just add a button back to the student's data and a save button that will create the student and his contract for us, and then transfer it to the page with the list of students.



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 %} 




Fuh, like everything is described, now you need to pick up the whole thing to the url and run the project.



 url(r'^addstudent/$',AddStudentWizard.as_view(FORMS),name='addstudent'), url(r'^liststudent$',StudentsView.as_view(),name='liststudent'), 




Oh yeah, let's describe another view for the list of students.



 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 




We describe the template for this view.



 {% extends "base.html" %} {% block content %} {% for s in students %} {{ s }}<br> {% endfor %} <br> <a href="{% url addstudent %}" class="btn btn-primary"> </a> {% endblock %} 


Now that's it. Now to practice.



The initial form view.



After incorrect input.





Transition to the form with the contract with the correct filling of the previous form.





After incorrect input.





When everything is correctly filled out and clicked on “Save“, we are thrown to the page with the students.





That's all. Thank you all for your attention.

Source: https://habr.com/ru/post/158969/



All Articles