Search code examples
pythondjangoperformancemodularity

Future-proofing my application code for readability (Django, Beginner)


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?


Solution

  • 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()