How to work with ModelFormSet in Form Wizard - python

Django documentation is not very well documented on this subject. Indeed, the only reference they have in the docs is this paragraph:
How to work with ModelForm and ModelFormSet
WizardView.instance_dict.
WizardView supports ModelForms and ModelFormSets. Additionally to initial_dict, the as_view() >method takes an instance_dict argument that should contain model instances for steps based on >ModelForm and querysets for steps based on ModelFormSet.
I haven't found any good and clear examples on how to use this. Can someone help me with this?
Specifically:
What to do in the forms.py?
What if I need a ModelFormSet only on certain steps of the form, not in all of them?
What do I need to do in the views.py and templates?
To put a use case and little project I'm working on a as an example I share my code:
Use case:
A user wants to register in a multistep form, in the first step he introduces his name and last name.
in the second step he introduces his passport number and a hotel registration, he also wants to register his son and wife, which are going with him to this hotel (here I want to use the modelformset from the HotelRegistration model).
in the third step he types his flight information. And then receives a confirmation message if the form is valid and saved in the database.
Here is my code:
models.py
class Event(models.Model):
name = models.CharField(max_length=100)
date_from = models.DateField(auto_now=False)
date_to = models.DateField(auto_now=False)
description = models.TextField()
def __unicode__(self):
return self.name
class Hotel(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class HotelRegistration(models.Model):
pax_first_name = models.CharField(max_length=50)
pax_last_name = models.CharField(max_length=50)
hotel = models.ForeignKey(Hotel)
def __unicode__(self):
return self.pax_first_name
class Registration(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
passport = models.CharField(max_length=15)
city_origin = models.CharField(max_length=50)
flight_date_from = models.DateField(auto_now=False)
flight_date_to = models.DateField(auto_now=False)
require_transfer = models.BooleanField(default=None)
event = models.ForeignKey(Event)
hotel_registration = models.ForeignKey(HotelRegistration, blank=True, null=True)
def __unicode__(self):
return self.first_name
forms.py
from django import forms
from .models import Registration
class FormStep1(forms.ModelForm):
class Meta:
model = Registration
fields = ['first_name', 'last_name']
widgets = {
'first_name': forms.TextInput(attrs={'placeholder': 'Nombre de la persona que esta reservando', 'label_tag': 'Nombre'}),
'last_name': forms.TextInput(attrs={'placeholder': 'Apellido'})
}
class FormStep2(forms.ModelForm):
class Meta:
model = Registration
fields = ['passport', 'hotel_registration']
exclude = ('first_name', 'last_name', 'event' , 'city_origin', 'flight_date_from', 'flight_date_to', 'require_transfer')
widgets = {
'passport': forms.NumberInput(attrs={'placeholder':'Escriba su pasaporte'})
}
class FormStep3(forms.ModelForm):
class Meta:
model = Registration
fields = ['city_origin', 'flight_date_from', 'flight_date_to', 'require_transfer', 'event']
exclude = ('first_name', 'last_name', 'hotel_registration')
widgets = {
'city_origin': forms.TextInput(attrs={'placeholder':'Ciudad desde donde esta viajando'}),
'flight_date_from': forms.DateInput(format=('%d-%m-%Y'), attrs={'class':'myDateClass', 'placeholder':'Select a date'}),
'flight_date_to': forms.DateInput(format=('%d-%m-%Y'), attrs={'class':'myDateClass', 'placeholder':'Select a date'}),
'require_transfer': forms.Select(),
'event': forms.Select()
}
views.py
from django.shortcuts import render
from django.contrib.formtools.wizard.views import SessionWizardView
from django.http import HttpResponseRedirect
from django.views.generic import TemplateView
from django.forms.models import inlineformset_factory
from .models import Registration, HotelRegistration
from .forms import FormStep1, FormStep2, FormStep3
FORMS = [
("step1", FormStep1),
("step2", FormStep2),
("step3", FormStep3)
]
TEMPLATES = {
"step1" : "wizard/step1.html",
"step2" : "wizard/step2.html",
"step3" : "wizard/step3.html"
}
class TestFormWizard(SessionWizardView):
instance = None
def get_form_instance(self, step):
if self.instance is None:
self.instance = Registration()
return self.instance
def get_form(self, step=None, data=None, files=None):
form = super(TestFormWizard, self).get_form(step, data, files)
HotelRegistFormSet = inlineformset_factory(HotelRegistration, Registration, can_delete=True, extra=1)
# determine the step if not given
if step is None:
step = self.steps.current
if step == '2':
hotel_registration_formset = HotelRegistFormSet(self.steps.current, self.steps.files, prefix="step2")
return form
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kwargs):
self.instance.save()
return HttpResponseRedirect('/register/confirmation')
class ConfirmationView(TemplateView):
template_name = 'wizard/confirmation.html'
Template
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
{% endif %}
<input type="submit" value="{% trans "submit" %}"/>
</form>
{% endblock %}

What to do in the forms.py?
Override the get_form_instance method of your SessionWizardView. This is the method the FormWizard uses to determine if a model instance is used w/ a model form
WizardView.get_form_instance(step)
This method will be called only if a ModelForm is used as the form for step step.
Returns an Model object which will be passed as the instance argument when instantiating the ModelForm for step step. If no instance object was provided while initializing the form wizard, None will be returned.
This can be done conditionally per step within the SessionWizardView implementation. I don't understand what you're trying to do well enough to give you an exact example, so here's a more generic example.
def get_form_instance(self, step):
if step == u'3':
past_data = self.get_cleaned_data_for_step(u'2')
hotel_name = past_data['hotel_field']
hotel = Hotel.objects.get(name=hotel_name)
return hotel #do NOT set self.instance, just return the model instance you want
return self.instance_dict.get(step, None) # the default implementation
What if I need a ModelFormSet only on certain steps of the form, not in all of them?
See above; use the 'if step == (form/step name)' expression to determine what happens at each step.
What do I need to do in the views.py and templates?
Using a ModelForm and passing it a model object instance will set the initial form values. Do you need more?
Hopefully this shows you the structure expected within the FormWizard. More than any other part of Django I have used, FormWizard requires a very specific structure and is a monolithic class.

Related

Django Select2MultipleWidget leads to form continuously trying to load

I'm trying to incorporate Select2 into my django form -- specifically ModelSelect2MultipleWidget -- so that the user can associate multiple event objects to another model (like CheckboxSelectMultiple). The associated models are:
from django.contrib.auth import get_user_model
from django.db import models
class F4Events(models.Model):
name = models.CharField(max_length=300)
handling_type = models.ManyToManyField(WaferHandlingType)
def __str__(self):
return self.name
class ToolPm(models.Model):
ceid = models.ForeignKey(CEID, on_delete=models.CASCADE)
name = models.CharField(max_length=250)
wafer_handling = models.BooleanField(default=True)
wafer_based = models.BooleanField(default=False)
frequency = models.FloatField(blank=True, null=True)
handling_type = models.ManyToManyField(WaferHandlingType, blank=True)
user_edited = models.BooleanField(default=False)
pm_f4_events = models.ManyToManyField('F4Events', blank=True)
def __str__(self):
return str(self.name) if self.name else ''
class Meta:
ordering = ('ceid', 'name')
My forms.py file is:
from django import forms
from .models import Entity, ToolPm, CEID, PDL, F4Events, WaferHandlingType
from django_select2 import forms as s2forms
class F4EventWidget(s2forms.ModelSelect2MultipleWidget):
search_fields = [
'name__icontains',
]
attrs = {'data-placeholder': 'Please Choose F4 Events'}
class PMForm(forms.ModelForm):
class Meta:
model = ToolPm
fields = ['wafer_handling', 'wafer_based', 'handling_type', 'pm_f4_events']
widgets = {'handling_type': forms.CheckboxSelectMultiple,
'pm_f4_events': F4EventWidget
}
my view.py is:
from datetime import datetime, timedelta
from django.db.models import Sum
from django.http import JsonResponse, HttpResponseRedirect
from django.http.response import HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.views import View
from django.views.generic import ListView
from django_filters.views import FilterView
from pages.filter import CeidFilter, PmRunDateFilter
from tools.forms import EntityForm, PMForm, CEIDForm, PDLAddForm
from tools.models import CEID, Entity, F4Events, ToolPm, WaferHandlingType, PmRunDate
from django.urls import reverse
class UserCeidPMUpdate(View):
template_name = 'tools/update_pm_attributes.html'
def get(self, request, username, pk, pm_id):
pm = ToolPm.objects.get(pk=pm_id)
form = PMForm(request.GET or None, instance=pm)
return render(request, self.template_name, {'form': form, 'pm': pm, })
def post(self, request, username, pk, pm_id):
pm = ToolPm.objects.get(pk=pm_id)
pm.user_edited = True
pm.save()
form = PMForm(request.POST or None, instance=pm)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('pm_view', kwargs={'username': username, 'pk': pm.ceid.id}))
return render(request, self.template_name, {'form': form, 'pm': pm, })
lastly, my .html file is:
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<form method="post">
<p>Process: {{ pm.ceid.process }}</p>
<p>CEID: {{ pm.ceid.ceid }}</p>
<p>Name: {{ pm.name }}</p>
<p>Frequency: {{ pm.frequency }}{% if pm.wafer_based %} wafers {% else %} days {% endif %}</p>
<p>Score: {{ pm.score }}</p>
{% csrf_token %}
{{ form }}
<input type="submit" class="btn btn-success" value="Update PM"><button type="button" class="btn btn-primary">Go Back</button>
</form>
{% endblock content %}
If I switch the pm_f4_events widget to a CheckboxSelectMultiple or a ModelSelect2Widget, the code works.
However, when I try to use a ModelSelect2MultipleWidget, the form continually tries to load but cannot and eventually just times out.
There are ~5000 items within the F4Events model, so that may have something to do with it. Any help to point me in the right direction would be greatly appreciated!
You can empty the pm_f4_events field's choices in the __init__ method:
class PMForm(forms.ModelForm):
class Meta:
model = ToolPm
fields = ['wafer_handling', 'wafer_based', 'handling_type', 'pm_f4_events']
widgets = {
'handling_type': forms.CheckboxSelectMultiple,
'pm_f4_events': s2forms.ModelSelect2MultipleWidget(
model=F4Events,
search_fields=["name__icontains",],
attrs = {'data-placeholder': 'Please Choose F4 Events'}
)
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
choices_F4Events = []
pm_f4_events_field = self.fields["pm_f4_events"]
# selected values
selected_F4Events = self.data.getlist("pm_f4_events") or self.initial.get("pm_f4_events")
if selected_F4Events:
# check of selected values not object
if not isinstance(selected_F4Events[0], F4Events):
selected_F4Events = pm_f4_events_field.queryset.filter(pk__in=selected_F4Events)
choices_F4Events = [(item.pk, item.name) for item in selected_F4Events]
# assign selected values or empty list
pm_f4_events_field.choices = choices_F4Events
Has been tested on my server, works well.

Error : Select a valid choice. That choice is not one of the available choices

I am creating a blog using Django. I want my author to be auto-selected in the author's box. Actually the box is getting correctly filled by javascript but when I submit the form it shows "Select a valid choice. That choice is not one of the available choices." But I have provided it a TextInput Field. It works well when instead of TextInput, Select is provided to the author. But I don't want to let the author select I want it to get filled by the first_name who is logged in.
I want to update my choice list automatically when a new category is added on the form without shutting the whole server down and then run it again.
Forms.py
from django import forms
from .models import Post, Category
from django.contrib.auth.models import User
# choices = [('coding','coding'),('entertainment','entertainment'),('sports','ssports')]
choices = Category.objects.all().values_list('name','name')
choice_list = []
for item in choices:
choice_list.append(item)
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'title_tag','author','category', 'body', 'snippet')
widgets = {
'title': forms.TextInput(attrs={'class':'form-control'}),
'title_tag': forms.TextInput(attrs={'class':'form-control'}),
'author': forms.TextInput(attrs={'class':'form-control',
'id':'gauname','value':'','type':'hidden'}),
# 'author': forms.Select(attrs={'class':'form-control'}),
'category': forms.Select(choices=choice_list,attrs={'class':'form-control'}),
'body': forms.Textarea(attrs={'class':'form-control'}),
'snippet': forms.Textarea(attrs={'class':'form-control'}),
}
views.py
class AddCategoryView(CreateView):
model = Category
form_class = AddCategory
template_name = 'add_category.html'
# fields = '__all__'
models.py
class Post(models.Model):
title = models.CharField(max_length=255)
title_tag = models.CharField(max_length=255)
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
# body = models.TextField()
body = RichTextField(blank=True, null=True)
post_date = models.DateTimeField(default=datetime.now)
category = models.CharField(max_length=255, default='coding')
snippet = models.CharField(max_length=255)
likes = models.ManyToManyField(User, related_name='blog_posts')
def total_likes(self):
return self.likes.count()
def __str__(self):
return self.title + '|' + str(self.author)
def get_absolute_url(self):
# return reverse('article-detail', args=(str(self.id)))
return reverse('home')
HTML
{% block content %}
{% if user.is_authenticated %}
<h1>Add Post...</h1>
<br><br><br>
<form method="POST">
<div class="form-group">
{% csrf_token %}
{{form.media}}
{{form.as_p}}
<!-- <input class = form-control type="text" id="gauname" name="author" value=""> -->
<button class="btn btn-secondary" name="button">Post</button>
</div>
</form>
<script>
var name = "{{ request.user.first_name }}";
document.getElementById("gauname").value = name;
</script>
{% else %}
You are not allowed here!
{% endif %}
{% end block %}
UPDATE
forms.py
class AddCategory(forms.ModelForm):
class Meta:
model = Category
fields = ('name',)
widgets = {
'name': forms.TextInput(attrs={'class':'form-control'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["name"] = forms.ModelChoiceField(
queryset=Category.objects.all(),
)
I want to update my choice list automatically when a new category is added on the form without shutting the whole server down and then run it again.
If there are two people with the same first name, how is Django supposed to know who is the user here? In other words, how can Django map the first name to a user? It doesn't make sense
Since it's a hidden field anyway, instead of request.user.first_name you can just pass request.user.pk instead (The Unique ID of that particular user)

Django forms with variable user entries

I want to create a django form that captures user entry such as name, address, age. For this type of information I can create a model such as
class GeneralUserInfo(models.Model):
firstname = models.CharField()
lastname = models.CharField()
address = models.CharField()
# etc....
However, I also want to capture maybe some information like their class schedule or family information.
class UserSchedule(models.Model):
course_number = model.IntegerField()
course_name = model.CharField()
# etc....
class FamilyInfo(models.Model):
family_member_type = models.CharField(choices = MEMBER_CHOICES) # mother, father, sibling
family_member_name = models.CharField() # jon doe
# etc....
where by each user, the number of courses and number of family members could vary.
I would like the form to look something like below
with a simple submit button to send things off to be saved.
My question is, how should I structure the form template considering there are multiple models?
The answer to the above question can take a couple of forms. I'll rewrite the example above to add more context.
Say there's a student, Ashley, with college course work (say she's taking 4 course) and with family (mom, pop, sis, bro). I'd like to capture all this information for Ashley. So I've written the following models.py
class Student(models.Model):
firstname = models.CharField()
lastname = models.CharField()
address = models.CharField()
# etc....
class Course(models.Model):
user = models.ForeignKey(GeneralUserInfo, on_delete = models.CASCADE)
course_number = model.IntegerField()
course_name = model.CharField()
# etc....
class Family(models.Model):
user = models.ForeignKey(GeneralUserInfo, on_delete = models.CASCADE)
family_member_type = models.CharField(choices = MEMBER_CHOICES) # mother, father, sibling
family_member_name = models.CharField() # jon doe
# etc....
Next, what is needed is to use inlineformset_factory inside your views.
You could potentially have two views in views.py. One that creates the student, and another that edits the information belonging to that student.
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .forms import StudentForm
from django.forms import inlineformset_factory
from .models import Student, Course, Family
#login_required
def createstudent(request):
context = {'studentform': StudentForm}
if request.method == "POST":
form = StudentForm(request.POST)
if form.is_valid():
instance = form.save(commit = False) # https://www.youtube.com/watch?v=2h57cqFRcqg
instance.user = request.user
instance.save()
messages.success(request, "Saved new contact!")
return redirect('home')
return render(request, 'mainapp/createstudent.html', context)
#login_required
def editview(request, id):
student = Student.objects.get(pk = id)
CourseFormSet = inlineformset_factory(Student, Course, fields = ('name', 'skill'), extra=5, max_num=5)
FamilyFormSet = inlineformset_factory(Student, Family, fields = ('name', 'skill'), extra=5, max_num=5)
if request.method == "POST":
courseformset = CourseFormSet(request.POST, instance = contact)
Familyform = FamilyFormSet(request.POST, instance = contact)
if courseformset.is_valid():
courseformset.save()
if Familyform.is_valid():
Familyform.save()
if courseformset.is_valid() or Familyform.is_valid():
messages.success(request, "Saved new information")
return redirect('editview', id=id)
courseformset = CourseFormSet(instance = contact)
Familyform = FamilyFormSet(instance = contact)
context = {'courseformset': courseformset, 'title': 'Edit View', 'Familyform': Familyform}
return render(request, 'mainapp/editview.html', context)
Then inside an edit view template, editview.html
{% extends "mainapp/base.html" %}
{% block content %}
<h1>{{ title }}</h1>
<br>
<h5>Edit the form below</h5>
<br>
<form method = "POST">
<br>
<h2>Courses</h2>
<br>
{% csrf_token %}
{{ courseformset.management_form }}
{% for form in courseformset %}
<article class="media content-section">
<div class="media-body">
{{ form.as_p }}
</div>
</article>
{% endfor %}
<br>
<h2>Family</h2>
<br>
{{ Familyform.management_form }}
{% for form in Familyform %}
<article class="media content-section">
<div class="media-body">
{{ form.as_p }}
</div>
</article>
{% endfor %}
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Submit</button>
</div>
</form>
{% endblock content %}
This is just an example. However, for a whole project example, please see: https://github.com/Johnnyboycurtis/onlineforms-project/

Custom Formset :- Assigning foreign key value and inputting a field only once [ Logged in User ]

I have a modelformset to populate the timetable model.
Models
class Timetable(models.Model):
day = models.ForeignKey('Day',on_delete=models.CASCADE)
start = models.IntegerField()
end = models.IntegerField()
period = models.CharField(max_length=12)
classteacher = models.ForeignKey('Class_teacher',on_delete=models.SET_NULL)
class Class_teacher(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
empid = models.CharField(max_length=10)
email = models.CharField(max_length=30)
Views
class Timetableadding(CreateView):
model = Timetable
success_url = '/dashboard'
form_class = Timetableform
template_name = 'newtest.html'
def get_context_data(self, **kwargs):
context = super(Timetableadding, self).get_context_data(**kwargs)
context['formset'] = TimetableFormSet(queryset=Timetable.objects.none())
return context
def post(self, request, *args, **kwargs):
formset = TimetableFormSet(request.POST)
if formset.is_valid():
return self.form_valid(formset)
def form_valid(self, formset):
formset.classteacher = get_object_or_404(Class_teacher, email=self.request.user.email)
formset.save()
# return super().form_valid(formset)
return HttpResponseRedirect('/dashboard')
Forms
class Timetableform(ModelForm):
class Meta:
model = Timetable
fields = ( 'start', 'end', 'period')
TimetableFormSet = modelformset_factory(Timetable, fields=('start', 'end', 'period'),extra=8,)
Template
<form class="form-material m-t-40 floating-labels" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form }}<br><br>
{% endfor %}
<div class="form-group row">
<button type="submit" class="btn waves-effect waves-light btn-rounded btn-success">
Submit
</button>
</div>
</form>
While populating the Timetableform using the createview view the fields start end period in Timetable model is done like a general form.
Requirements
The webapp has a login feature . When the user ( classteacher ) login they can add timetable. What I want is classteacher field in Timetable(model Form ) should be automatically set as user which is the classteacher. ( Classteacher ) and should be saved in the db after creating the timetable. Classteacher model is updated with respective required fields .
I tried passing classteacher to formset , but it was execute as I need
I know how to do within a normal form , But I have not done this in a formset.
The day field in the Timetable should be selected only once , so there will be 8 forms to supply start end and period but there should be only one form to supply day. I succeeded in dealing 8 form for start end and period but unaware about the day.
There are better ways to do this:
If you use an inlineformset_factory you can pass the teacher instance to which the form set belongs to directly when initializing the formset. See the example here.
Or you can loop through the forms in order to modify the instances before they are saved:
instances = formset.save(commit=False)
for instance in instances:
instance.classteacher = ...
instance.save()
If you want the user to submit an extra field that is common to all instances, you can add another form to your view:
class DayForm(forms.Form):
day = ModelChoiceField(queryset=Day.objects.all())
def get_context_data(self, **kwargs):
...
context['day_form'] = DayForm()
return context
def post(self, *args, **kwargs):
...
day_form = DayForm(data=request.POST)
if formset.is_valid() and day_form.is_valid():
return self.form_valid(formset, day_form)
def form_valid(self, formset, day_form):
day = day_form.cleaned_data['day']
instances = formset.save(commit=False)
for instance in instances:
instance.day = day
instance.teacher = ...
instance.save()
return HttpResponseRedirect(...)
Template to render
<form class="form-material m-t-40 floating-labels" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ day_form }} <br>
{{ formset.management_form }}
{% for form in formset %}
{{ form }}<br><br>
{% endfor %}
<div class="form-group row">
<button type="submit" class="btn waves-effect waves-light btn-rounded btn-success">
Submit
</button>
</div>
</form>
In your CreateView you could override get_initial method:
def get_initial(self):
self.initial = CreateView.get_initial(self)
self.initial["classteacher"] = self.request.user
return self.initial.copy()
Then you need to add this field to your form
class Timetableform(ModelForm):
class Meta:
model = Timetable
fields = ( 'start', 'end', 'period', “classteacher”)
if you dont want to show this field in your form and still add classteacher as a current user you could use Hiddeninput widget with this field:
class Timetableform(ModelForm):
class Meta:
model = Timetable
fields = ( 'start', 'end', 'period', “classteacher”)
widgets = {"classteacher": HiddenInput}
in the formset you could use hiddeninput widget as well or use form=Timetableform argument in the constructor. Same idea

How to use django formsets to do a questionnaire

I'm doing an application and now I need to make an evaluation that users can take, my problem is that I want to use a formset to list the questions with respective choices, I know this can be done with using formsets but not the way to get it done. Following is my code:
# models.py
class Evaluation(models.Model):
"""
An evaluation is for a session.
Each session should have an evaluation
"""
session = models.OneToOneField(
Session,
related_name='evaluation',
verbose_name=u'Sesión'
)
class Meta:
verbose_name = u'Evaluación'
verbose_name_plural = u'Evaluaciones'
def __unicode__(self):
return u'Evaluación de la sesion {0}'.format(
self.session.name
)
class Question(models.Model):
"""
A question inside of an evaluation
"""
evaluation = models.ForeignKey(
Evaluation,
verbose_name=u'Evaluación',
related_name='questions'
)
question_type = models.CharField(
max_length=20,
verbose_name=u'Tipo de respuesta',
choices=QUESTION_TYPES
)
sentence = models.CharField(
max_length=255,
verbose_name=u'Pregunta'
)
position = models.IntegerField(
default=0,
verbose_name=u'Posición'
)
class Meta:
verbose_name = u'Pregunta'
verbose_name_plural = u'Preguntas'
ordering = ['position', 'sentence']
def __unicode__(self):
return self.sentence
class Choice(models.Model):
question = models.ForeignKey(
Question,
related_name='choices',
verbose_name=u'Pregunta'
)
sentence = models.CharField(
max_length=255,
verbose_name=u'Posible respuesta'
)
position = models.IntegerField(
default=0,
verbose_name=u'Posición'
)
class Meta:
verbose_name = u'Posible respuesta'
verbose_name_plural = u'Posibles respuestas'
ordering = ['position', 'sentence']
def __unicode__(self):
return self.sentence
----
# forms.py
from django.forms.models import inlineformset_factory
from models import Evaluation, Question
AnswerFormSet = inlineformset_factory(
Evaluation, Question, exclude=('question',),
extra=0, can_delete=False
)
----
# views.py
#login_required()
def session_evaluation(request, course_slug, session_position):
data = {}
course = get_object_or_404(Course, slug=course_slug)
session = Session.objects.filter(course=course).get(position=session_position)
evaluation = get_object_or_404(Evaluation, session=session)
if request.method == 'POST':
formset = AnswerFormSet(request.POST, instance=evaluation)
if formset.is_valid():
formset.save()
print 'formset valid...'
else:
formset = AnswerFormSet(instance=evaluation)
data['course'] = course
data['session'] = session
data['formset'] = formset
return render(request, 'courses/session_evaluation.html', data)
----
# template.html
<form id="evaluation" method="post" role="form">
{% csrf_token %}
{{ formset.management_form }}
<ul class="evaluation">
{% for form in formset.forms %}
<li>
{{ form.instance }}
{{ form.instance.choices.all }}
</li>
{% endfor %}
</ul>
<div class="form-group clearfix nmb">
<button type="submit" class="btn btn-primary pull-right">Enviar respuestas</button>
</div>
</form>
As you can see I have the models well written but from the forms all the code is only an experiment and I really don't know the way to do it.
What I'm getting in the template with this code is the question sentence and the list of choices, something like:
[question sentence here]? [<Choice: [choice #1 here]>, <Choice: [choice #2 here]>]
What is the clean and easy way to do it with formsets?
What I want to get is a list of questions with the respective list of choices with radio buttons because this is an evaluation.
Should not it be {{ form.as_p }} or {{ form.as_table }}? The instance is already created in the view, so need not be used in the template.

Categories

Resources