Django survey with forms - python

I am building a survey tool and I'm wondering how would I continue with this or if my current solution is even the proper way to do it.
Admin of the page may add or remove questions from questionnaires, so if I have understood it, I can't use ModelForms to handle the form data?
A form may consist of 5 multichoice questions and 2 free text questions or any other amount of different types of questions so there isn't any fixed type of questionnaire
How do I then save the values of the form as I do not have a model to use?
Is this even possible to achieve without using a model for the form?
Thank you for any input in advance.
views.py
from django.shortcuts import render
from .models import Questionnaire, Question, Answer
def index(request):
all_questionnaires = Questionnaire.objects.all()
all_questions = Question.objects.all()
return render(request, 'questions/index.html', locals())
def questionnaire_detail(request, questionnaire_id):
questionnaire = Questionnaire.objects.get(id=questionnaire_id)
questions = Question.objects.filter(questionnaire=questionnaire)
return render(request, 'questions/questionnaire.html',
{'questionnaire': questionnaire, 'questions': questions})
def calculate(request):
if request.method == 'POST':
pass
models.py
from django.db import models
MC = 'MC'
CN = 'CN'
TX = 'TX'
CATEGORY = (
(MC, 'Multichoice'),
(CN, 'Choose N'),
(TX, 'Text'),
)
VALYK = '1'
VALKA = '2'
VALKO = '3'
VALNE = '4'
VALVI = '5'
MULTICHOICE = (
(VALYK, 'Least'),
(VALKA, 'Less than average'),
(VALKO, 'Average'),
(VALNE, 'More than average'),
(VALVI, 'Most'),
)
class Questionnaire(models.Model):
questionnaire_name = models.CharField(max_length=100,
verbose_name="Questionnaire",
null=False,
default=None,
blank=False)
def __str__(self):
return self.questionnaire_name
class Question(models.Model):
questionnaire = models.ManyToManyField(Questionnaire)
question_text = models.CharField(max_length=200,
verbose_name="Questionnaire name",
null=True,
default=None,
blank=True)
question_category = models.CharField(max_length=2,
verbose_name="Question category",
null=False,
choices=CATEGORY,
default=None,
blank=False)
def __str__(self):
return self.question_text
class Answer(models.Model):
question = models.ForeignKey(Question)
class MultiChoiceAnswer(Answer):
answer = models.IntegerField(choices=MULTICHOICE)
def __str__(self):
return self.answer
questionnaire.html
{% extends "questions/base.html" %}
{% block title_html %}
Questionnaire
{% endblock %}
{% block h1 %}
Questionnaire
{% endblock %}
{% block content %}
{% if questions|length > 0 %}
<form action="{% url "questions:calculate" %}" method="post">
{% csrf_token %}
{% for question in questions %}
{{ question.question_text }}<br>
{% if question.question_category == "MC" %}
<input type="radio" name="{{ question.id }}" value="1"> 1<br>
<input type="radio" name="{{ question.id }}" value="2"> 2<br>
<input type="radio" name="{{ question.id }}" value="3"> 3<br>
<input type="radio" name="{{ question.id }}" value="4"> 4<br>
<input type="radio" name="{{ question.id }}" value="5"> 5<br>
{% elif question.question_category == "CN" %}
<input type="checkbox" name="{{ question.id }}" value="a">a<br>
<input type="checkbox" name="{{ question.id }}" value="b">b<br>
{% elif question.question_category == "TX" %}
<textarea rows="4" cols="50" name="{{ question.id }}">Test</textarea><br>
{% endif %}
{% endfor %}
<input type="submit" value="Send" />
</form>
{% else %}
<span>No questions</span>
{% endif %}
{% endblock %}

This is the solution I ended up with. Edited it a bit to be more generic.
In the view there is checking if the form was loaded or submitted. If it was submitted, then check the validity of all of the forms. As there are multiple forms they are then made as formsets.
View
def answerpage(request, questionnaire_pk):
AnswerFormSet = formset_factory(AnswerForm, extra=0)
questions = Question.objects.filter(questionnaire=questionnaire_pk)
qname = Questionnaire.objects.get(id=questionnaire_pk)
if request.method == 'POST':
answer_formset = AnswerFormSet(request.POST)
if answer_formset.is_valid():
for answer_form in answer_formset:
if answer_form.is_valid():
instance = answer_form.save(commit=False)
instance.fieldValue = answer_form.cleaned_data.get('fieldValue')
instance.save()
return redirect('main:calcs')
else:
return redirect('main:home')
else:
quest_id = request.session.get('questionnaire_key', defaultValue)
question_data = [{'question': question,
'questionnaire_key': quest_id} for question in questions]
answer_formset = AnswerFormSet(initial=question_data)
combined = zip(questions, answer_formset)
context = {
'combined': combined,
'answer_formset': answer_formset,
'qname': qname,
}
return render(request, 'main/questionnaire.html', context)
The form is a modelform with the default widgets overwritten.
Form
class AnswerForm(forms.ModelForm):
class Meta:
model = QuestionAnswer
exclude = ['']
widgets = {
'field1': RadioSelect(choices=CHOICES, attrs={'required': 'True'}),
'field2': HiddenInput,
}
Webpage renders the values from the view in to a table. Management_form is needed to handle the formset values correctly.
Webpage
{% extends "main/base.html" %}
{% load static %}
{% block content %}
<link rel="stylesheet" href="{% static 'css/survey.css' %}">
<h1>{{ qname.questionnaire_text }}</h1>
<h2>{{ qname.description }}</h2>
<form method="post">{% csrf_token %}
{{ answer_formset.management_form }}
<table>
{% for question, form in combined %}
<tr><td style="width:65%">{{ question }}{{ question.question_type }}</td><td style="width:35%">{{ form.business_id }}{{ form.question }}{{ form.questionnaire_key }}{{ form.answer_text }}</td></tr>
{% endfor %}
</table>
<div class="buttonHolder">
<input type="submit" value="Save" id="next_button"/>
</div>
</form>
{% endblock %}

Related

display data of form 1 on form 2 django SessionWizardView

Django SessionWizardView: User fills in form 1 and then, some of the values of the fields in form one are needed to calculate a number. This number must then be displayed on form 2. I don't know how I can grab the data of form 1 and use it to make a calculation and then display it on form 2.
models.py
class CapsProd(models.Model):
production = models.OneToOneField(Productions, on_delete=models.CASCADE, primary_key=True)
amount_of_caps = models.IntegerField()
conc_per_cap = models.FloatField()
conc_per_tab = models.FloatField()
amount_of_weighed_tabs = models.IntegerField()
mass_all_tabs = models.FloatField()
required_mass_powder = models.FloatField()
weighed_mass_powder = models.FloatField()
caps_size = models.FloatField(choices=CHOICES,)
forms.py
class CapsProdForm1(forms.ModelForm):
class Meta:
model = CapsProd
fields = [
'production',
'amount_of_caps',
'conc_per_cap',
'conc_per_tab',
]
class CapsProdForm2(forms.ModelForm):
class Meta:
model = CapsProd
fields = [
'amount_of_weighed_tabs',
'mass_all_tabs',
'required_mass_powder',
'weighed_mass_powder',
'caps_size',
]
urls.py
app_name = 'caps_prod'
urlpatterns = [
path('', views.CapsProdWizardView.as_view([CapsProdForm1, CapsProdForm2]), name="add_new"),
]
views.py
class CapsProdWizardView(SessionWizardView):
template_name = "caps_prod/prod_form.html"
html
{% extends "base.html" %}
{% load django_bootstrap5 %}
{% block body_block %}
<h1>
Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}
</h1>
<form class="custom-form" method="post">
{% csrf_token %}
<!-- {% bootstrap_form form %} -->
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{wizard.form.management_form }}
{% for form in wizard.form.forms %}
{% bootstrap_form form %}
{% endfor %}
{% else %}
{% bootstrap_form wizard.form %}
{% endif %}
{% if wizard.steps.prev %}
<button type="submit" class="btn btn-primary" value="{{ wizard.steps.first }}">First step</button>
<button type="submit" class="btn btn-primary" value="{{ wizard.steps.prev }}">Previous step</button>
{% endif %}
<input type="submit" value="Submit">
</form>
{% endblock %}
Thanks!

Using fields from a aggregated QuerySet in a django template

So I got a QuerySet result from an aggregate function to display in a low-spec eBay clone of mine. But my problem is displaying certain fields in the Django template as I want, for example, I want to display the highest bidder's username and bid. When I call the whole object itself like so {{winner}}, then it displays. But when I try to access its fields like so {{winner.user_id.username}}, I get no output although the QuerySet does execute.
When I try to get a field like so (winner.user_id.username):
When I only call winner:
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
CATEGORIES = [
('Appliances', 'Appliances'),
('Tech', 'Tech'),
('Gaming', 'Gaming'),
('Fashion', 'Fashion'),
('Sports and Fitness','Sports and Fitness'),
('Other','Other'),
("Hygiene and Medicine","Hygiene and Medicine"),
("Stationery","Stationery"),
('Decor', 'Decor'),
('Furniture','Furniture'),
('Cars and Mechanical Things','Cars and Mechanical Things'),
("Tools","Tools")
]
# Create models here
class User(AbstractUser):
pass
class Auction_Listing(models.Model):
user_id = models.IntegerField(default=1)
list_title = models.CharField(max_length=64)
desc = models.TextField(max_length=600)
img_url = models.URLField(max_length=200, null=True, blank=True)
start_bid = models.IntegerField()
category = models.CharField(choices=CATEGORIES, max_length=35, null=True, blank=True)
active = models.BooleanField(default=True)
def __str__(self):
return f"ID:{self.id}, {self.list_title}: {self.desc}, {self.start_bid} posted by user:{self.user_id} in Category:{self.category}, url:{self.img_url}"
class Bids(models.Model):
user_id = models.ForeignKey('User', on_delete=models.CASCADE)
auctions = models.ForeignKey('Auction_Listing', on_delete=models.CASCADE, default=1,related_name='bidauc')
bid = models.IntegerField()
def __str__(self):
return f"ID:{self.id}, Bid {self.bid} posted by user:{self.user_id} on auction {self.auctions}"
class Auction_Comments(models.Model):
user_id = models.ForeignKey('User', on_delete=models.CASCADE)
comment = models.TextField(max_length=324, default='N/A')
auctions = models.ForeignKey('Auction_Listing', on_delete=models.CASCADE, default=1,related_name='comauc')
def __str__(self):
return f"ID:{self.id}, Comment: {self.comment} posted by user:{self.user_id} on auction {self.auctions}"
class Watchlist(models.Model):
user_id = models.ForeignKey('User', on_delete=models.CASCADE)
auctions = models.ForeignKey('Auction_Listing', on_delete=models.CASCADE, default=1, related_name='watchauc')
def __str__(self):
return f"ID:{self.id}, user:{self.user_id} on auction {self.auctions}"
views.py
def render_listing(request, title):
if request.method == "POST":
form = BidForm(request.POST)
bid = int(request.POST['new_bid'])
listing = Auction_Listing.objects.get(list_title=title)
comments = Auction_Comments.objects.filter(auctions=listing)
if bid <= listing.start_bid:
error = True
else:
error = False
listing.start_bid = bid
listing.save()
new_bid = Bids(user_id=request.user, auctions=listing, bid=bid)
new_bid.save()
return render(request, 'auctions/listing.html', {
"listing": listing,
"form": form,
"comments": comments,
"error": error,
"comform": CommentForm()
})
else:
form = BidForm()
comform = CommentForm()
listing = Auction_Listing.objects.get(list_title=title)
comments = Auction_Comments.objects.filter(auctions=listing)
high_bid = Bids.objects.filter(auctions=listing).aggregate(maximum=Max("bid"))
winner = Bids.objects.filter(auctions=listing, bid=high_bid['maximum'])
print(winner)
return render(request, 'auctions/listing.html', {
"listing": listing,
"form": form,
"comments": comments,
"error": False,
"comform": comform,
"winner": winner
})
template's code:
{% extends "auctions/layout.html" %}
{% load static %}
{% block title %} Listing: {{listing.list_title}} {% endblock %}
{% block body %}
{% if listing.active %}
<h2>{{ listing.list_title }}</h2>
<div class='listing'>
{% if listing.img_url == "" or listing.img_url == None %}
<a href='#'><img src="{% static 'auctions/img404.png' %}" class='img-fluid'></a>
{% else %}
<a href='#'><img src="{{ listing.img_url }}" class="img-fluid" alt='image of {{ listing.list_title }}'></a>
{% endif %}
<p>
{{ listing.desc }}
</p>
<p>
Current Bid: ${{ listing.start_bid }}
</p>
<p>Category: {{ listing.category }}</p>
<p></p>
{% if user.is_authenticated %}
<div class="bid">
<a href='{% url "watch" listing.list_title %}' class='btn btn-primary'>Add to/Remove from Watchlist</a>
{% if listing.user_id == user.id %}
<a href='{% url "close" listing.list_title %}' class='btn btn-primary'>Close Auction</a>
{% endif %}
</div>
<div class="bid">
<h3>Bid:</h3>
<form method="POST" action='{% url "renlist" listing.list_title %}'>
{% csrf_token %}
{{form}}
<button type="submit" class='btn btn-primary'>Make New Bid</button>
{% if error %}
Please enter a bid higher than the current bid.
{% endif %}
</form>
</div>
{% else %}
<p><a href='{% url "register" %}' class='register'>Register</a> to bid on this item and gain access to other features</p>
{% endif %}
</div>
<div class="listing">
<h3>Comments:</h3>
{% if user.is_authenticated %}
<div id='comform'>
<h4>Post a Comment</h4>
<form method="POST" action="{% url 'commentadd' %}">
{% csrf_token %}
{{comform}}
<button type="submit" class="btn btn-primary">Post Comment</a>
</form>
</div>
{% endif %}
<p>
{% for comment in comments %}
<div class="comment">
<h4>{{comment.user_id.username}} posted:</h4>
<p>{{comment.comment}}</p>
</div>
{% empty %}
<h4>No comments as of yet</h4>
{% endfor %}
</p>
</div>
{% else %}
<h2>This auction has been closed</h2>
<div>
<a href='{% url "watch" listing.list_title %}' class='btn btn-primary'>Add to/Remove from Watchlist</a>
</div>
<p>{{winner.user_id.username}}</p>
{% endif %}
{% endblock %}
Thanks in advance for the help!
Kind Regards
PrimeBeat
This will give a QuerySet. You can see in your second image.
A QuerySet represents a collection of objects from your database.
winner = Bids.objects.filter(auctions=listing, bid=high_bid['maximum'])
You need to iterate over that QuerySet, get whatever data you want and show it in your template.
{% for win in winner %}
<p>{{ win.user_id.username }}</p>
{% endfor %}

ValueError: Cannot assign "1": "Topic.board" must be a "Board" instance

I know there are a bunch of questions addressing this issue, but I haven't solved it out yet. please any help i tried many solutions but didn't work
models.py
class Board(models.Model):
name = models.CharField(max_length=50,unique=True)
description = models.CharField(max_length=150)
def __str__(self):
return self.name
class Topic(models.Model):
subject = models.CharField(max_length=255)
board = models.ForeignKey(Board,related_name='topics',on_delete=models.CASCADE)
created_by = models.ForeignKey(User,related_name='topics',on_delete=models.CASCADE)
created_dt = models.DateTimeField(auto_now_add=True)
class Post(models.Model):
message = models.TextField(max_length=4000)
topic = models.ForeignKey(Topic, related_name='posts',on_delete=models.CASCADE)
created_by = models.ForeignKey(User,related_name='posts',on_delete=models.CASCADE)
created_dt = models.DateTimeField(auto_now_add=True)
view.py
def new_topic(request,board_id):
board = get_object_or_404(Board,pk=board_id)
if request.method == 'POST':
subject = request.POST['subject']
message = request.POST['message']
user = User.objects.first()
topic = Topic.objects.create(
subject=subject,
board=board_id,
created_by=user
)
post = Post.objects.create(
message=message,
topic=topic,
created_by=user
)
new_topic.html
{% extends 'base.html' %}
{% block title %}Start a New Topic{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item">Boards</li>
<li class="breadcrumb-item">{{ board.name }}</li>
<li class="breadcrumb-item active">New topic</li>
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="id_subject">Subject</label>
<input type="text" class="form-control" id="id_subject" name="subject">
</div>
<div class="form-group">
<label for="id_message">Message</label>
<textarea class="form-control" id="id_message" name="message" rows="5"></textarea>
</div>
<button type="submit" class="btn btn-success">Add</button>
</form>
{% endblock %}
I'm fairly new to Django. How would I go about resolving that?
in new_topic(views.py), convert board=board_id to board=board

How to render Model field in Django, either it contains text or image?

In models.py, question field contains text and question_img filed contains image. Questions may be text or image. If question filed contains text, question_img field should be empty and if question_img filed contains image, question filed should be empty. So, how to render text question or image question based on condition? If question field is empty then it should render question_img from Database vice-versa.
models.py:
class Questions(models.Model):
paper = models.CharField(max_length=10, default='None')
qs_no = models.IntegerField(default=None)
question = models.TextField(max_length=500)
question_img = models.ImageField(null=True, blank=True)
option_a = models.CharField(max_length=100)
option_b = models.CharField(max_length=100)
option_c = models.CharField(max_length=100)
option_d = models.CharField(max_length=100)
ans = models.CharField(max_length=50)
forms.py:
class QuestionsForm(forms.ModelForm):
paper = forms.CharField(widget=forms.Select(choices = PAPER_CHOICES))
class Meta:
model=Questions
fields=[
'paper',
'qs_no',
'question',
'question_img',
'option_a',
'option_b',
'option_c',
'option_d',
'ans',
]
views.py:
def questions(request):
if not request.user.is_superuser:
return render(request, 'login.html')
else:
if request.method == 'POST':
form = QuestionsForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('questions')
else:
return HttpResponseRedirect('questions')
else:
form = QuestionsForm()
return render(request, 'questions.html', {'form':form})
def render_questions(request):
print(f'user in render_questions is {request.user.username}', flush=True)
if not request.user.is_authenticated:
return render(request, 'login.html')
else:
global exam_session
exam_session = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
print(f"session id in render ques is {exam_session}", flush=True)
questions = Questions.objects.all()
ques = []
qs_no = 1
for q in questions:
que = {}
que['qs_no'] = qs_no
qs_no = qs_no + 1
que['question'] = q.question
que['id'] = q.id
que['option_a'] = q.option_a
que['option_b'] = q.option_b
que['option_c'] = q.option_c
que['option_d'] = q.option_d
ques.append(que)
print(f"ques: {ques}", flush=True)
return render(request, 'report.html', {'ques': ques})
template.html:
<form id="myform" name="myform" action="answer" method="POST">
{% csrf_token %}
<div class="questionContainer">
{% for question in ques %}
<div class="questiondiv" data-questionId="{{ question.id }}">
<div class="bghead1">
<h5><strong>Question {{ question.qs_no }}.</strong></h5>
</div>
<div class="font mrl">
<h5><input type="hidden" class="form-check-input" name="question" value="{{ question.question }}">{{ question.question }}</h5>
</div>
<div class="radio pad">
<label><input type="radio" class="form-check-input" name="question{{question.qs_no}}" value="{{question.option_a}}"> A) {{ question.option_a }}</label>
</div>
<div class="radio pad">
<label><input type="radio" class="form-check-input" name="question{{question.qs_no}}" value="{{question.option_b}}"> B) {{ question.option_b }}</label>
</div>
<div class="radio pad">
<label><input type="radio" class="form-check-input" name="question{{question.qs_no}}" value="{{question.option_c}}"> C) {{ question.option_c }}</label>
</div>
<div class="radio pad">
<label><input type="radio" class="form-check-input" name="question{{question.qs_no}}" value="{{question.option_d}}"> D) {{ question.option_d }}</label>
</div>
</div>
{% endfor %}
</div>
</form>
Using template if/else is a straightforward approach.
{% if question.question %}
<div class="font mrl">
<h5><input type="hidden" class="form-check-input" name="question" value="{{ question.question }}">{{ question.question }}</h5>
</div>
{% else %}
<div class="font mrl">
<h5><img src="{{ question.image.url }}"></h5>
</div>
{% endif %}

I need to modify my Function based view "FBV" to be class based view "CBV"

i need to make some modifications on my code,as i have a function based view and need to change it to be class based view
FBV:
def Adress_Use_Prev(request):
# print(request.POST)
address_type=request.POST.get('address_type',"shipping")
address_id=request.POST.get("Address-id")
request.session[address_type+"_address_id"]=address_id
next_ = request.GET.get("next")
next_post = request.POST.get("next")
redirected_path = next_ or next_post or None
if is_safe_url(redirected_path, request.get_host()):
return redirect(redirected_path)
return redirect("/")
what is the best solution to convert this fucntion to be CBV
i tried on the following code, but i failed
class UsePrevAddress(NextUrlMixin,FormView):
# def get_success_url(self):
# return self.get_next_url()
def form_valid(self, form):
print("test5")
address_type = self.request.POST.get('address_type', "shipping")
address_id = self.request.POST.get("Address-id")
self.request.session[address_type + "_address_id"] = address_id
return self.get_next_url()
def form_invalid(self, form):
super().form_invalid(form)
the form.html:
{% if address_qs %}
<form method="POST" action="{% url "cart:checkout-Address-reuse" %}"> {% csrf_token %}
{% for Address in address_qs %}
<label for="address-{{ Address.id }}">
<input id="address-{{ Address.id }}" type="radio" name="Address-id" value="{{ Address.id }}"/>
{{ Address.Address_line_1 }},
{% if Address.Address_line_2 %} Address.Address_line_2,
{% endif %}
{{ Address.State }},{{ Address.Postal_Code }},{{ Address.city }}
</label><br/>
{% endfor %}
{% if next_url %}
<input type="hidden" name="next" value="{{ next_url }}">
{% endif %}
{% if address_type %}
<input type="hidden" name="address_type" value="{{ address_type }}">
{% endif %}
<button type="submit" class="btn btn-success">Use Address</button>
</form>
{% endif %}
urls.py:
url('^checkout/Address/reuse$',UsePrevAddress.as_view(),name="checkout-Address-reuse"),
Create a ModelForm for the model class. Add form_class to your CBV.
Ref: ModelForm Django
Example
from django import forms
from django.views.generic.edit import CreateView
class YourModelForm(forms.ModelForm):
model = YourModel
fields = ('field_1', 'field_2', )
# use `CreateView`. `FormView` is for non-model forms
# and I have used a `ModelForm` here.
class YourModelFormView(NextUrlMixin, CreateView):
form_class = YourModelForm
# rest of your code

Categories

Resources