django.urls.exceptions.NoReverseMatch: Reverse for 'word-detail' with no arguments not found

I there,
amazing community :slight_smile:

I am at the moment building a vocabulary quizzing app - too much has been said about this before …

I have a FormView in which I want to handle the form, and I will share all of the necessary steps:

class WordDetailView(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 - this needs to be implemented
    '''
    template_name = 'word_detail.html'
    form_class = AnswerWord
    #as I think I need to handle the success manually, here is None
    success_url = None
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        word_id = self.kwargs.get('pk')
        context['current_word'] = Word.objects.get(pk=word_id)
        return context

    def form_valid(self, form):
        current_word_id = self.kwargs.get('pk')
        current_word = Word.objects.get(pk=current_word_id)
        if form.cleaned_data['translation'] == current_word.text:
            random_word = Word.objects.exlcude(pk=current_word_id).order_by('?').first()
            if random_word:
                return redirect('word-detail', pk=random_word.pk)
        else:
            pass
        #handling of incorrect answers still has to be implemented
        return redirect('word-detail', pk=current_word_id)

Word-Model - in models.py:

class Word(models.Model):
    '''The translated representations of each AbstractWord
    word = the concept behind abstract
    text = the word in the language we are looking for.
    '''
    word = models.ForeignKey(AbstractWord, to_field='abstract_word', on_delete=models.CASCADE)
    language = models.ForeignKey(Language, on_delete=models.CASCADE)
    text = models.CharField(max_length=100, blank=True)
    definition = models.CharField(max_length=100, blank=True)

    def __str__(self):
        return f'{self.word}, {self.language}, {self.text}, {self.definition}'

the word_detail.html looks like this:

{% extends 'VocabTrainer/templates/base.html' %}

{% block content %}


    <form method="post" action=" {% url 'word-detail' %}">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Go</button>
    </form>


{% endblock %}

when testing the form I receive this error message:

django.urls.exceptions.NoReverseMatch: Reverse for ‘word-detail’ with no arguments not found. 1 pattern(s) tried: [‘word\-list/(?P[^/]+)/(?P[0-9]+)\Z’]

The error message still does not find any patterns, if I add more arguments (there should be a current_word.pk, if I did not make a mistake :-D)

The code absolutely runs when I empty the action=" {% url 'word-detail' %}">
Can anyone give me a hint, where I should check and change anything? I think I have shared all the important code-snippets and models. I have a somewhat bad deprecatedWordDetailView(DetailView) which I can shaer later on, but this would obfuscate the issue further :smiley:

I hope you can help me!

I thought I added the urls.py

urlpatterns = [
    path('', IndexView.as_view(), name='index'),
    path('word-list/', WordListView.as_view(), name='word-list-view'),
    path('word-list/<str:language>/', LanguageWordListView.as_view(), name='lang-word-list-view'),
    path('word-list/<str:language>/<int:pk>', WordDetailView.as_view(), name="word-detail"),
    ]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

All the best,

One of the important code snippet you should have shown is your urls.py file where the word-detail url is declared.
Based on what is present in the view, I guess this url path contains a pk parameter. This parameter is correctly filled when creating urls in the view, but this is not the case in your template.

You have to replace

By

action="{% url 'word-detail' pk=current_word.pk %}"
1 Like

added the urls.py in the question!

I figured that I forgot that, but problem is, as I stated (in an unclear manner)
that even if I add more to it, the error just follows the same pattern and adjusts to whatever I add:

Reverse for ‘word-detail’ with keyword arguments ‘{‘pk’: 3}’ not found. 1 pattern(s) tried: [‘word\-list/(?P[^/]+)/(?P[0-9]+)\Z’]

<div class="card">
    <form method="post" action="{%  url 'word-detail' pk=current_word.pk %}">
        {% csrf_token %}
        <fieldset>
            <legend>Word Bedeutung</legend>
            {{ form.as_p }}
            <button type="submit">Go</button>
        </fieldset>
    </form>
</div>

additionally, my templates folder structure:
image

thanks for your help, so far :slight_smile:

So, the url also expects a “language” argument you have to provide as well.

This means that your redirect calls in the view are incorrect too: they should include the language=self.kwargs[“language”] keyword parameter, or maybe current_word.language.<language name attribute>, depending on what the purpose of the language parameter is. As this parameter is not used in the view, it may also be removed from url… or if next random word is to be selected in the “current” language, you may have to change the query for your random_word by filtering words on the current language.

Finally, if you keep the language url parameter, you also have to define this keyword arg when using the url template tag (either filled from the current_word.language or from the current url parameters (using view.kwargs.language)

sorry for the long following post…

I have three models in total, which I should’ve shared before:

class AbstractWord(models.Model):
    '''Abstract word model that is the common ground for other words, contains the word_id and the english version of the word
    as an abstract representation of the word.
    '''
    word_id = models.BigAutoField(primary_key=True)
    abstract_word = models.CharField(max_length=100, blank=False, default="", unique=True)

    def __str__(self):
        return f'{self.word_id}, {self.abstract_word}'


class Language(models.Model):
    '''The language(s) that are supported for translations
    '''
    name = models.CharField(max_length=30)

    def __str__(self):
        return f'{self.name}'


class Word(models.Model):
    '''The translated representations of each AbstractWord
    word = the concept behind abstract
    text = the word in the language we are looking for.
    '''
    word = models.ForeignKey(AbstractWord, to_field='abstract_word', on_delete=models.CASCADE)
    language = models.ForeignKey(Language, on_delete=models.CASCADE)
    text = models.CharField(max_length=100, blank=True)
    definition = models.CharField(max_length=100, blank=True)

    def __str__(self):
        return f'{self.word}, {self.language}, {self.text}, {self.definition}, {self.pk}'

as you can see, I have the AbstractWord as “base class” which contains the meaning of the word in English, and a pk-field. (ID - I took some wrong turns, I think)

To also make it relevant to your response:

I will probably need the language-parameter to check for correct answers.

maybe my deprecatedWordDetailView(DetailView) makes it clearer what I am looking for:

class deprecatedWordDetailView(DetailView):
    def get(self, request, pk, language):
        word = Word.objects.get(pk=pk)
        language = language
        context = {
            'word' : word,
            'language':language
        }
        return render(request, 'VocabTrainer/templates/word_detail.html', context)
    def post(self, request, pk): # here the language is missing
        word = Word.objects.get(pk=pk)
        user_input = request.POST.get('word_input')
        context = {
            'word' : word,
            'is_submitted': True
        }
        if user_input == word.text:
            context['is_match'] = True
            # the magic of removing known words from the user-word-list needs to be here
            # replace the cranky random-id stuff below and fix it.
            random_id = random.randint(1, Word.objects.count())
            redirect_url = reverse('word-detail', kwargs={'pk': random_id, 'language': word.language })
            redirect_url_with_param = f'{redirect_url}?refresh={random_id}'

            return redirect(f'{redirect_url_with_param}')
        else:
            context['is_match'] = False

        return render(request, 'VocabTrainer/templates/word_detail.html', context)

somehow I was able to confuse myself with word.pk and the abstract-word.
my current code gives the word.pk, while the old code used the AbstractWord.word_id (as does my list-view dealing with listing the words, even in the language specific list:

class LanguageWordListView(ListView):
    '''users will pick a language later on and get a filtered list of elements which they can click'''
    template_name = 'VocabTrainer/templates/word_list.html'
    model = Word
    def get_queryset(self):
        language = self.kwargs['language'].lower()
        Word_all = Word.objects.all()
        words = Word_all.filter(language__name=language)
        return words
    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['words'] = self.get_queryset()
        context['language'] = self.kwargs['language'].lower()
        return context

Then, if the pk refers to the AbstractWord instead of Word, you may consider renaming the pk parameter of your url to word_id to make things clear.

I think your models should be updated to:

  • set unique=True in the definition of Language’s name field
  • add a unique constraint on the word + language fields of Word model.

Finally your queries to retrieve current word should be in the form Word.objects.filter(word_id=self.kwargs["word_id"], language__name=self.kwargs["language"]).select_related("language")
and to get next random word in the form
Word.objects.filter(language=current_word.language).exclude(word_id=current_word.word_id)

And url should be built using: word_id=current_word.word_id language=current_word.language.name

My head is spinning at the moment, I even ventured to chat-gpt - it’s surprisingly unhelpful :smiley:

can u maybe provide the spots where I should put it? I am a little overwhelmed

Updated views with your help:

class WordDetailView(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 - this needs to be implemented
    '''
    template_name = 'word_detail.html'
    form_class = AnswerWord
    #as I think I need to handle the success manually, here is None
    success_url = None
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        word_id = self.kwargs.get('pk')
        context['current_word'] = Word.objects.get(pk=word_id)
        return context

    def form_valid(self, form):
        current_word_id = self.kwargs.get('pk')
        current_word = Word.objects.filter(word_id=self.kwargs['word_id'], language__name = self.kwargs['language']).select_related('language')
        if form.cleaned_data['translation'] == current_word.text:
            random_word = Word.objects.filter(language=current_word.language).exclude(word_id=current_word.word_id)
            if random_word:
                return redirect('word-detail', pk=random_word.pk)
        else:
            pass
        #handling of incorrect answers still has to be implemented
        return redirect('word-detail', pk=current_word_id)
urlpatterns = [
    path('', IndexView.as_view(), name='index'),
    path('word-list/', WordListView.as_view(), name='word-list-view'),
    path('word-list/<str:language>/', LanguageWordListView.as_view(), name='lang-word-list-view'),
    path('word-list/<str:language>/<int:word_id>', WordDetailView.as_view(), name="word-detail"),
    #path('word-list/<str:language>/test-<int:pk>', newWordFormView.as_view(), name='word-form'),
    ]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

result:
Word matching query does not exist. for line 61

    template_name = 'word_detail.html'
    form_class = AnswerWord
    #as I think I need to handle the success manually, here is None
    success_url = None
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        word_id = self.kwargs.get('pk')
        context['current_word'] = Word.objects.get(pk=word_id) # this line
        return context

(the newFormView was an experiment by me, not successful, so I commented everything out, for now )

with your help (and a fresh start for the view I found a working (and cleaner) solution:

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
    Todo 2: Implement logic for forwarding
    '''
    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)
        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']
        if user_input == current_word.text:
            # if the user input is the correct value, we'll for now forward to the index
            self.success_url = reverse('index')
            return super().form_valid(form)

I know that handlung of incorrect answers etc. is stll to be implemented, but I have to go out, my significant other demands it! :smiley:

Thanks for your patience and help :slight_smile:

Well, let’s take this from the models point of view.

A Word can be identified in two ways:

  • its primary key (let’s call it technical id)
  • the AbstractWord it refers to for its specific Language: there should nod be several words refering to the same AbstractWord for a given language (hence the unique constraint I mentionned in my previous post). Let’s call it the natural key of a Word.

With that in mind, you have to make a decision on how you want to identify a Word in word detail urls: by its technical id or its natural key.

For first solution, the url for a word detail would be in the form:
word-detail/<pk:int>/

For the second solution, the url would be in the form:
word-detail/<word_id:int>/<language:str>/

Once the decision is made on thise two choices, each time you build a url for word detail (in the word list template, in the redirects of the word detail view or in thebword detail template), you have to pass the correct parameters to build the url.

In the detail view, you retrieve the current word either by its pk (for first solution) or using word_id + language (for second solution), but you xannot mix both identifiers like you didbin your previous post where you use pk as well as word_id. Also, you should not use self.kwargs.get(“pk”) when pk is expected to be in urls parameters; use self.kwargs[“pk”] instead, that would give you a clearer error about missing parameter when the code in the view is not in sync with the parameters you defined in url