Two fields in a regroup grouper

Is it possible to put two fields in a grouper?? I need to display daynr and start_date here.

{% regroup shifts by weeknr as week_list %}
    {% for weeknr in week_list %}
        <p>Week nr: {{ weeknr.grouper }}</p>

          {% regroup weeknr.list by daynr as day_list %}
            {% for daynr in day_list %}
                <p>Day nr: {{ daynr.grouper }} ??{{ start_date }}??</p><!--2 FIELDS HERE-->
                    {% for shifts in daynr.list %}
                        <p>Shift: {{ shifts.daynr }} {{ shifts.start_date }}</p>
                    {% endfor %}
            {% endfor %}
    {% endfor %}

What “start_date” are you looking at rendering? Is it the start_date in daynr? If so, then the field you’re referencing is daynr.start_date.

We can’t provide a definitive answer without knowing what data you have in your context and what you’re looking to render.

Hi Ken,
The main question here is if it is possible to have 2 fields at this posistion. I think not.
I use a MySQL external database. I solved the problem by creating a view there with a CONCAT_WS combining the 2 fields and give it a new name ‘date_header’

Thanks again for your help!

This is my view:

class Shift_view(models.Model):
    status = models.ForeignKey('Status', default=1, on_delete=models.CASCADE)
    shift_title = models.CharField('Shift title', max_length=255)
    function = models.ForeignKey('Function', on_delete=models.CASCADE)
    start_date = models.DateField('Start date')
    start_time = models.TimeField('Start time')
    end_date = models.DateField('End date')
    end_time = models.TimeField('End time')
    site = models.ForeignKey('Site', blank=True, null=True, on_delete=models.CASCADE)
    user = models.ForeignKey('Shiftuser', blank=True, null=True, on_delete=models.CASCADE)
    weeknr = models.DateField('weeknr')
    daynr = models.DateField('daynr')
    date_header = models.CharField('Date header', max_length=100)

    class Meta:
        managed = False
        db_table = "shift_view"

At what position?

All you showed in your question was the rendering of a field within a for loop.

This isn’t a view, this is a model. This is the representation of where the data is stored. A view is the code that receives the request and renders and returns a response.

Cool! Yes, that’ll do what you’re looking to do.

However, I’d like to caution you against trying to inject too much logic into your templates. That’s generally not the best way to do things in Django. It’s usually a lot better to build your data structures in your view, allowing the template to just render what is being presented to it.

Now, your site may be small enough that it doesn’t matter, but given enough users and weeks and shifts, you may find this approach to start to bog down. (Just something to keep in mind. If you start encountering response-time issues on this page, this would likely be one of the first items to evaluate.)

Hi Ken, I like to share with you the working layout. I’m a little proud of the result.

This is my template code where “date_header” generates this code from the view:

<div class="day0"><div class="dayheader">Sunday 1-1</div>

If you have a suggestion how to remove some logic from the template to the view that would be much appriciated.

Here is my template:

{%  extends 'base.html' %}

{% block content %}

<div class="m20"><!--START PAGE-->

{% regroup shifts by weeknr as week_list %}
    {% for weeknr in week_list %}
       <div class="grid-container">
           <div class="weeknr"><h1>Week {{ weeknr.grouper }}</h1></div>
       </div>

          {% regroup weeknr.list by date_header as day_list %}

        <div class="grid-container shifts"><!--START DAY-->
            {% for date_header in day_list %}
                 {{ date_header.grouper|safe }}
                    {% for shifts in date_header.list %}

                        <!--START SHIFT-->
                             <div class="grid-item card" style="background-color: {{ shifts.status.color }}">

                                    <div class="flex-container inverse sb">
                                        <div><b>{{ shifts.start_date|date:'D j M Y'}}</b></div>
                                        <div>{{ shifts.start_time }} - {{ shifts.end_time }}</div>
                                    </div>

                                        <div>{{ shifts.function }} | {{ shifts.shift_title }}</div>
                                        <div><i>{{ shifts.user.first_name}} {{ shifts.user.last_name }}, <b>{{ shifts.user.Function_short }}</b></i></div>

                                    <div class="flex-container sb">
                                        <div>{{ shifts.site.Short_value }} - {{ shifts.site }}</div>
                                        <div><i class="bi bi-exclamation-circle-fill"></i></div>
                                    </div>
                             </div>
                        <!--END SHIFT-->
                {% endfor %}
                </div>
            {% endfor %}
        </div>
    {% endfor %}

</div><!--END PAGE-->

{% endblock %}

</body>
</html>```

So there are a couple different ways to reduce the amount of logic in the templates.

The first, and probably the easiest that comes to mind is using the ifchanged tag to identify when a day or week changes.

The other would involve creating a couple of model methods, where each method is responsible for returning the set of rows for the next level down. For example, you might have a method to return all weeks, return all days for a week, and return all shifts for a day. Each level of the loop then iterates over each of those collections.

Either way, the mindset that you want to foster is that the work belongs in the view and models, not the templates. In Django, the templates are a second-class citizen.

Hi Ken,
I like to share my ‘final’ solution that I’m working with now
Here is my view:

def show_weekdays(request):
    shifts = Shift.objects.all().order_by('start_date')

    weeks = []
    current_week = None
    previous_week_number = None

    for shift in shifts:
        week_number = shift.start_date.isocalendar()[1]
        weekday = shift.start_date.weekday()

        if previous_week_number is not None and week_number != previous_week_number:
            weeks.append(current_week)

        if week_number != previous_week_number:
            current_week = {'week_number': week_number, 'days': []}

        current_day = next((day for day in current_week['days'] if day['weekday'] == weekday), None)

        if current_day is None:
            current_day = {'weekday': weekday, 'start_date': shift.start_date, 'shifts': []}
            current_week['days'].append(current_day)

        current_day['shifts'].append(shift)
        previous_week_number = week_number

    if current_week:
        weeks.append(current_week)

    context = {'weeks': weeks}
    return render(request, 'shift-layout.html', context)

And this my template:

{% extends "base.html" %}

{% block shift-layout %}

{% load static %}

    <div>
        {% include "menu.html" %}
    </div>

<!--END WEEKNAV CONTAINER-->

<div class="m20"><!--START PAGE-->

    <div class="inline-row">
        <div>
            <input type="checkbox" id="select-all-checkbox">
            <label for="select-all-checkbox">Select All</label>
        </div>
        <button id="deselect-button" class="custom-button btn btn-primary btn-sm">Deselect All</button>
        <div id="selected-count">0 selected</div>
    </div>

    {% for week in weeks %}

        <div class="weeknr">
            <h1>Week {{ week.week_number }}</h1>

            <div class="grid-container shifts">

                {% for day in week.days %}
                    <div class="weekday day{{ day.weekday }}">

                            <div class="dayheader day{{ day.weekday }}">{{ day.start_date|date:'l j M Y' }}</div>

                            <!--START SHIFT-->
                            {% for shift in day.shifts %}
                                {% include shift.status.template_admin %}
                            {% endfor %}
                            <!--END SHIFT-->

                    </div>
                {% endfor %}

            </div>

        </div>

    {% endfor %}

</div><!--END PAGE-->
{% endblock %}

Do you think this will have the most ‘work’ in the view instead of the template?

1 Like