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.
Related
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)
i have 2 apps in Django Project one is Saas and seccond one is Menu.
from Saas i can get everything to template but from Menu i cannot get menu items in html navbar.
my project Tree is
Code in Html Template
{% for item in Menu_list %}
<li class="nav-item dropdown dropdown__megamenu">
<a class="nav-link dropdown-opener" href="#">
{{ item.title }}
</a>
<div class="custom-dropdown-menu custom-megamenu">
<div class="content">
<ul class="megamenu">
menu
</ul>
</div>
</div>
</li>
{% endfor %}
Code for Views.Py
from django.shortcuts import render
from .models import Menu,MenuItem
def index(request):
Menu_list = Menu.objects.all()
MenuItem_list = MenuItem.objects.all()
return render(request, 'header-footer.html',{'Menu_list' : Menu_list,
'MenuItem_list': MenuItem_list,}
)
this is my models in admin panel everything works fine and i cant add menu and sub menu item just cannot get it from database.or in django can i geT fro database without views?
My models
from django.db import models
class Menu(models.Model):
name = models.CharField(
(u'Name'),
max_length=100
)
slug = models.SlugField(
(u'Slug')
)
base_url = models.CharField(
(u'Base URL'),
max_length=100,
blank=True,
null=True
)
description = models.TextField(
(u'Description'),
blank=True,
null=True
)
class Meta:
verbose_name = (u'menu')
verbose_name_plural = (u'menus')
def __unicode__(self):
return u"%s" % self.name
def __str__(self):
return self.__unicode__()
def save(self, *args, **kwargs):
"""
Re-order all items from 10 upwards, at intervals of 10.
This makes it easy to insert new items in the middle of
existing items without having to manually shuffle
them all around.
"""
super(Menu, self).save(*args, **kwargs)
current = 10
for item in MenuItem.objects.filter(menu=self).order_by('order'):
item.order = current
item.save()
current += 10
class MenuItem(models.Model):
menu = models.ForeignKey(
Menu,
verbose_name=(u'Name'),
on_delete=models.CASCADE,
)
order = models.IntegerField(
(u'Order'),
default=500
)
link_url = models.CharField(
(u'Link URL'),
max_length=100,
help_text=(u'URL or URI to the content, eg /about/ or http://test.com/')
)
title = models.CharField(
(u'Title'),
max_length=100
)
login_required = models.BooleanField(
(u'Login required'),
blank=True,
default=False,
help_text=(u'Should this item only be shown to authenticated users?')
)
anonymous_only = models.BooleanField(
(u'Anonymous only'),
blank=True,
default=False,
help_text=(u'Should this item only be shown to non-logged-in users?')
)
class Meta:
verbose_name = (u'menu item')
verbose_name_plural = (u'menu items')
def __unicode__(self):
return u"%s %s. %s" % (self.menu.slug, self.order, self.title)
You can list all MenuItem using the reverse relationship:
{% for item in menu_list %}
<li>
<a href="#">
{{ item.title }}
</a>
<ul>
{% for subitem in item.menuitem_set.all %}
<li>
<a href="">
{{ subitem.title }}
</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
Bear in mind this will introduce a new query for every menu you have, so you should use prefetch_related in your QuerySet. You should also use related_name in your models to make the reverse relationship more readable:
class MenuItem(models.Model):
menu = models.ForeignKey(Menu, related_name="items", ...)
def index(request):
menu_list = Menu.objects.prefetch_related("items").all()
return render(request, 'header-footer.html',{'menu_list' : menu_list})
You are derived Menu_list using Menu.objects.all(). And in menu model there are no any type of title field which are you want to use in template as item.title. So, it will give you nothing because there are no any type pf 'title'
field in Menu list.
I have my models.py
class Schedule(models.Model):
name = models.CharField(max_length=255)
date_from = models.DateField('')
date_to = models.DateField('', null=True)
desc = models.TextField(blank=True, null=True)
here my views.py
class Schedule(CreateView):
fields = ()
model = models.Schedule
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
return super(ModelFormMixin, self).form_valid(form)
and here my template.html
{{form.as_p}}
this form only can do 1 time input. however I need to perform 3 times input in single form with different name & date (in my case).
and form maybe look like
{{form.as_p}}
{{form.as_p}}
{{form.as_p}}
I check on documentation theres bulk_create can do multiple input in single run but i dont have idea how to deal with my template.html
A demo for you:
views.py
from django import forms
from django.shortcuts import render, HttpResponse
from .models import Schedule
class ScheduleForm(forms.ModelForm):
class Meta:
model = Schedule
fields = "__all__"
def multicreate(request):
if request.method == "POST":
forms = [
ScheduleForm(dict(name=n, date_from=df, date_to=dt, desc=ds))
for n, df, dt, ds in zip(
request.POST.getlist("name"),
request.POST.getlist("date_from"),
request.POST.getlist("date_to"),
request.POST.getlist("desc"),
)
]
if all(forms[i].is_valid() for i in range(len(forms))):
for form in forms:
form.save()
return HttpResponse(
f"success to create {len(forms)} Schedule instances."
)
else:
forms = [ScheduleForm() for _ in range(3)]
return render(request, "create.html", {"forms": forms})
models.py
from datetime import date
from django.db import models
class Schedule(models.Model):
name = models.CharField(max_length=255)
date_from = models.DateField("date from", default=date.today)
date_to = models.DateField("date to", default=date.today)
desc = models.TextField(blank=True, null=True)
def __str__(self):
return self.name or self.__class__.__name__
template
<form method='post'>{% csrf_token %}
{% for form in forms %}
{{ form.Meta.model }} {{ forloop.counter }}<br>
{{ form.as_p }}
-------------------------------------------<br>
{% endfor %}
<input type='submit', value='OK'>
</form>
My model looks like this:
class ComicSeries(models.Model):
"""Model definition for ComicSeries."""
# TODO: Define fields here
user = models.ForeignKey(User, on_delete=models.CASCADE,
null=True, blank=True, verbose_name='Uploaded by: '
)
title = models.CharField(verbose_name='Series Title', max_length=500)
cover = models.ImageField(verbose_name='Series cover', upload_to='comic_series',
height_field=None, width_field=None, max_length=None
)
description = models.TextField(verbose_name='Description')
artist = models.CharField(verbose_name='Artist(s)', max_length=500)
date_uploaded = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(default='')
class ComicIssue(models.Model):
"""Model definition for ComicIssue."""
# TODO: Define fields here
user = models.ForeignKey(User, on_delete=models.CASCADE,
null=True, blank=True, verbose_name='Uploaded by: '
)
title = models.ForeignKey(ComicSeries, on_delete=models.CASCADE, verbose_name='Series Title')
issue = models.CharField(verbose_name='Issue Number', max_length=500)
issue_title = models.CharField(verbose_name='Issue Title', max_length=1000)
issue_cover = models.ImageField(verbose_name='Issue cover', upload_to='comic_issues', height_field=None, width_field=None, max_length=None)
issue_description = models.TextField(verbose_name='Description')
issue_file = models.FileField(verbose_name='Issue file', upload_to='comic_issues_files', max_length=100,
help_text='File in pdf or as single image'
)
is_favorite = models.BooleanField(default=False)
issue_slug = models.SlugField(default='')
views.py :
class ComicIssueCreate(LoginRequiredMixin, CreateView):
model = ComicIssue
fields = ['issue_title', 'issue_cover', 'issue_description', 'issue_cover', 'issue_file']
def form_valid(self, form):
form.instance.user = self.request.user
return super(ComicIssueCreate, self).form_valid(form)
I am able to select to which ComicSeries a ComicIssue belongs to in Django admin.
In django admin there is an option to upload
But on my form, there is no field when I add 'title'
Template:
{% block body %}
<div class="container">
<h2>Add new comic issue/chapter</h2>
<form class="form", action="", method="POST", enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="form-group form">
<span class="text-danger small">
{{field.errors}}
</span>
</div>
<label class="control-label col-sm-2">
{{field.label_tag}}
{{field.help_text}}
</label>
<div class="col-sm-10">{{field}}</div>
{% endfor %}
<button type="submit" class="btn grey-text black">Add</button>
</form>
</div>
{% endblock body %}
But I have a problem doing this in a custom form. Is there a way I can determine to which series an issue belongs to in a custom form using CreateView?
You missed 'title' in the fields specified in the view. Since title is the foreign key of ComicIssue to ComicSeries, You need to include that in the fields to achieve whats required
class ComicIssueCreate(LoginRequiredMixin, CreateView):
model = ComicIssue
fields = ['title', 'issue_title', 'issue_cover', 'issue_description',
'issue_cover', 'issue_file']
def form_valid(self, form):
form.instance.user = self.request.user
return super(ComicIssueCreate, self).form_valid(form)
Updates:
The problem was due to not initializing the 'select' in MaterializeCSS. It is necessary to select field to work in MaterializeCSS
<script>
$(document).ready(function() {
$('select').material_select();
});
</script>
The fields in ComicIssueCreate should include title
So fields = ['title','issue_title', 'issue_cover', 'issue_description', 'issue_cover', 'issue_file']
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.