The FormView is getting out of hand

Hi there, besides some minor issues my app now runs really well…
the minor issues:

I have messages that I want to display after a correct input has been received and the form submitted and then hidden after forwarding the word.

and more importantly,
Is it normal that the CBV really gets absolutely crazy or is it bad code?

class WordFormView(FormView):
    '''shows the form to enter the meaning of the word.
    Forwards to a random new word, once the word is answered correctly.
    After three incorrect guesses, the word should be skipped
    Todo 1: Implement logic for wrong answers
    '''
    template_name = 'VocabTrainer/templates/word_form.html'
    form_class = AnswerWord
    success_url = None


    def get_context_data(self, **kwargs):
        '''set the context data accordingly - now we can use the current_word in word_form'''
        context = super().get_context_data(**kwargs)
        word_id = self.kwargs.get('pk')
        context['current_word'] = Word.objects.get(pk=word_id)
        context['learned_words'] = self.request.session.get('learned_words', [])
        return context


    def form_valid(self, form):
        '''checks whether the form is valid'''
        # user_input defines that the field is the form field 'translation'
        user_input = form.cleaned_data['translation']
        # here we pass the current word from get_context_data into this function
        current_word = self.get_context_data()['current_word']
        # save the current word language in a variable current language
        current_language = current_word.language
        if user_input == current_word.text:
            messages.success(self.request, 'correct!')
            learned_words = self.request.session.get('learned_words', [])
            learned_words.append(current_word.pk)
            self.request.session['learned_words'] = learned_words

            #mark the current word as learned
            Word.objects.filter(pk=current_word.pk).update(learned=True)
            # if the user input is the correct value, forward to a random word:
            # filter Word by the language line that we are in
            language_words = Word.objects.filter(language=current_language).exclude(learned=True)
            # remove the current_word.pk so that the next word will not be the same
            #all_language_words_except_current = language_words.exclude(pk=current_word.pk)

            # Todo 2: Implement a list with "learned words" and put the current word if answered correctly to "learned
            #  words" and remove it from the word-list
            #  maybe user handling first with users having their own list of "known words and unknown words
            if language_words.exists():
                #
                random_word = random.choice(language_words)
                self.success_url = reverse('word-form', kwargs={'language':current_language, 'pk':random_word.pk})
            else:
                Word.objects.all().update(learned=False)
                return HttpResponseRedirect(reverse('index'))
                # I don't like that I'm repeating myself here
        else:
            messages.error(self.request, 'try again')
            return self.render_to_response(self.get_context_data(form=self.form_class()))
        return super().form_valid(form)

the accompanying word_form.html:

<div class="card">
    <form method="post" action="">
        {% csrf_token %}
        <fieldset>
            <legend> <h3> <em>{{ current_word.word.abstract_word }}</em> in {{ current_word.language }}</h3> </legend>
            <em> {{ current_word.definition }}</em>
            {{ form.as_p }}
            <button type="submit">Check</button>
        </fieldset>

    </form>

        {% if messages %}
            {% for message in messages %}
                <span class="message">{{ message }}</span>
            {% endfor %}
        {% endif %}
</div>

For the first “small isue”: I want the messages to be displayed in between the clicking and the forwarding, which I seem unable to achieve. I am sure there is a solution (could be JS, even though I think there should be a way to get this without js - I tried time.sleep but that breaks alot of the code…

regarding the worse issue: readability… I think the code is rather long and I fear I am doing “too much” in the cbv-logic, what are your thoughts?

I’m not sure I understand exactly what you’re trying to say here.

A person presses a submit button. That button results in a POST to a view. The view returns a page.
In the context of this sequence of events, what do you want to see happen?

Are you looking for an intermediate page to be displayed with your message, and then proceed on to another page?

If not that, then what?

Not at all. Not anywhere close. You have business logic that needs to be performed, and it’s got to be done somewhere. The length of this function is far from being a problem.

Now, having said that, I will point out a couple things.

  1. You’re setting the learned field to True on the Word object. However, that object appears to be global. That means that when one person has learned a word, everyone is assumed to have learned it. (You have not associated that learned flag with the user who has learned it.)

  2. You don’t need to do this:

The object current_word is the Word instance to be updated. You can replace that query with:

current_word.learned = True
current_word.save()
  1. You’re creating this learned_words list in the session, but you don’t appear to be using it (at least not in this view).

  2. This query, and the work you’re doing with it:

could end up consuming a fair amount of memory every time this view gets called.

Rather than getting a full list of objects, get just the list of PKs to select a random pk from the list, then a query to retrieve that specific row.

  1. You have a comment that you “don’t like that I’m repeating myself.” My opinion is that expressiveness and clarity are far more important than brevity-for-the-sake-of-brevity. (I’m also not sure what you say you’re repeating yourself. I don’t see any unnecessary repetition here.)

Before I forward to the next word there should be a message that is subsequently not displayed again.

thank you.

yeah, I have a user already, but not implemented the logic to tie it to the user, yet.

from what I see here, I should rework this, keeping your info in mind!

yeah, this comment was for my mind and I no longer repeat myself :slight_smile:
in the style-book for work, we also follow the “clarity over brevity” - I am conflicted. with CBVs I feel like I have to document a lot, so that whenever I have time to work on this app, I still understand what I did there :smiley:

Again, you need to be more precise here as to what you want to see. Translate this intent into what this means in terms of pages and/or html.

As I asked before:

Or are you looking for something else to happen?

I am at a loss :smiley:

I want to have a message displayed under the form like in this picture:

problem is that it says “correct” even when I haven’t entered anything. Furthermore, the “correct” should not be visible on the next word.

so the timeline is roughly like this:
0) form is displayed without message (neither correct nor try again)

  1. click on Check
  2. correct or try again is displayed - depending on whether the answer was correct
  3. if the input was correct → forward to next random word; if it wasn’t do nothing (yet)
  4. next word - form starts with no visible messages (neither correct or try again)

Imagine flash-messages (https://codepen.io/arthurra/pen/ExJzLw ) maybe imagining isn’t that bad of an idea, bcs I want to add these later on, the correct and try again was just an early replacement :smiley:

Good - so then you want the message appearing on the same page.

To accomplish this, you’ll need to submit the guess via AJAX, and receive the result in your JavaScript. Your JavaScript would then set the appropriate message and redirect you accordingly.

1 Like