Search code examples
pythondjangolistview

problems with get_context_data in ListView (django)


I need to show in a template two models:

models.py:

class Dimension(TimeStampedModel):

    level = models.ForeignKey('Level', verbose_name=_('Level'), on_delete=models.CASCADE)
    name = models.CharField(verbose_name=('Name'), max_length=200)
    active = models.BooleanField(verbose_name=_('Active'), default=True)
    sort_order = models.PositiveIntegerField(verbose_name=_('sort order'), default=0)

    class Meta:
        verbose_name = _('Dimension')
        verbose_name_plural = _('Dimensions')
    
    def __str__(self):
        return self.name

class Subdimension(TimeStampedModel):

    dimension = models.ForeignKey('Dimension', verbose_name=_('Dimension'), on_delete=models.CASCADE)
    name = models.CharField(verbose_name=('Name'), max_length=200)
    active = models.BooleanField(verbose_name=_('Active'), default=True)
    sort_order = models.PositiveIntegerField(verbose_name=_('sort order'), default=0)

    objects = managers.SubdimensionManager()
    
    class Meta:
        verbose_name = _('Subdimension')
        verbose_name_plural = _('Subdimensions')
    
    def __str__(self):
        return self.name

and created a ListView of this

views.py

class DimensionListView(generic.ListView):
    model = models.Dimension
    template_name = 'dimension.html'
    context_object_name = 'dimensions'


    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        self.user = self.request.user
        self.level = self.get_level(pk=kwargs.get('level_pk'))         
        return super(DimensionListView, self).dispatch(request, *args, **kwargs)

    def get_level(self, pk):
        level = get_object_or_404(models.Level, pk=pk)
        return level

    def get_queryset(self):
        queryset = super(DimensionListView, self).get_queryset()
        return queryset.filter(active = True, level = self.level)

    def get_context_data(self, **kwargs):
        context = super(DimensionListView, self).get_context_data(**kwargs)
        context['subdimensions'] = models.Subdimension.objects.filter(active=True, dimension__level=self.level )
        return context


dimension_list_view = DimensionListView.as_view()

I need to created a filter of the same 'dimension' so that in the template show only the subdimensions of that dimension.

my template dimension.html:

{% include 'base.html'%}
{% block content %}
<div class="row">
    {% for dimension in dimensions %}
    <div class="col">
        <div class="card" style="width: 18rem;">
        <a class="">{{dimension.name}}</a>
        <div class="card-body"> 
        <ul>
        {% for subdimension in subdimensions %}
        <li>{{subdimension.name}}</li>
        {% endfor %}
        </ul>
        </div> 
        </div>
    </div> 
    {% endfor %}
</div>
{% endblock %}

but if u notice, show all the subdimensiones in all cards, not only the subdimension of these dimension.

Manager.py only returns a objects with filter active=True and order_by('sort_order')


Solution

  • You can do it with inefficient rendering, but if you have 100-200 subdimensions - it should not be a problem.

    {% for dimension in dimensions %}
    <div class="col">
        <div class="card" style="width: 18rem;">
        <a class="">{{dimension.name}}</a>
        <div class="card-body"> 
        <ul>
        {% for subdimension in subdimensions %}
        {% if subdimension.dimension == dimension %}
        <li>{{subdimension.name}}</li>
        {% endif %}
        {% endfor %}
        </ul>
        </div> 
        </div>
    </div> 
    {% endfor %}
    

    Alternatively, you can annotate all dimensions with corresponding subdimensions in the view.

    # views.py
    class DimensionListView(generic.ListView):
        ...
        
        def get_queryset(self):
            queryset = super(DimensionListView, self).get_queryset()
            # Save db hits with `prefetch_related`
            return queryset.filter(active = True, level = self.level).prefetch_related('subdimension_set')
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            dims = list(context['dimensions'])
            for d in dims:
                d.all_subdimensions = list(d.subdimension_set.all())
            context['dimensions'] = dims
            return context
    

    and then in template

    {% for dimension in dimensions %}
    <div class="col">
        <div class="card" style="width: 18rem;">
        <a class="">{{dimension.name}}</a>
        <div class="card-body"> 
        <ul>
        {% for subdimension in dimension.all_dimensions %}
        <li>{{subdimension.name}}</li>
        {% endfor %}
        </ul>
        </div> 
        </div>
    </div> 
    {% endfor %}
    

    (... or switch to Jinja2 template rendering to access dimension.subdimension_set.all() directly in template)

    Also you can have a look at LoginRequiredMixin instead of method_decorator(login_required)