Display form in Django not based on Models/Form - python

My view calls some backend classes which need some user input. When user input is required, i halt processing and store the questions into the session - request.session['questions']. request.session['questions'] is a list of dictionaries. e.g.
request.session['question'] = []
request.session['question'].append({'question' : 'Whats your firstname', 'answer' : ''})
request.session['question'].append({'question' : 'Whats your firstname', 'answer' : ''})
I need to display these questions to the user along with an input box for each question. When the user submits the form, I need to dump the input into the answers part of the session variable. Could someone show me how to do this? I'm a little lost as this isn't really based on Django forms or models as such.
Thanks

You could use forms that aren't associated with models, like this:
class QuestionForm(forms.Form):
answer = forms.CharField()
def questions(request):
if request.method == 'POST':
form = QuestionForm(request.POST)
if form.is_valid():
# Process the data in form.cleaned_data
return HttpResponseRedirect('/done/')
else:
form = QuestionForm() # An unbound form
return render_to_response('questions.html', {'form': form,})
More documentation here.

Related

How to validate a formset in dajngo

I am using formset to input my data into the database but for some reason it just doesn't validate, whenever I test in the terminal and call the .is_valid() It just returns false no matter what I try. Here's the code in my views.py and forms.py . Any help will be much appreciated!
# Advanced Subjects (Advanced Biology)
def form_5_entry_biology_view(self, request):
current_teacher = User.objects.get(email=request.user.email)
logged_school = current_teacher.school_number
students_involved = User.objects.get(school_number=logged_school).teacher.all()
data = {"student_name": students_involved}
formset_data = AdvancedStudents.objects.filter(class_studying="Form V", combination="PCB")
student_formset = formset_factory(AdvancedBiologyForm, extra=0)
initial = []
for element in formset_data:
initial.append({"student_name": element})
formset = student_formset(request.POST or None, initial=initial)
print(formset.is_valid())
context = {
"students": students_involved,
"formset": formset,
"class_of_students": "Form V",
"subject_name": "Advanced Biology",
}
return render(request, "analyzer/marks_entry/marks_entry_page.html", context)
And here is my forms.py
class AdvancedBiologyForm(forms.ModelForm):
student_name = forms.CharField()
class Meta:
model = ResultsALevel
fields = ('student_name', 'advanced_biology_1', 'advanced_biology_2',
'advanced_biology_3',)
Before using request.POST and is_valid() you probably want to check if there actually is a post request or if the page is just viewed:
def form_5_entry_biology_view(self, request):
current_teacher = User.objects.get(email=request.user.email)
logged_school = current_teacher.school_number
students_involved = User.objects.get(school_number=logged_school).teacher.all()
data = {"student_name": students_involved}
formset_data = AdvancedStudents.objects.filter(class_studying="Form V", combination="PCB")
# Here you are creating the formset using the model
student_formset = formset_factory(AdvancedBiologyForm, extra=0)
# Here you are generating your initial data
initial = []
for element in formset_data:
initial.append({"student_name": element})
# Here you are using the initial data to create pre-populated
# forms with it using the formset.
# These forms will be displayed when the page loads.
formset = student_formset(initial=initial)
context = {
"students": students_involved,
"formset": formset,
"class_of_students": "Form V",
"subject_name": "Advanced Biology",
}
# But if the user hits the "submit"-Button...
if request.method == 'POST':
# ... you don't want to have the formset with your
# initial data. Instead you want the entries by the user
# which are transmitted in request.POST to populate the
# formset forms.
formset = student_formset(request.POST or None)
# Now you can validate the formset with the fields which
# got input the the user; not the "initial" data like in
# your original code
if formset.is_valid():
# This runs formset validation.
# You can define your own formset validations like
# you would for models/forms.
for form in formset:
# And / Alternatively:
# you could in theory also add another "if form.is_valid():" in here
# This would trigger any validation on the
# model/form; not the validators on the formset.
form.save()
return HttpResponseRedirect(...
return render(request, "analyzer/marks_entry/marks_entry_page.html", context)
Otherwise you might call is_valid() on an unbound form.
From Django docs:
If the form is submitted using a POST request, the view will once again create a form instance and populate it with data from the request: form = NameForm(request.POST) This is called “binding data to the form” (it is now a bound form).
Basically if a form is empty it's unbound, and if it's populated with data it gets bound after POST. When you open a page and immediately try "is_valid()" it will basically always be false as you are checking if an empty form is valid; which it likely never is.
To point out the error:
formset = student_formset(request.POST or None, initial=initial)
print(formset.is_valid())
This is not valid. Because initial values are not equal to populating a form field with "real" values. So what happens is that it tries to populate the fields in the form with request.POST or None.
But you don't have a if request.method == 'POST': condition. So your code will just run through before hitting the last line of code which is the return statement displaying the page.
This means that your code validates request.POST or None before the user even saw the page. So there is no way a user could have already entered data and hit submit. Which means there is no POST-request, so it always turns None. So you are basically calling is_valid() on a form that has no field values in it which leads to the validation failing.
EDIT 1: I just noticed in your forms.py you have written:
fields = ('student_name', 'advanced_biology_1', 'advanced_biology_2',
'advanced_biology_3',)
This should be a list instead:
fields = ['student_name', 'advanced_biology_1', 'advanced_biology_2',
'advanced_biology_3',]
EDIT 2: fixed wrong variable name
EDIT 3: added extensive comments to clarify what's happening in the code
EDIT 4: pointed out cause of problem more distinctly.

using wildcard on flask-wtf form fields in flask

I have been working on building a questionnaire using flask-wtf. my questions has 30 questions and
will likely grow in the future.
From reading the flask-wtf documentation. I see that to the inputted data from a form is accessible like so "form.username.data".
username = StringField('Username')
#app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate():
user = User(form.username.data)
db_session.add(user)
return redirect(url_for('login'))
return render_template('register.html', form=form)
My question:
my qusetionair form has many fields and would be illogical to have to enter form.FIELDNAME.data to get the inputted data for every field. i understand for a user registration form that it would be acceptable as there are only about 3-5 fields. However in my scenario (questionair form) this is not ideal
Solution i tried:
I named my fields question_1 question_2 qusetion_3 , then created a while look like below to dynamically enter the field name. however, flask-wtf complains "AttributeError: 'MyForm' object has no attribute 'question'"
if form.validate_on_submit():
string = 'q'
for i in range(30):
question = "q"+str(i)
field_name = form.question.data
answer_to_add = Answer(answers=field_name)
db.session.add(answer_to_add)
db.session.commit()
For people in the future who run into a similiar issue. The resolution is to simply use "form.data" and not specify a field as this will mean all fields are sent.
Code example:
if form.validate_on_submit():
answer = form.data
"do something"
You can find more useful info in the docs: https://wtforms.readthedocs.io/en/2.3.x/forms/?highlight=form#wtforms.form.Form.data

Django Edit Form Secure Way

I have edit form (modelform) and for that form I created a url something like /contacts/edit//
My view is method base view and I am passing the contact_id from my template to the view as parameter.
Then that's what I am doing:
def edit_contact(request, contact_id):
is_user_authenticated(request.user.is_authenticated)
contact_person = get_object_or_404(domain.Contact.contacts, pk=contact_id)
if request.method == 'POST':
form = forms.ContactPersonForm(request.POST, instance=contact_person)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contacts/')
However that url can be reached by any user with just giving ID and basically you can edit any person you want.
I could do one more query to DB in order to find if a person is the right person to edit contact information but is that right way to do that? What's best practice for my situation?

How to check if Django form contains any data?

I have Django function view with two forms inside. What is the best solution to detect which form has data typed by user? I know how form.is_valid() works, but still I want to check firstly which form was filled with data.
My code:
def edit_profile_view(request):
if request.method == "POST":
edit_form = EditProfileForm(request.POST)
pass_form = ChangePasswordForm(request.POST)
# here I want to detect which form has data inside,
# and then save this form
else:
edit_form = EditProfileForm()
pass_form = ChangePasswordForm()
template = get_template("profiles/profile_edit.html")
variables = RequestContext(request, {'edit_form': edit_form, 'pass_form': pass_form})
output = template.render(variables)
return HttpResponse(output)
Just have two different actions/views for the forms then the form that has data is the one that the user clicked submit on.
You could, like you say, just check what the form errors actually are or make a method on your forms for is_filled_in but this all seems overkill to me.

Django edit form based on add form?

I've made a nice form, and a big complicated 'add' function for handling it. It starts like this...
def add(req):
if req.method == 'POST':
form = ArticleForm(req.POST)
if form.is_valid():
article = form.save(commit=False)
article.author = req.user
# more processing ...
Now I don't really want to duplicate all that functionality in the edit() method, so I figured edit could use the exact same template, and maybe just add an id field to the form so the add function knew what it was editing. But there's a couple problems with this
Where would I set article.id in the add func? It would have to be after form.save because that's where the article gets created, but it would never even reach that, because the form is invalid due to unique constraints (unless the user edited everything). I can just remove the is_valid check, but then form.save fails instead.
If the form actually is invalid, the field I dynamically added in the edit function isn't preserved.
So how do I deal with this?
If you are extending your form from a ModelForm, use the instance keyword argument. Here we pass either an existing instance or a new one, depending on whether we're editing or adding an existing article. In both cases the author field is set on the instance, so commit=False is not required. Note also that I'm assuming only the author may edit their own articles, hence the HttpResponseForbidden response.
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render, reverse
#login_required
def edit(request, id=None, template_name='article_edit_template.html'):
if id:
article = get_object_or_404(Article, pk=id)
if article.author != request.user:
return HttpResponseForbidden()
else:
article = Article(author=request.user)
form = ArticleForm(request.POST or None, instance=article)
if request.POST and form.is_valid():
form.save()
# Save was successful, so redirect to another page
redirect_url = reverse(article_save_success)
return redirect(redirect_url)
return render(request, template_name, {
'form': form
})
And in your urls.py:
(r'^article/new/$', views.edit, {}, 'article_new'),
(r'^article/edit/(?P<id>\d+)/$', views.edit, {}, 'article_edit'),
The same edit view is used for both adds and edits, but only the edit url pattern passes an id to the view. To make this work well with your form you'll need to omit the author field from the form:
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
exclude = ('author',)
You can have hidden ID field in form and for edit form it will be passed with the form for add form you can set it in req.POST e.g.
formData = req.POST.copy()
formData['id'] = getNewID()
and pass that formData to form

Categories

Resources