I'm working in Django with a quiz web app that needs to keep a form submitted on every question. The questions are displayed just properly. The problem happens when trying to submit the form with the questions.
The answers of the question (multichoice, 4 answers) are displayed within <li> elements. I want that by clicking in the answer (so, clicking in the <li> element) the answer gets submitted so the next question can be displayed. I can't make it work, so I can't get any answer submitted. The form HTML looks like this:
<form id="game" action="" method="POST">{% csrf_token %}
<input type="hidden" name="question_id" value="{{ question.id }}">
<input type="hidden" name="answer">
<ul class="list-group">
{% for answer in form.answers %}
<li class="list-group-item" name="answer" value="{{answer}}">
{% endfor %}
</ul>
And the jQuery is something like this:
$(document).ready(function() {
$('#game li').click(function() {
$('input[name="answer"]').val($(this).value('answer'));
$('#game').submit();
});
});
It happens nothing when clicking on the answers' <li>, and the console shows no log.
How may I make it work? Thankyou.
EDIT 1:
Yes I have the views.py that manage the quiz, here's a snippet:
class QuizTake(FormView):
form_class = QuestionForm
template_name = 'question.html'
def dispatch(self, request, *args, **kwargs):
self.quiz = get_object_or_404(Quiz, url=self.kwargs['quiz_name'])
if self.quiz.draft and not request.user.has_perm('quiz.change_quiz'):
raise PermissionDenied
self.logged_in_user = self.request.user.is_authenticated()
if self.logged_in_user:
self.sitting = Sitting.objects.user_sitting(request.user,
self.quiz)
else:
self.sitting = self.anon_load_sitting()
if self.sitting is False:
return render(request, 'single_complete.html')
return super(QuizTake, self).dispatch(request, *args, **kwargs)
def get_form(self, form_class):
if self.logged_in_user:
self.question = self.sitting.get_first_question()
self.progress = self.sitting.progress()
else:
self.question = self.anon_next_question()
self.progress = self.anon_sitting_progress()
if self.question.__class__ is Essay_Question:
form_class = EssayForm
return form_class(**self.get_form_kwargs())
def get_form_kwargs(self):
kwargs = super(QuizTake, self).get_form_kwargs()
return dict(kwargs, question=self.question)
def form_valid(self, form):
if self.logged_in_user:
self.form_valid_user(form)
if self.sitting.get_first_question() is False:
return self.final_result_user()
else:
self.form_valid_anon(form)
if not self.request.session[self.quiz.anon_q_list()]:
return self.final_result_anon()
self.request.POST = {}
return super(QuizTake, self).get(self, self.request)
def get_context_data(self, **kwargs):
context = super(QuizTake, self).get_context_data(**kwargs)
context['question'] = self.question
context['quiz'] = self.quiz
if hasattr(self, 'previous'):
context['previous'] = self.previous
if hasattr(self, 'progress'):
context['progress'] = self.progress
return context
def form_valid_user(self, form):
progress, c = Progress.objects.get_or_create(user=self.request.user)
guess = form.cleaned_data['answers']
is_correct = self.question.check_if_correct(guess)
if is_correct is True:
self.sitting.add_to_score(1)
progress.update_score(self.question, 1, 1)
else:
self.sitting.add_incorrect_question(self.question)
progress.update_score(self.question, 0, 1)
if self.quiz.answers_at_end is not True:
self.previous = {'previous_answer': guess,
'previous_outcome': is_correct,
'previous_question': self.question,
'answers': self.question.get_answers(),
'question_type': {self.question
.__class__.__name__: True}}
else:
........
Also this is getting its information from the this forms.py:
class QuestionForm(forms.Form):
def __init__(self, question, *args, **kwargs):
super(QuestionForm, self).__init__(*args, **kwargs)
choice_list = [x for x in question.get_answers_list()]
self.fields["answers"] = forms.ChoiceField(choices=choice_list,
widget=forms.RadioSelect)
I've changed the code you provided me and tried different combinations but still don't work. I can't get the form submitted and the console shows no errors. Is this impossible?
Edit 2:
when I change the HTML code and try to put the '<li>' tag like this:
<li class="list-group-item" name="answer" data-answer="{{answer}}">{{answer}}</li>
I get this as an output (in this case 2 possible answers, 2 <li> elements displayed):
And when I quit the 'data-answer' of the li element, it gets:
Now when I put the mouse on the text, it doesn't get 'cursor:pointer' while before in the 'False">' it did. However, when I click on it nothing happens. May it be a problem in the 'views.py'? Before I used to use a 'submit' button and it worked well. #JacobWindsor
Did I give some light to the issue? I'm quite confused thank you for the answers.
I'm a little confused by your use case. It sounds like you want to send an AJAX request to your server whenever a multiple choice question is clicked. The server then responds with the next question that is rendered into the form. If this is the case then it is a little more complicated than what you have provided. You will need to:
Grab the selected answer (as you have)
Make an AJAX request to the server but not using the form submit function.
Store the answer on the server.
Render the next question
However, from the code and description you have provided I can see that the selector is incorrect.
Try this:
$('#game ul.list-group li').click(function(){...});
However, I would recommend using delegated events since the overhead is a lot lower if you have a lot of list items.
$('#game ul.list-group').on('click', 'li', function(){...});
This also helps if you want to dynamically add or remove questions since you will not need to bind any new event handlers. Since you say "the answer gets submitted so the next question can be displayed" I assume you are doing some kind of dynamic addition of questions.
you havn't closed you li tag
<li class="list-group-item" name="answer" value="{{answer}}">{{answer}}</li>
also li doesn't have value attribute so you can't catch it like that .... try this
<li class="list-group-item" name="answer" data-answer="{{answer}}">{{answer}}</li>
$(document).ready(function() {
$('#game li').click(function() {
$('input[name="answer"]').val($(this).data('answer'));
$('#game').submit();
});
});
Related
Currently when the user introduces a string in an input field and clicks the submit button, this invokes a view that returns through return render(request, 'index.html', context) a context that
it basically contains data that is displayed in a table.
I would like said table to be visible only after submitting the form and not before and that when it is visible it shows the information obtained from the view.
The problem is that if through inline styling I make this table not visible, for example in the following way:
<div class="row" id="searchVariantTable" style="display: none;">
<!-- SOME TABLE HERE-->
</div>
And then I use the onsubmit event for form or onclick for button, it doesn't work. (It works partially, I can see the tbody but thead is missing, so basically I can't display the retrieved data from the database).
Similarly, if I try something like this:
$('document').ready(function() {
$('#searchVariantTable').hide();
$('form').submit(function(e) {
$('#searchVariantTable').show();
e.preventDefault();
});
});
It doesn't work either.
I think the last option, if I'm not mistaken, is AJAX, but I'm not quite sure how to do something like that with Django (it's my first time using Django)
What am I doing wrong? Is there an option that I am missing?
You can try by using if else in django template:
index.html
<form method="post">
<input type="submit">
</form>
{% if allowed == "yes" %}
<!-- your table code -->
{% endif %}
and in views.py
if request.method == "POST":
return render(request, 'index.html',{'allowed':'yes'})
else:
return render(request,'index.html',{'allowed':'no'})
I have a Django project that has a Students model with multiple fields, and I have implemented a ModelChoiceField form which drops down and allows for selecting a particular record in the Students table.
forms.py:
class StudentChoiceField(forms.Form):
students = forms.ModelChoiceField(
queryset=Student.objects.values_list().order_by("last_name"),
empty_label="(select student)",
widget=forms.Select(attrs={"onChange":'refresh()'})
)
def __init__(self, *args, **kwargs):
super(StudentChoiceField, self).__init__(*args, **kwargs)
# without the next line label_from_instance does NOT work
self.fields['students'].queryset = Student.objects.all().order_by("last_name")
self.fields['students'].label_from_instance = lambda obj: "%s %s" % (obj.last_name, obj.first_name)
The label_from_instance method is overridden, so that the drop-down form displays just two model fields (there are eleven total in the model).
When a student is selected, I want to update some textfields in the page to display the remaining fields of the model. Currently, have implemented a javascript function refresh() which is invoked for the onChange event of the StudentChoiceField form.
index.html (all_students_choice is the StudentChoiceField form):
{% extends "base.html" %}
{% block content %}
<body>
<script>
function refresh(){
var id = document.getElementById("id_students").value;
console.log(id);
}
</script>
<div class="container">
<form method=POST action="">
{% csrf_token %}
{{ all_students_choice }}
</form>
</div>
</body>
{% endblock %}
I have confirmed through the browser console that the javascript function is getting called, and printing the value of the ModelChoiceField form. As expected, after selecting an instance from the dropdown menu the value of the form element is the primary key of the table.
I need advice on the best approach to populate the textfields which I will be adding to display the remaining Student model fields (aside from first and last name). Should these be passed as parameters to the javascript function? Is there a best way to approach this problem.
Answering this question with the approach that that was eventually used, in case it would be of assistance to someone else. Decided to render the same template, but with additional element in the context to reference the selected student. In the initial index home page, the selected_student is None:
def index(request):
....
context = {
'students_choice_ln': students_choice_ln,
'students_choice_fn': students_choice_fn,
'selected_student': None
}
return render(request, 'awards/index.html', context)
For the select function, the selected_student is passed in through the context:
def select(request):
if request.method == "GET":
...
student_id = ...
selected_student = Student.objects.get(pk=student_id)
...
context = {
...
'students_choice_ln': students_choice_ln,
'students_choice_fn': students_choice_fn,
'selected_student': selected_student,
...
}
return render(request, 'awards/index.html', context)
The template can then check whether the selected_student variable is available or not, and then accordingly display the additional fields in a separate div.
If there are any experienced web developers / django developers who see problems with this structure, perhaps they can point them out.
I have a cripsy form and I want to change one field from Textarea to CKEDitorUploadingWdidget
So my form looks like this (I have left in what was previoulsy working:
class RenameStudyForm(BetterModelForm):
name = forms.CharField(label='Study Name', max_length=51, required=False) # Update study name
#waiver = forms.CharField(widget=forms.Textarea, label='Waiver of Documentation', required=False)
waiver = forms.CharField(widget=CKEditorUploadingWidget(), label='Waiver of Documentation', required=False)
I have amended my model as follows:
class study(models.Model):
researcher = models.ForeignKey("auth.user") # Researcher's name
name = models.CharField(max_length = 51) # Study name
instrument = models.ForeignKey("instrument") # Instrument associated with study
#waiver = models.TextField(blank = True)
waiver = RichTextUploadingField(blank = True)
My template looks has:
{% load crispy_forms_tags %}
{{ form.media }}
{% crispy form %}
When I enter the screen to edit the waiver I get a rich text field to edit, as I would expect. However, nothing I enter into the field is passed back to the form. Within the form I added a print statement, as below
def clean(self):
cleaned_data = super(RenameStudyForm, self).clean()
print(cleaned_data['waiver'])
The print always gives the original text. Can anyone help me please
EDIT
I've been reviewing console when I'm using the CKEditorUploadingWidget against the forms.Textarea widget and it appears to be generating the following jQuery warning
Synchronous XMLHttpRequest on the main thread is deprecated because of
its detrimental effects to the end user's experience.
I believe I am getting this because I am loading the form into a modal using this button
<button type="button" class="btn btn-secondary btn-block" onclick = "modal_form('/interface/study/{{ current_study|urlencode }}/rename_study/')" >Update Study</button>
And this view
def rename_study(request, study_name):
#do stuff
return render(request, 'researcher_UI/add_study_modal.html', form_package)
So my JavaScript for ckeditor is being loaded now rather than when the document is originally loaded so I think this causes the issues. Any thoughts really appreciated
Found the answer. The form is being submitted via ajax. As such I need to copy the CKEditor data into the form field, which I do with
for (var instance in CKEDITOR.instances){
CKEDITOR.instances[instance].updateElement();
}
I've implemented TinyMCE with the django-tinymce package. However, my submit button which worked fine without TinyMCE now has become rather useless since I can't submit the form, once everything is filled out.
I can use Ctrl + S inside of TinyMCE (I discovered that by accident) and everything will get submitted correctly. Also, I can use the save-button of the TinyMCE "save" plugin to submit.. Do I have to configure the submit button to make it work with TinyMCE?
Template:
{% extends 'medisearch/header.html' %}
{% load crispy_forms_tags %}
{% block header %}
{{ form.media }}
{% endblock %}
{% block content %}
▷⋅⋅⋅⋅⋅⋅⋅<form action="{{ url }}" method="post">
▷⋅⋅⋅⋅⋅⋅⋅ <div class="form-group">
▷⋅⋅⋅⋅⋅⋅⋅ {% csrf_token %}
▷⋅⋅⋅⋅⋅⋅⋅ {{ form|crispy }}
▷⋅⋅⋅⋅⋅⋅⋅ </div>
▷⋅⋅⋅⋅⋅⋅⋅ <input type="submit" class="btn btn-primary" value="Speichern" />
▷⋅⋅⋅⋅⋅⋅⋅</form>
{% endblock %}
views.py
class EntryDetail(DetailView):
model = Mediwiki
slug_field = 'non_proprietary_name'
template_name = 'mediwiki/entry.html'
class MediwikiForm(FormView):
template_name = 'mediwiki/create.html'
form_class = MediwikiForm⋅
success_url = "/" #TODO user get's redirected to page he's created⋅
def form_valid(self, form):
form.save()
return super(MediwikiForm, self).form_valid(form)
class EntryDisplay(View):
def get(self, request, *args, **kwargs):
try:
view = EntryDetail.as_view()
return view(request, *args, **kwargs)
except Http404: # If there's no entry in db:
if check_user_editor(request.user) == True:
view = MediwikiForm.as_view()
return view(request, *args, **kwargs)
else:
pass
def post(self, request, *args, **kwargs):
view = MediwikiForm.as_view()
return view(request, *args, **kwargs)⋅
forms.py
class MediwikiForm(ModelForm):
wiki_page = forms.CharField(widget=TinyMCE(attrs={'cols': 80, 'rows': 30}))
class Meta:
model = Mediwiki⋅
fields = '__all__'
TinyMCE is in urls.py and under INSTALLED_APPS..
I know it's probably too late for you, but it seems that i had the same issue, just now and my solution might help someone in the future.
You are using crispy, which includes the javascript files for the form on it's own.
Therefore the django_tinymce/init_tinymce.js will be referenced twice.
This will break the submittion of your content, since the form is initialized twice.
In order to fix this you may just remove the call of {{ form.media }}.
I had a similar issue and learned that it has to do with the way that TinyMCE deals with text areas. The following init script worked for me:
<script>
tinymce.init({
selector:'.editor',
setup: function (editor) {
editor.on('submit', function (e) {
editor.save();
});
}
});
</script>
#artifex_knowledge answers makes sense and it works.
To build up on it, besides calling editor.save() on submit (or on change), keep in mind that if users don't fill the text area, they won't be able to submit the form, but the this field is required error message won't be displayed.
This is because the text area field (in this case wiki_page) is required by default, so in the html it will be rendered with required. But TinyMCE hides the text area (and replaces it with an iframe :( ), so if you try to submit the form with an empty required, it won't, but the error message will keep hidden.
(A possible solution is to use JS to remove the required attribute and check it in django later).
Just delete required field from textarea element, which is used as editor.
Deleting the 'required' field in the textarea element solved my problem (like Krysits mentioned)
I also had the same issue as yours, and I just removed for instance: "wiki_page" charfield from the subclass of Modelform, and put Tinymce widget in the Meta class.
class MediwikiForm(ModelForm):
class Meta:
model = Mediwiki⋅
fields = '__all__'
widgets = {
'wiki_page': TinyMCE(attrs={'cols': 80, 'rows': 30})
}
I have been struggling with this problem for more than two days, I have looked at similar questions here and to many documentations but nothing helped me. Basically, I am building a Django web application (where people can add themselves as friends) I am using Django package django-friendship, but I want the following to happen: when a user is in someone else profile, the first can click an 'Add friend' button and a friend request must be sent. But I want to achieve this without reloading the page or rendering to another one. The method that has to be triggered in order to send the request is in views.py. Probably I need to use ajax but in what way? My final goal is the button to perform as an 'Add friend' button in facebook, for example (without reloading).
friend_profile.html
<form action="{% url 'users:fr_request' pk=friend.id %}" method="POST">
{% csrf_token %}
<input type="submit" value="Add friend"/>
views.py
def friendship_add(request, pk):
if request.method == 'POST':
to_user = User.objects.get(pk=pk)
from_user = request.user
try:
Friend.objects.add_friend(from_user, to_user)
except AlreadyExistsError as e:
print('Exception when adding a friend ' + e)
else:
return HttpResponse('You added a friend')
return render(request, 'users/friend_profile.html')
urls.py
urlpatterns = [
url(r'users/(?P<pk>\d+)/send/$', views.friendship_add, name='fr_request'),
]
At the moment when submitting the form, I render the page to users/(?P\d+)/send/ which calls the view method and executes Friend.objects.add_friend, but I want this to happen without going to /send/ page or reloading the current?
This can be easily achieved using jQuery. You may want to assign an id to your form in case there are multiple forms in your page:
<form action="{% url 'users:fr_request' pk=friend.id %}" method="POST" id="friend_form">
{% csrf_token %}
<input type="submit" value="Add friend"/>
The script could then look like that:
$(function() {
$("#friend_form").submit(function(event) {
// Stop form from submitting normally
event.preventDefault();
var friendForm = $(this);
// Send the data using post
var posting = $.post( friendForm.attr('action'), friendForm.serialize() );
// if success:
posting.done(function(data) {
// success actions, maybe change submit button to 'friend added' or whatever
});
// if failure:
posting.fail(function(data) {
// 4xx or 5xx response, alert user about failure
});
});
});
For more information and examples refer to the jQuery.post() documentation.