ListView not using correct context

Hi there,

I have recently started building a vocabulary app and was amazed by the competence and helpfulness of this community so that I came back today with another issue :slight_smile:

I have this CBVs

class LanguageWordListView(ListView):
    def get(self, request, language):
        words = Word.objects.all().filter(language__name= language)
        context = {
            'words': words,
            'language':language

        }
        return render(request, 'vocabTrainer/word_list.html', context)

class WordListView(ListView):
    def get(self, request):
        words = Word.objects.all()
        return render(request, 'vocabTrainer/word_list.html', {'words':words})

and the following template:

<h1> Hi from the <span>{{ language }}</span>  word list</h1>

    <ul>
        {% for word in words %}
            <li><a href="{% url 'word-detail' word.pk %}">{{ word.word.abstract_word }}, {{ word.language.name }}</a></li>
        {% endfor %}
    </ul>

Both CBVs should roughly do the same - render a selection of words, while WordListView should render all words, the LanguageWordListView should only render those of a certain language, thus I filter all the words for language and get the intended result for {{ language }} but not for the words.
I assume this is because filter-method changes the object’s structure somehow.
in the console I tried the following:

word = Word.objects.all()
test = word.filter(language__name = 'german')

for word in test:
    print(word.text)

and I get the intended “Morgen” or when I print word.word I get “morning” (which is my “base word”)

So I cannot see what might be wrong here …

any ideas?

Should I use two different templates? seems kinda “too much”, given how similar the purpose of these is.

edit: left out an important part of the template.

as requested, the models.py:

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}'

and additionally 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"),
    path('word-list/<int:pk>', WordDetailView.as_view(), name="word-detail"), 
    ]

First, for altering a ListView, you don’t want to override get unless it’s necessary. What you’re doing here is short-circuiting 90% of what a ListView can provide - you might as well be using View as your base class.

For changing the queryset, you would override get_queryset, and for changing the context override get_context_data.

If you’re not really familiar with the internal structure of the CBVs, I suggest you take some time to read through the source code for them. I also always recommend people become familiar with the Classy Class-Based Views site along with the CBV diagrams page.

I’m not following you at this point as to what you’re saying is wrong.

In your template you have:

Which is going to render the base word.

If you’re looking to render the foreign language word, you would need to render word.text as you go on to demonstrate.

whoops, that’s not whatI wanted nor what I should do. will re-work it!

I think my edit makes it more clear, I added the line where I get the {{ language }}

Do I understand this correctly?
Instead of my

class LanguageWordListView(ListView):
    def get(self, request, language):
        words = Word.objects.all().filter(language__name= language)
        context = {
            'words': words,
            'language':language

        }
        return render(request, 'vocabTrainer/word_list.html', context)

I should have sth. along the lines of this:

class LanguageWordListView(ListView):
    template_name = 'word_list.html'
    model = Word
    def get_queryset(self):
        language = self.kwargs['language']
        words = Word.objects.all().filter(language__name=language)
        return words
    def get_context_data(self,**kwargs):
        context = super().get_context_data(**kwargs)
        context['language'] = self.kwargs['language']

        return context

I just wonder how I get the words to be displayed and I think the issue is me using self.kwargs[‘language’] in the get_queryset … but no idea how to fix it

also:
adjusted the WordListView:

class WordListView(ListView):
    model = Word
    template_name = 'word_list.html'
    context_object_name = 'words'
    def get_queryset(self):
        words = Word.objects.all()
        return words

how about this?

Yes, you views look a lot better here.

But it’s still not clear to me what the issue is.

What are you trying to produce?

What are you getting?

It may also help if you posted the models involved.

Thanks :slight_smile:

I added the models.py in the opening post, so that all necessary information is in one spot.

For now, i have added languages and words in these languages with an AbstractWord as a base (english version of the word) and in the LanguageWordListView, I want to show only words whose language is {language}

class LanguageWordListView(ListView):
    template_name = 'word_list.html'
    model = Word
    def get_queryset(self):
        language = self.kwargs['language']
        Words = Word.objects.all()
        words = Words.filter(language__name=language) # here I think, I am stuck
        return words
    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['language'] = self.kwargs['language']
        return context

and this returns the list but without the word.word.abstract_word or word.pk that are mentioned in the template:


<h1> Hi from the <span>{{ language }}</span>  word list</h1> <!-- this {{ language}} is replaced with whichever value I enter -->

    <ul>
        {% for word in words %}
            <li><a href=" {%  url 'word-detail' word.pk %}">{{ word.word.abstract_word }}</a></li>
        {% endfor %}
    </ul>

What is your url definition for this view?

How is this view being called? (Is it a link on another page, for example?)

What is the url being requested for your test case here?

Let me try to answer this:

http://127.0.0.1:8000/word-list/German/

it is at the moment me typing it in

for completeness:
the urls.py (I will also add this up top)

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"),
    path('word-list/<int:pk>', WordDetailView.as_view(), name="word-detail"), 
    ]

And you can verify that you have an entry in your Language model where name == 'German' instead of 'german', which is what you show here?

In the console I get the corelating results, yes.

I just realised that it capitalises the output. but I don’t see where that might happen. I have literally zero places where I capitalise anything …

for reference, maybe the whole repo helps?

The output doesn’t matter.

The issue is that you apparently have “german” in your database which is why the test query works - but you’re entering the url with “German”, which is not the same.

obviously it’s not the same!
Now I have to search where this might unintentionally happen :-/

edit: apparently the following:

    def get_queryset(self):
        language = self.kwargs['language'] # if I add .lower() it is not capitalised.
        Words = Word.objects.all()
        words = Words.filter(language__name=language)
        return words
    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['language'] = self.kwargs['language']# same as before
        return context

do you by chance know, why it does this?

Does what? I’m not following what you’re trying to say here.

Sorry, It’s late over here …

Capitalise in the line

‘german’ in this line:

language = self.kwargs['language']
In the db, I checked, it’s not capitalised…

In get_context_data it’s the same, but I don’t get why it would happen :sweat_smile:

Thank you for your immense time and help!

To help people coming here in the future:

fixed LanguageWordListCiew(ListView):

class LanguageWordListView(ListView):
    template_name = '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

let me try to explain:

apparently django capitalises the string that is the right side of language = self.kwargs['language'].lower() thus I added the .lower() and I had to add the context[‘words’] in the get_context_data which I had not added in the under model = Word beforehand.

  1. do you really have all the context that you need?
  2. did u think of django capitalising strings?
  3. sometimes an added print(context) or print(language) would have helped me.

with the amazing help of @KenWhitesell I was able to identify the issue and then I spent some time trying and tinkering …