Django Nested Views

2020-05-01 02:10发布

I'm developing an internal application and I would like to be able to nest my views to keep everything nice and organized. I plan on doing this by keeping different parts of the page in their own HTML files with their own Views (separate sidebar and navbar, separate charts, etc).

views.py

from django.shortcuts import render
from django.views.generic import TemplateView
import Recall.data_logger.models as DLM


class ReportHome(TemplateView):
    template_name = 'data_logger/index.html'

class SelectorSidebar(TemplateView):
    template_name = 'data_logger/sidebar.html'

    def get(self, request, *args, **kwargs):        
        companies = DLM.Company.objects.order_by('company_name').all()

        return render(request, self.template_name, {'companies':companies,})

index.html

<html>
    <head></head>
    <body data-gr-c-s-loaded="true">
        {% include 'data_logger/navbar.html' %}

        <div class="container-fluid">
            <div class="row">
                {% include 'data_logger/sidebar.html' %} <!-- This is the part I need help with-->
            </div>
        </div>
    </body>
</html>

sidebar.html

<div class="col-sm-3 col-md-1 sidebar">
    <ul class="nav nav-sidebar">
        {% for company in companies %}
            <li><a href="#">{{ company.company_name }}</a></li>
        {% endfor %}
    </ul>
</div>

I understand that by just using {% include 'data_logger/sidebar.html' %} it's just loading the HTML and bypassing SelectorSidebar, how do I direct it through the View? I'd like a solution that allows me to access anything from a simple list of names to relitively large datasets being fed into a D3 chart.

Solution

This is what I ended up using:

index.html

<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"
                integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
                crossorigin="anonymous"></script>
        <script>            
            $.get("_sidebar", function(data, status){
                $("#_sidebar").html(data);
            });
        </script>
    </head>
    <body data-gr-c-s-loaded="true">
        {% include 'data_logger/navbar.html' %}

        <div class="container-fluid">
            <div class="row" id="_sidebar"></div>
        </div>
    </body>
</html>

Where _sidebar is the URL to SelectorSidebar:

urlpatterns = [
    path('', v.ReportHome.as_view(), name='ReportHome'),
    path('_sidebar', v.SelectorSidebar.as_view(), name='SelectorSidebar'),
]

2条回答
在下西门庆
2楼-- · 2020-05-01 02:48

As @baxeico answered, you can't have multiple views to serve a page, because one HTTP request is one view.

If you have content that needs to appear on a lot of pages, like your sidebar, and that content also requires some context information to render (like a list of companies to fetch from the db), you have two options:

  1. If the stuff required to add to the sidebar is fairly limited, create a template context processor that you add to the list of context processors in your settings (TEMPLATES setting).

    def sidebar_context(request):
        return {'companies': DLM.Company.objects.order_by('company_name').all()}
    

    and in your settings, you'd add something like 'myapp.custom_contexts.sidebar_context' at the top of the list.

    Now, every single template has access to the context variable companies, including your sidebar template.

  2. If the stuff shown in the sidebar is more dynamic, or more complex, you should consider fetching the data from within the browser using AJAX. You would create a view that returns JSON instead of HTML and in your sidebar template add javascript to fetch the data and populate the sidebar.

    The view is as simple as your current one:

    def sidebar(request):
        return JsonResponse({'companies': Company.objects.all().values('name', 'id')})
    

    which will return a list of dicts containing name and id of each company. In your AJAX handler for the successful response (which receives the data), you can then loop through data and access data[i].name and data[i].id which you can use to populate your list.

    I won't go as far as posting the full javascript (please search for jQuery, ajax and django) but here's a bit to give you an idea, assuming jQuery:

    $(window).on('load', function() {
          $.ajax({
            url: "{% url 'sidebar' %}",  // assuming this is inside a template, if not {% url %} won't work and you'll have to get it in a different way
            success: function(data) {
              if (data.length > 0) {
                for (var i=0; i<data.length; i++) {
                    var elem = $("<li>" + data[i].name + "</li>")
                    $("#companies").append(elem)
              }
            }
          })
    })
    
查看更多
SAY GOODBYE
3楼-- · 2020-05-01 02:49

I think you are making some confusion on how Django templates and views work together.

In very simple terms a Django template is what defines the HTML code that makes up a page. You can keep your templates very modular and organized; to do this you can use the include template tag or you can use template inheritance, which is a very powerful way to have "modular" templates.

A Django view is basically a function (or a class of you are using class based views) that receive an HTTP request and build an HTTP response. It doesn't make much sense to have "nested" views because usually you have just one HTTP request and you want to build just a response with the HTML needed to display the page.

So I think that you can happily use Django templates to put together all the modules that make up your page (header, sidebar, etc.), but each page should correspond to a single Django view.

Another approach could use AJAX and Javascript to make different HTTP requests and build up the page client-side, but I think that this is not the approach you are considering here.

查看更多
登录 后发表回答