The aim of my website is to have a page, let's call it "Random Question!"
Each time the user enters the page, a set of numbers are randomly generated, and they have to correctly answer the question: numbera + number b .
IF they are correct, they go to a page that says "Correct" and then they are redirected back to the same page, again, with a different set of numbers.
Now, the issue is, on the first page "Random Question!", I want to add another question to it.
Views.py:
def form_handle(request):
if request.method == 'POST':
form = MyForm(request.POST) # if post method then form will be validated
if form.is_valid():
cd = form.cleaned_data
num1 = cd.get('num1')
a = request.session.get('a', 0)
b = request.session.get('b', 0)
if float(num1) == float(a + b):
# give HttpResponse only or render page you need to load on success
return render(request, 'sectipn1part1success', {})
else:
# if sum not equal... then redirect to custom url/page
return HttpResponseRedirect('rr/') # mention redirect url in argument
else:
a = random.randrange(5,10);
b = random.randrange(10,20);
request.session['a'] = a
request.session['b'] = b
question1 = ('What is ' + str(a) + ' + ' + str(b) + ' ?')
form = MyForm() # blank form object just to pass context if not post method
context = {
'form': form,
'a': a,
'b': b,
'question1': question1
}
return render(request, "section1part1.html", context)
As you can see, right now it only does one simple style of question, addition only (question 1).
I would like to add another question, such as question 2, which could be something like "What is a / 2".
Something like the above would be achieved using something like a Java SWITCH statement ( I am not sure if Django has it, though it should be possible to do it without it). A random number would be generated corresponding to another question. Then I'd have to use another IF statement after if request.method == 'POST' to calculate the correct answer as each new question would be calculated differently.
Now, the above would be a good strategy in the short term.
In the long term, I am not sure if it is ideal. Is there a better way of doing this sort of thing or is my way ok?
I was thinking of other ways including:
Using a view-dispatcher sort of function, for example, it would call another view instead of everything on one view but I am not sure of this is possible
** Are there any performance considerations I should be aware of ? **
Update #1
Ok, I've gone ahead and implemented what I suggested above. This is what happens:
Django view gets called --> Another view function is randomly called and does the processing and returns a context object
else: #add another 'if' randomiser to selection question function randomly
context = question1(request)
context = question2(request)
return render(request, "section1part1.html", context)
Is this good practice / style? Should I just continue doing it this way?
form_handle should only be responsible for handling forms. All the work of creating questions and answers should be done by something else. Once you do that, it's easy to to switch out question types, or generate endless loops of questions, or create a list of questions to loop through, or ...
Here's an example with a Question class. You could just as easily create functions that return a tuple of (question, answer) or any other variety of question/answer generators.
class Question(object):
def answer(self):
raise NotImplementedError
def question(self):
raise NotImplementedError
class AdditionQuestion(Question):
def __init__(self):
a_range = (5, 10)
b_range = (10, 20)
self.a = random.randrange(*a_range)
self.b = random.randrange(*b_range)
def answer(self):
return self.a + self.b
def question(self):
return 'What is {} + {}?'.format(self.a, self.b)
class MultiplicationQuestion(Question):
def __init__(self):
a_range = (100, 200)
b_range = (10, 20)
self.a = random.randrange(*a_range)
self.b = random.randrange(*b_range)
def answer(self):
return self.a * self.b
def question(self):
return 'What is {} * {}?'.format(self.a, self.b)
then, your code only relies on an object that has object.question() and object.answer(). If you want non-numeric answers, or answers that are close enough (ex: 3.3333 is close enough to 3.333333) you can change if float(num1) == float(answer): to if compare_answers(num1, answer): and then write a compare_answers function.
def form_handle(request):
if request.method == 'POST':
form = MyForm(request.POST) # if post method then form will be validated
if form.is_valid():
cd = form.cleaned_data
num1 = cd.get('num1')
answer = request.session.get('answer', 0)
if float(num1) == float(answer):
# give HttpResponse only or render page you need to load on success
return render(request, 'sectipn1part1success', {})
else:
# if sum not equal... then redirect to custom url/page
return HttpResponseRedirect('rr/') # mention redirect url in argument
else:
question = AdditionQuestion()
answer = question.answer()
request.session['answer'] = answer
form = MyForm() # blank form object just to pass context if not post method
context = {
'form': form,
'answer': answer,
'question': question.question()
}
return render(request, "section1part1.html", context)
If you want a random question-type generator, also make that its own function/object. Keep it separated from the form_handle so that it's easy to change:
def random_question():
class_ = random.choice([MultiplicationQuestion, AdditionQuestion])
return class_()
then change the line here from:
else:
question = AdditionQuestion()
to:
else:
question = random_question()
Related
I have an app that shows some questions to the user. It has to reply in input text fields and then there's a view that checks all these results (the one below).
This view perfectly works but I know I made a mess with dictionaries and lists. I thought to create those just to prepare a results variable that then I'll print on the HTML page. How can I simplify it?
def check(request):
# Initialize results
results = []
i = 0
for input_name, input_value in request.POST.items():
# Remove cfsr from inputs
if input_name.isdigit():
# Get the question from the input name
question = Question.objects.get(pk=input_name)
# Get the related correct answer to that question
answer = question.answer
# Create results
results.append({'question_text': question.text, 'user_answer': input_value,
'correct_answer': answer.text})
# Check if user answer is correct
if input_value == answer.text:
results[i]['is_correct'] = True
else:
results[i]['is_correct'] = False
i += 1
context = {'results': results}
return render(request, 'quiz/results.html', context)
You could make these improvements for readability:
Create a list of the inputs that you want to access; the if check for the csrf_token field makes the code more difficult to read, and the assumption that csrf_token always contains letters may not be true.
Remove the counter variable (i); counters like this in for loops aren't pythonic, and often just complicate the code.
Replace the if/else test for the correct answer with a boolean expression; you can always to this with if/else blocks that simply assign True or False to a variable depending on the result of some test.
Build your results dictionary completely before appending to the list to avoid having to access via its index (results[i]...).
from django.http import HttpResponseNotAllowed, HttpResponseBadRequest
def check(request):
if not request.method == 'POST':
return HttpResponseNotAllowed(['POST'])
# guaranteed by CsrfViewMiddleware to exist
del request.POST['csrfmiddlewaretoken']
results = []
for question_id, user_answer in request.POST.items():
try:
question = Question.objects.get(pk=name)
except Question.DoesNotExist:
return HttpResponseBadRequest("Oops!")
results.append({
'question_text': question.text,
'user_answer': user_answer,
'correct_answer': question.answer.text,
'is_correct': value == question.answer.text,
})
return render(request, 'quiz/results.html', {
'results': results,
})
You can replace
if input_value == answer.text:
results[i]['is_correct'] = True
else:
results[i]['is_correct'] = False
with
results[-1]['is_correct'] = input_value == answer.text
and get rid of the i
or even better you could put that in the results.append
results.append({'question_text': question.text,
'user_answer': input_value,
'correct_answer': answer.text,
'is_correct': input_value == answer.text})
I'm new to python REST API and so facing some particular problems. I want that when I enter the input as pathlabid(primary key), I want the corresponding data assigned with that key as output. When I run the following code i only get the data corresponding to the first row of table in database even when the id i enter belong to some other row.
This is the VIEWS.PY
class pathlabAPI(View):
#csrf_exempt
def dispatch(self, *args, **kwargs):
# dont worry about the CSRF here
return super(pathlabAPI, self).dispatch(*args, **kwargs)
def post(self, request):
post_data = json.loads(request.body)
Pathlabid = post_data.get('Pathlabid') or ''
lablist = []
labdict = {}
lab = pathlab()
labs = lab.apply_filter(Pathlabid = Pathlabid)
if Pathlabid :
for p in labs:
labdict["Pathlabid"] = p.Pathlabid
labdict["name"] = p.Name
labdict["email_id"] = p.Emailid
labdict["contact_no"] = p.BasicContact
labdict["alternate_contact_no"] = p.AlternateContact
labdict["bank_account_number"] = p.Accountnumber
labdict["ifsccode"] = p.IFSCcode
labdict["country"] = p.Country
labdict["homepickup"] = p.Homepickup
lablist.append(labdict)
return HttpResponse(json.dumps(lablist))
else:
for p in labs:
labdict["bank_account_number"] = p.Accountnumber
lablist.append(labdict)
return HttpResponse(json.dumps(lablist))
There are a number of issues with the overall approach and code but to fix the issue you're describing, but as a first fix I agree with the other answer: you need to take the return statement out of the loop. Right now you're returning your list as soon as you step through the loop one time, which is why you always get a list with one element. Here's a fix for that (you will need to add from django.http import JsonResponse at the top of your code):
if Pathlabid:
for p in labs:
labdict["Pathlabid"] = p.Pathlabid
labdict["name"] = p.Name
labdict["email_id"] = p.Emailid
labdict["contact_no"] = p.BasicContact
labdict["alternate_contact_no"] = p.AlternateContact
labdict["bank_account_number"] = p.Accountnumber
labdict["ifsccode"] = p.IFSCcode
labdict["country"] = p.Country
labdict["homepickup"] = p.Homepickup
lablist.append(labdict)
else:
for p in labs:
labdict["bank_account_number"] = p.Accountnumber
lablist.append(labdict)
return JsonResponse(json.dumps(lablist))
As suggested in the comments, using Django Rest Framework or a similar package would be an improvement. As a general rule, in Django or other ORMs, you want to avoid looping over a queryset like this and adjusting each element. Why not serialize the queryset itself and do the logic that's in this loop in your template or other consumer?
You are return the response in for loop so that loop break on 1st entry
import json
some_list = []
for i in data:
some_list.append({"key": "value"})
return HttpResponse(json.dumps({"some_list": some_list}), content_type="application/json")
Try above example to solve your problem
This is a rather silly issue but I stepped through the function but couldn't figure out what was causing the issue.
I was dynamically adding an attribute to the object I fetched from the db via SQLAlchemy and the objects were appended fine right before the return render_template was called, all but the last appended attribute in the variable from the list of found_survey.questions was lost. I.e. found_survey.questions[0].choices doesn't exist (but it did at one point), but found_survey.questions[-1].choices does.
I cannot seem to figure out what is going on... I thought it might be funky because of the database object (since I didn't commit survey, but I don't intend to, I was just appending the value so it's passed to the view with the correct logic).
Thanks so much for the help; I'm really stuck...
#app.route('/survey/<survey_id>', methods=['GET', 'POST'])
#login_required
def survey(survey_id):
form = UserSubmitForm()
found_survey = Survey.query.filter_by(id=survey_id).first()
if request.method == 'POST' and form.validate_on_submit():
print("input", form.answer_raw.data, form.category_list.data)
user_answer = Answer(note=found_survey.name,
answer_raw=form.answer_raw.data,
timestamp=datetime.utcnow(),
ip=request.remote_addr)
user_answer.user_id = g.user.id
user_answer.survey_id = survey_id
# other processing omitted
db.session.add(user_answer)
elif request.method != "POST":
for q in found_survey.questions:
q.choices = []
text_list = q.choice_text_string.split(',')
value_list = q.choice_value_string.split(',')
for i, text in enumerate(text_list):
q.choices.append((text, value_list[i]))
return render_template('survey.html',
form=form,
survey=found_survey,
is_editing=False)
Wouldn't it just be simpler to do this:
annotated_obj = []
for q in found_survey.questions:
text_list = q.choice_text_string.split(',')
value_list = q.choice_value_string.split(',')
question_choices = {}
for i, text in enumerate(text_list):
question_choices.setdefault(text, []).append(value_list[i])
annotated_obj.append((q, question_choices))
EDIT: FWIW, I am running django 1.3
I have...
class CreateProductWizard(FormWizard):
def get_template(self, step):
if step == 1:
return 'product/form_wizard/editor.html'
else:
return 'product/form_wizard/wizard_%s.html' % step
def process_step(self, request, form, step):
if step == 1:
self.extra_context = {'ptype': form.cleaned_data}
return
else:
return
def done(self, request, form_list):
# now that it's all together, store it.
return render_to_response('product/form_wizard/done.html',
{'form_data': [form.cleaned_data for form in form_list]},
context_instance=RequestContext(request))
and I'd like to get the self.extra_context to the template.
How do I get that on the template?
I've tried on the template:
{{extra_context}}
{{form.extra_context}}
{{form.extra_context.ptype}}
etc..
Looking at the docs i'd say that get_context_data is what you are after:
Returns the template context for a step. You can overwrite this method
to add more data for all or some steps. This method returns a
dictionary containing the rendered form step.
So what I ended up using on the template was:
{{ptype}}
which I had already tried.
The problem, and I'm still not sure why was that I had:
def process_step(self, request, form, step):
if step == 1:
self.extra_context = {'ptype': form.cleaned_data}
return
else:
return
and what worked was:
def process_step(self, request, form, step):
self.extra_context = {'ptype': 'hello!!',}
For some reason, the 'step' that is being passed to 'process_step()' is always == 0 which made my 'if step ==1:' logic fail...
After reviewing the source (django.contrib.formtools.wizard.FormWizard), one thing that looks like it could be failing on is my form is not valid. It must be valid for the step number to increment and call the process_step function. HOWEVER, the {{step}} variable is getting the right value. And I'm not doing anything crazy with the form...
So weird. But my main question is solved.
I have a form with checkboxes, the form functioning well, in my view i can use request.POST.getlist('list') to retrieve the list of values.
At the moment am trying to do some form validation inside the clean method and when i try to use self.cleaned_data['list'] I get the last value. I cannot retrieve the list of items.
Any idea how i could do that?
forms.py
class SelectList_Form(forms.Form):
list = forms.CharField(required=False)
def clean(self):
super(SelectList_Form, self).clean()
cleaned_data = self.cleaned_data
try:
# TODO: list validation
if cleaned_data['list'].__len__() is 0:
raise forms.ValidationError(_('Must select at least one of the lists below'),)
if cleaned_data['list'].__len__() > 1:
try:
# In here when i print list it only shows me the last value. It doesn't show me the list of values when the box is checked
print cleaned_data['list']
except Main.DoesNotExist:
raise Http404
except forms.ValidationError:
raise
class Posting_Wizard(FormWizard):
def render_template(self, request, form, previous_fields, step, context=None):
if step == 0:
obj = MainI18n.objects.filter(main__is_active=True, language=request.LANGUAGE_CODE).\
exclude(main__parent=None).order_by('main__parent').select_related(depth=1)
category_choices=dict(['%s,%s' % (i.main.slug, i.main.parent.slug), '%s - %s' % (i.main.parent,i.label)] for i in obj)
form.fields['categories'] = forms.CharField(widget=forms.RadioSelect(choices=category_choices.items()))
if step == 1:
category = request.POST.get('0-categories')
pobj = Main.objects.filter(slug=category.split(',')[1], parent=None).get()
cobj = Main.objects.filter(slug=category.split(',')[0], parent=pobj.id).get()
lobj = ListI18n.objects.filter(list__is_active=True, language=request.LANGUAGE_CODE, list__main__slug=category.split(',')[0], list__main__parent=pobj.id).select_related()
list_choices = dict([i.id, i.title] for i in lobj)
if cobj.mainproperties.relation == 'M':
# Here i generate the checkboxes
form.fields['list']=forms.CharField(widget=forms.CheckboxSelectMultiple(choices=list_choices.items()),label="Pick the list",)
else:
form.fields['list']=forms.CharField(widget=forms.RadioSelect(choices=list_choices.items()),label="Pick the list",)
return super(Posting_Wizard, self).render_template(request, form, previous_fields, step, context)
def done(self, request, form_list):
return HttpResponseRedirect(reverse('accounts-registration-wizard-done'))
def get_template(self, step):
return 'listing/post/wizard/wizard_%s.html' % step
First, there are a number of basic Python errors here. There is almost never a need to access the double-underscore functions - they are internal implementation details. Always use the normal len() function instead. And, never use is for comparisons: it's for identity, so should only be used with things you know have the same identity, which basically just means None. So your code should read:
if len(cleaned_data['list']) == 0:
etc.
Now, secondly, I don't understand why you think there could ever be more than one 'element' in list. You've defined it as a CharField, which is a single field containing many characters. Your len is testing the number of characters entered into that field, not the number of fields, however you think you've defined them.