I am trying to create a number of surveys in Django and put them in a survey index page. I have run into an issue: Up until now, all of my surveys had questions which had the same set of answer choices, so in my Django models I linked the answers only to the survey because it was the easiest for me and it was working. Now I have to add a new survey where each question has a different set of answers choices (if you are curious, this is the survey: https://www.fresno.ucsf.edu/pediatrics/downloads/edinburghscale.pdf). My problem is that for this new survey I am now getting every possible answer choice in the whole survey for each question.
How can I link the answer choices to the question so that I will only see the choices for the corresponding question? Yes, I have read the Django polls tutorial and have tried requesting the question_id, but then I got an error along the lines of "missing question_id" so I went back to my original code. I think the right approach would be to link the answer choices to both the question and the survey, but not sure.
Here is my model:
class Questionnaire(models.Model):
name = models.CharField(max_length=255)
text = models.CharField(max_length=200)
# def get_absolute_url(self):
# return reverse('questionnaire-detail', args=[str(self.id)])
def __str__(self):
return self.name
class Question(models.Model):
text = models.CharField(max_length=200)
questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE, related_name='questions')
def __str__(self):
return self.text
class Answer(models.Model):
question = models.ForeignKey(Question, models.SET_NULL, blank=True, null=True, related_name='answerlist')
questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE, related_name='answers', default='')
text = models.CharField(max_length=200)
image = models.CharField(null=True, max_length=20)
image_location = models.CharField(null=True, max_length=20)
def __str__(self):
return self.text
Here is my views:
ResponseFormSet = modelformset_factory(Response, form=ResponseForm, extra=0)
if request.method == 'POST':
formset = ResponseFormSet(request.POST or None, request.FILES, form_kwargs={'user': request.user})
if formset.is_valid():
print("Formset is valid")
formset.save()
return HttpResponseRedirect(reverse ('questionnaires:questionnaire_index'))
else:
print("Formset is NOT valid")
print(formset.errors)
print(formset.non_form_errors())
questionnaire = Questionnaire.objects.get(id=questionnaire_id)
questions = Question.objects.filter(questionnaire_id=questionnaire_id)
answers = Answer.objects.filter(questionnaire_id=questionnaire_id)
return render(request, 'questionnaires/questionnaire_lec.html', {
'questions': questions,
'answers': answers,
'questionnaire': questionnaire,
})
And my HTML:
<form method="post" action="{{ request.url }}">
{% csrf_token %}
<legend><h2>{{ questionnaire.text }}</h2></legend>
<br>
{% for question in questions %}
<h3>{{question.text}}</h3>
<label hidden="" for="id_form-{{ forloop.counter0 }}-question">Question:</label>
<select hidden="" name="form-{{ forloop.counter0 }}-question" id="id_form-{{ forloop.counter0 }}-question">
<option value="{{question.id}}" selected="">{{question.text}}</option>
</select>
<!-- <label for="id_form-{{ forloop.counter0 }}-answer">Answer:</label> -->
{% for answer in answers %}
<label for="id_form-{{ forloop.counter0 }}-answer">
<input type="radio" name="form-{{ forloop.parentloop.counter0 }}-answer" id="id_form-{{ forloop.counter0 }}-answer" value="{{answer.id}}">
{{answer.text}}
</label> <br>
{% endfor %}
<input type="hidden" name="form-{{ forloop.counter0 }}-id" value="{{ forloop.counter }}" id="id_form-{{ forloop.counter0 }}-id">
<input type="hidden" name="form-TOTAL_FORMS" value="{{questions|length}}" id="id_form-TOTAL_FORMS" />
<input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS" />
<input type="hidden" name="form-MAX_NUM_FORMS" value="{{questions|length}}" id="id_form-MAX_NUM_FORMS" />
{% endfor %}
<br />
<br />
<input type="submit" value="Submit">
</form>
{% endif %}
{% endblock %}
You should use:
{% for answer in question.answerlist.all %}
…
{% endfor %}
There is no need to pass a QuerySet of Answers to the template.
Related
I know my code is not optimal as I'm only a beginner with Django so don't be too hard on me
Here is my code :
views.py
class ComposantUpdate(UpdateView):
model = configuration
fields = '__all__'
template_name = 'accueil/exploitation/update_composant.html'
update_composant.html
<form action="{% url 'composant_update' pk=composant.id %}" method="post">
{% csrf_token %}
<div class="form-group">
{% for field in form %}
<label class="col-md-6 offset-md-3 nom_champ"> {{field.label_tag}}</label>
<input class="col-md-4 offset-md-4 contenu_champ" type="text" name="{{ field.label }}" id="{{ field.id_for_label }}" value="{{ field.value }}"/>
{%endfor%}
<br>
<br>
<button class="col-md-6 offset-md-3 btn btn-primary" type="submit" value="Update" /> Mettre à jour </button>
</div>
</form>
urls.py
path('update_composant/<int:pk>', views.ComposantUpdate.as_view(),
name='composant_update'),
models.py
class configuration(models.Model):
Num_ordre = models.CharField(max_length=15)
Composant = models.CharField(max_length=15)
Designation = models.CharField(max_length=15)
Qte_servie = models.IntegerField()
Qte_a_servir = models.IntegerField()
Lot = models.CharField(max_length=15)
Categorie = models.CharField(max_length=15)
Famille = models.CharField(max_length=15)
def __str__(self):
return '%s %s %s' % (self.Num_ordre, self.Designation, self.Lot)
when I go to the url of the form, all the fields get the content of the database which is what I expect but if I modify the "Lot" field and click to submit, my database is not updated at all with the new value of "Lot". DO you have any idea of why ? For information, I do get redirected to the 'composant_update' view and no error is risen
In fact, I didn't write my html correctly, the attribute "name" in the input should have been {{field.html_name}} instead of {{field.label}}.
This doesn't work :
<input class="col-md-4 offset-md-4 contenu_champ" type="text" name="{{ field.label }}" id="{{ field.id_for_label }}" value="{{ field.value }}"/>
This works :
<input class="col-md-4 offset-md-4 contenu_champ" type="text" name="{{ field.html_name}}" id="{{ field.id_for_label }}" value="{{ field.value }}"/>
See https://docs.djangoproject.com/en/2.0/topics/forms/#s-looping-over-the-form-s-fields for more information
Thanks Willem Van Onsem !
Forewarning: I'm very new to Django (and web development, in general).
I'm using Django to host a web-based UI that will take user input from a short survey, feed it through some analyses that I've developed in Python, and then present the visual output of these analyses in the UI.
My survey consists of 10 questions asking a user how much they agree with a a specific topic.
Example of UI for survey:
Example of UI input screen
For models.py, I have 2 fields: Question & Choice
class Question(models.Model):
question_text = models.CharField(max_length=200)
def __str__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
I am wanting to have a user select their response to all 10 questions, and then click submit to submit all responses at once, but I'm having trouble with how that would be handled in Django.
Here is the html form that I'm using, but this code snippet places a "submit" button after each question, and only allows for a single submission at a time.
NOTE: The code below is creating a question-specific form for each iteration.
{% for question in latest_question_list %}
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<div class="row">
<div class="col-topic">
<label>{{ question.question_text }}</label>
</div>
{% for choice in question.choice_set.all %}
<div class="col-select">
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
</div>
{% endfor %}
</div>
<input type="submit" value="Vote" />
</form>
{% endfor %}
I'm interested in how I would take multiple inputs (all for Question/Choice) in a single submission and return that back to views.py
EDIT: ADDING VIEWS.PY
Currently, my views.py script handles a single question/choice pair. Once I figure out how to allow users to submit the form one time for all 10 question/choices, I will need to reflect this in views.py. This could sort of be part 2 of the question. First, how do I enable a user to submit all of their responses to all 10 questions with one "submit" button? Second, how do I setup views.py to accept more than 1 value at a time?
views.py
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/survey.html', {
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls:analysis'))
Please let me know if additional context it needed.
Thanks in advance!
-C
Use getlist()
In your views.py
if method=="POST":
choices = request.POST.getlist('choice')
I feel you should change the input radio to checkbox. Radio won't allow multiple selection but Checkbox will.
Refer here: https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.QueryDict.getlist
Ideally, this should have been done with Django Forms. Django forms have widgets and RadioSelect is one of them. You can use that to render your form and get the answer to each question at once.
But that will need a lot of change in the way you are currently doing things.
So, what you can do is, on click on a submit button, collect all the question/choice pairs and send them at once with a POST request.
{% for question in latest_question_list %}
<form>
<div class="row">
<div class="col-topic">
<label>{{ question.question_text }}</label>
</div>
{% for choice in question.choice_set.all %}
<div class="col-select">
<input type="radio" name="choice" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
</div>
{% endfor %}
</div>
</form>
{% endfor %}
<input id="submit-btn" type="submit" value="Vote" />
<script>
$(document).on('click', '#submit-btn', function(event){
var response_data = []
var question_objs = $('.col-topic');
var choice_objs = $('.col-select');
for(i=0;i<question_objs.length;i++){
var question_text = $(question_objs[i]).find('label').text();
var choice_id = $(choice_objs[i]).find('input').val();
var choice_text = $(choice_objs[i]).find('label').text();
var question_choice = {
"question_text": question_text,
"choice_id": choice_id,
"choice_text": choice_text
}
response_data.push(question_choice);
}
$.ajax({
type: "POST",
url: "url_to_your_view",
data: response_data,
success: function(response){
alert("Success");
}
});
});
</script>
This is how your view should look like.
def question_choice_view(request):
if request.method == "POST":
question_choice_data = request.POST['data']
# further logic
Now, question_choice_data is a list of dictionaries. Each dict in the list will have the question_text, choice_text and choice id of user's response.
You just need to organize your template a bit differently in order to have multiple questions within the same form. Litteraly in HTML it would translate into multiple text inputs and then one submit input below, all within one single form:
<form action="{% url 'polls:vote' question.id %}" method="post">
{% for question in latest_question_list %}
{% csrf_token %}
<div class="row">
<div class="col-topic">
<label>{{ question.question_text }}</label>
</div>
{% for choice in question.choice_set.all %}
<div class="col-select">
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
</div>
{% endfor %}
</div>
{% endfor %}
<input type="submit" value="Vote" />
</form>
Is it working now ?
Can anyone help me with this issue? I saw all posts which are similar to my issue, but I can't fix this up.
Error:
Reverse for 'commit_add' with arguments '('',)' and keyword arguments '{}' not found. 1 pattern(s) tried: ['git/project/(?P<pk>[0-9]+)/add_commit/$']
views.py
class CommitCreate(CreateView):
template_name = 'layout/project_commit_detail.html'
model = Commit
fields = ['user', 'project', 'branch', 'commit_title', 'commit_body']
success_url = reverse_lazy('git_project:index')
html form
<div class="container-fluid">
Add New Commit
<div id="demo1" class="collapse" >
<form class="form-horizontal" action="{% url 'git_project:commit_add' project.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% if user.is_authenticated %}
<label for="user">Commit created by: "{{ user.username }}"</label><br>
<input id="user" type="hidden" name="user" value="{{ user.id }}">
<label for="project">Commit for project: "{{ project.proj_title }}"</label><br>
<input id="project" type="hidden" name="project" value="{{ project.id }}">
<label for="branch">Branch: </label>
<select>
{% for branch in all_branches %}
<option id="branch" name="branch">{{branch.branch_name}}</option>
{% endfor %}
</select><br>
<label for="commit_title">Commit Title: </label>
<input id="commit_title" type="text" name="commit_title"><br>
<textarea id="commit_body" name="commit_body" rows="5" cols="50" placeholder="Commit body..."></textarea><br>
<button type="submit" class="btn btn-success">Commit</button>
{% endif %}
</form>
</div>
url.py
url(r'project/(?P<pk>[0-9]+)/add_commit/$', views.CommitCreate.as_view(), name='commit_add'),
model.py
class Commit(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=0)
project = models.ForeignKey(Project, on_delete=models.CASCADE, default=0)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, default=0)
commit_title = models.CharField(max_length=64)
commit_body = models.CharField(max_length=16384)
commit_date = models.DateTimeField(default=timezone.now)
I don't know why is happening. Can anyone help me? I am very new in Django.
Thanks! :)
Is it possible to create a form that modifies when information is already find in the database or so adds information if there is nothing in the database ?
So I have a template that allows me to display the value if there is one in the base given to the fields or answer then display a blank field for the answer.
My template for my formset :
<form method="POST" action="">
{{ formset.management_form }} {% csrf_token %}
<table>
<!-- <br>{{ formset.as_table }}<br> -->
{% for question in questions %}<hr>
<label for="question">{{ question }} [{{ question.id }}]</label>
<input type="hidden" id="id_form-{{ forloop.counter0 }}-question" name="form-{{ forloop.counter0 }}-question" value="{{ question.id }}"/>
</p>
{% if question.reply_set.all %}
{% for reply in question.reply_set.all %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.parentloop.counter0 }}-answer" name="form-{{ forloop.parentloop.counter0 }}-answer" value="{{ reply.answer }}"/>
</p>
{% endfor %}
{% else %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.counter0 }}-answer" name="form-{{ forloop.counter0 }}-answer" placeholder="answer here"/>
</p>
{% endif %}
{% endfor %}
<br><br><br><hr> <br><br><br>
</table><br>
<center><input type="submit" value="Submit" class="btn btn-success" />
Retour</center>
</form>
My problem is that I get a good add when there is nothing in the database but when I have Data if I change its value this create another answer as I would like it replaced the answer with the new!
My views.py
def access(request, instance):
questions = Question.objects.filter(page=instance)
pages = Page.objects.all()
numPages = Page.objects.get(pk=instance)
length_questions = len(questions)
logged_user = get_logged_user_from_request(request)
instance = {'instance':instance}
ReplyFormSet = modelformset_factory(model=Reply, form=ReplyForm, extra=length_questions)
ReplyFormSet.form = staticmethod(curry(ReplyForm, instance=numPages))
if logged_user:
if request.method == 'POST':
formset = ReplyFormSet(request.POST, request.FILES)
if formset.is_valid():
new_instances = formset.save(commit=False)
for new_instance in new_instances:
new_instance.user = logged_user
new_instance.save()
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
messages.add_message(request, messages.INFO, "L'ajout à bien été effectué !")
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
formset = ReplyFormSet(queryset = Reply.objects.none())
return render_to_response('polls/access.html', {
'formset': formset,
'questions':questions,
'logged_user':logged_user,
'numPages' : numPages
})
else:
return HttpResponseRedirect('/login/')
My model allows me to answer several questions at the same time ..
My models.py
class Page(models.Model):
title = models.CharField(max_length=30)
def __str__(self):
return self.title
class Question(models.Model):
label = models.CharField(max_length=30)
page = models.ManyToManyField(Page)
def __str__(self):
return self.label
class Reply(models.Model):
question = models.ForeignKey(Question)
user = models.ForeignKey(Personne)
answer = models.CharField(max_length=30)
creationDate = models.DateTimeField(default=django.utils.timezone.now)
def __str__(self):
return str(self.answer)
How can I change the values instead adding in once again when there is already in the database?
Edit : screenshot of the problem
This saves 2 other lines more when I submit ..
Although you can edit current replies using your current approach by adding a hidden id field in your reply forms like
{% for reply in question.reply_set.all %}
<p>
<label for="answer">Réponse :</label>
<input type="text" id="id_form-{{ forloop.parentloop.counter0 }}-answer" name="form-{{ forloop.parentloop.counter0 }}-answer" value="{{ reply.answer }}"/>
<input type="hidden" id="id_form-{{ forloop.parentloop.counter0 }}-id" name="form-{{ forloop.parentloop.counter0 }}-id" value="{{ reply.id }}"/>
</p>
{% endfor %}
this will tell your ReplyFormset that these records already exist, so instead of creating new records, edit existing records.
Yet, most of the work you are doing manually, can be done using Django's built in functionality, i.e. Django Formsets (which you are using but not using right). Django Inline Formsets are the Django way of doing it.
Django form will create a new entry in db if you are calling a form without an instance.
form = MyForm()
On the contrary, if you want to update the existing entry in tha db pass the instance to the form.
form = MyForm(instance=myinstance)
Long post ahead!
This is a follow-up to my previous question, which was about creating a feedback page that associates one form to each user. I was able to get that done, but it was done in what I guess is a hacked-together way because I'm seeing some really strange behaviour after I submit feedback one time.
Let's say I've not submitted any feedback previously, and now I want to submit feedback for two of three people (img). The hidden management_form details appear as follows:
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="3" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="0" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
On submit this successfully creates two new entries in the Feedback table, as wanted. The problem is that now if I go to any feedback page (as any user) I see the feedback I have already created and I'll get errors on submit.
If I now go to a page with one or two users I'll see one or two forms (this is good) but the management_form data is incorrect. For example, on a one user page I'll see this management_form data:
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="3" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="2" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
And get this error:
MultiValueDictKeyError at /feedback/2/
"u'form-1-id'"
Since I'm only supposed to have one form appear that's all I see, and it's set to the first of the two feedbacks already created, but there's obviously a problem with the management data. Initial should be 0 (not 2) and total forms should be 1 (not 3).
If I go to a page with an equal number of users I'll see the original three feedback forms, with this management_form data:
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="5" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="2" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
Again, total should be 3 (not 5) and initial should be 0 (not 2). This time I don't get an error though, because I'll get a message saying that I need to fill in values for two forms which don't even appear on the page.
Hopefully that explains the problem well enough, so here's the code:
models.py
class Feedback(models.Model):
action = models.ForeignKey(Action)
feedback = models.CharField(max_length=1)
feedback_by = models.ForeignKey(UserProfile, related_name='feedback_by')
feedback_for = models.ForeignKey(UserProfile, related_name='feedback_for')
comment = models.CharField(max_length=200)
created = models.DateTimeField()
modified = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created = datetime.datetime.today()
self.modified = datetime.datetime.today()
return super(Feedback, self).save(*args, **kwargs)
forms.py
class FeedbackForm(forms.ModelForm):
choices = (('g', '(+1) Positive'),
('b', '(±0) Negative'),
('n', '(-1) No Show'),
('d', 'Don\'t Leave Feedback'))
feedback = forms.ChoiceField(widget=forms.RadioSelect(), choices=choices, initial='d')
comment = forms.CharField(widget=forms.Textarea())
class Meta:
model = Feedback
fields = ['feedback_for','feedback','comment']
views.py
#login_required
def new_feedback(request, action_id):
action = get_object_or_404(Action, id=action_id)
profile = UserProfile.objects.get(user_id=request.user.id)
participants = all_info_many_profiles(action.participants.filter(~Q(id=profile.id)))
fbformset = modelformset_factory(Feedback, form=FeedbackForm, extra=len(participants))
if request.method == 'POST':
formset = fbformset(request.POST, request.FILES)
if formset.is_valid():
#formset.save(commit=False)
for form in formset:
tmp = form.save(commit=False)
tmp.action = action
tmp.feedback_by = profile
if tmp.feedback != 'd':
tmp.save()
return index(request)
else:
print formset.errors
#return index(request)
else:
formset = fbformset()
return render(request, 'app/new_feedback.html',
{'action': action, 'participants': participants, 'formset': formset}
)
feedback.html
{% load multifor %}
{% block body_block %}
<h1>Leave Feedback</h1>
<form method="post" action="{% url 'app:new_feedback' action.id%}">
{% csrf_token %}
{% comment %}
<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="{{participants.count}}" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="0" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
{% endcomment %}
{{ formset.management_form }}
{{ formset.errors }}
{% for form in formset; participant in participants %}
{{ form.id }}
{{ form.errors }}
<input id="id_form-{{forloop.counter0}}-feedback_for" name="form-{{forloop.counter0}}-feedback_for" type="hidden" value="{{participant.id}}" /> <br />
{{ form.feedback_for.label }} {{ participant.username }}: <br />
{% for radio in form.feedback %}
{{ radio }} <br />
{% endfor %}<br />
{{ form.comment.label }} {{ form.comment }} <br /><br />
{% endfor %}
<input type="submit" name="submit" value="Submit Feedback" />
</form>
{% endblock %}
The problem is that you are using a modelformset_factory, this factory is tied to the model and it will "help" you by populating stuff, this is why the count is at 5.
You might get the result you want by using a plain formset_factory. Django's docs has some examples