michjnich
michjnich

Reputation: 3385

Include class based ListView as a template snippet in a TemplateView

I want to have the following:

A template view (actually my landing page), that contains a number of items, including a list view.

Now, I have a list view that works when I map it to it's own url:

app/views.py

class MymodelListView(ListView):
    model = Mymodel
    context_object_name = "mymodel_list"

app/urls.py

app_name = "myapp"
urlpatterns = [
 ...
    path("mlist/", MymodelListView.as_view(), name="mlist"),
 ...
]

app/../mymodel_list.html

{% extends 'base.html' %}
{% block content %}
<ul>
    {% for adventure in adventure_list %}
    <li>
        <a href="{{ adventure.get_absolute_url }}">{{ adventure.title }}</a>
    </li>
    {% endfor %}
</ul>
{% endblock content %}

Now this works as expected, and displays all records from Mymodel at the "mlist" link.

However, I'd like to embed this into my index.html TemplateView. My thought was to use a snippet and have this in index.html

{% include 'myapp/_mymodel_list.html' with mymodel_list=mymodel_list %}

(Not sure if the "with" part is required, but it seems like I should need to pass the list from the main template to the snippet. Doesn't work either way anyway at the moment due to more basic issues).

So then I have my main homepage view setup as follows:

class HomePageView(TemplateView):
    template_name = "index.html"

    def get_context_data(self, *args, **kwargs):
        context = super(HomePageView, self).get_context_data(*args, **kwargs)
        context["adventure_list"] = MymodelListView.get_context_data()
        print(context)
        return context

But this crashes with:

Exception Type: TypeError at /
Exception Value: super(type, obj): obj must be an instance or subtype of type

Same if I pass self into MymodelListView.get_context_data(self).

Now as far as I can see, the Mymodel get context is confused because it's being passed a HomePage context and it's all a bit wrong.

Is what I'm trying to do just completely wrong? Is that why I can't find any helpful hints with my (usually reasonably reliable) google-fu? If so, what approach should I be taking instead?

I'd like to re-use the Mymodel list view in other pages (albeit possibly changing the filtering criteria), hence my attempt to do it like this, in a DRY fashion.

Upvotes: 0

Views: 505

Answers (1)

Jacinator
Jacinator

Reputation: 1413

The problem you're seeing is that you're trying to call a method of MymodelListView with an instance of HomePageView. Since HomePageView doesn't inherit from MymodelListView Python can't do that and is throwing an error. You have a few ways that you can solve this.

myapp/views.py

# The first approach is to use Django's included mixin to get the
# queryset and include it in the context. This is a bit redundant and
# won't include any logic used in your ListView.

from django.views.generic.list import MultipleObjectMixin


class HomePageView(MultipleObjectMixin, TemplateView):
    context_object_name = 'adventure_list'
    model = Mymodel
    template_name = 'index.html'


# Your second option is to manually include the queryset. Again, this
# won't include any login from the ListView.

class HomePageView(TemplateView):
    template_name = 'index.html'

    def get_context_data(self, **kwargs):
        kwargs.setdefault('adventure_list', Mymodel.objects.all())
        return super().get_context_data(**kwargs)


# This is the option that I would suggest. Inherit from the ListView
# that you've already written and give it its own template and context
# name. I'm pretty sure this should do exactly what you want.

class HomePageView(MymodelListView):
    context_object_name = 'adventure_list'
    template_name = 'index.html'

Another issue I foresee is that you're trying to include an entire HTML file. I doubt that will work well for you. You're probably better off putting your list inside a file specifically to be included and then using that in both places. The only think you'll duplicate is the include tag.

templates/index.html

...
<!-- If the context name doesn't change you don't need to assign it and
Django will render the included file correctly. -->
{% include 'myapp/include_list.html' %}
...

templates/myapp/mymodel_list.html

{% extends 'base.html' %}

{% block content %}
  {% include 'myapp/include_list.html' with adventure_list=mymodel_list %}
{% endblock %}

templates/myapp/include_list.html

<ul>
  {% for adventure in adventure_list %}
    <li><a href="{{ adventure.get_absolute_url }}">{{ adventure.title }}</a></li>
  {% endfor %}
</ul>

Upvotes: 2

Related Questions