Delyew Delyew - 1 month ago 10
Python Question

Django - trouble with separating objects by user and date

So I have these models:

excercises_choices = (('Bench Press', 'Bench press'),('Overhead Press', 'Overhead Press'), ('Squat', 'Squat'),
('Deadlift', 'Deadlift'))

unit_choices = (('kg','kg'), ('lbs', 'lbs'))


class Lifts(models.Model):

user = models.ForeignKey('auth.User', null=True)
excercises = models.CharField(max_length=200, choices=excercises_choices)
sets = models.IntegerField(null=True, blank=True)
reps = models.IntegerField(null=True, blank=True)
weight = models.FloatField()
unit = models.CharField(max_length=3, choices=unit_choices)
created_date = models.ForeignKey('Dates')
amrap_set = models.BooleanField(default=False)
amrap_rep = models.IntegerField(null=True, blank=True)

def __str__(self):
return self.excercises


class Dates(models.Model):
created_date = models.DateField(unique=True)

def __str__(self):
return str(self.created_date)


Let's say I have few lifts at different dates for admin and few lifts at different for xx user.
I want multiple lifts matching one date that's why I've made foreign key. (eg. 3 lifts to 2016-10-10 and 2 lifts to 2016-10-11).

Here is a view for showing it:

@login_required
def entries(request):
date = Dates.objects.all().order_by('-created_date')
lifts_by_user = Lifts.objects.filter(user=request.user)
return render(request, 'lift/entries.html', {'date': date,
'lifts_by_user': lifts_by_user})


And template:

{% extends 'lift/base.html' %}

{% block content %}
{{ user }}


{% if user.is_authenticated %}

{% for date in date %}
<p><strong><a href="{% url 'lift_date' pk=date.pk %}">{{ date }}</a></strong>
{% for i in date.lifts_set.all %}
{{ i }}

{% endfor %}

<a href="{% url 'new_lifts' %}">add new lift</a></p>
{% endfor %}
{% endif %}
<p>
<a href="{% url 'entries_delete' %}">Delete lifts or dates </a>
</p>
{% endblock %}


The problem is that I dont know how to separate it by dates AND by user.
This is how it looks like How do i keep this pattern date - lifts_to_that_date but for separate users? I dont want to see admin's entries while I am on test user

Answer

Have a look at the regroup template tag, it does exactly what you need.

You can do something like this in your view:

@login_required
def entries(request):
    lifts_by_user = (Lifts.objects.filter(user=request.user)
        .order_by('-created_date__created_date'))
    return render(
        request,
        'lift/entries.html',
        {'lifts_by_user': lifts_by_user}
    )

And replace the for date in dates loop in your template with something like:

{% regroup lifts_by_user by created_date.created_date as lifts %}
<ul>
    {% for day in lifts %}
        <li>Date: {{ day.grouper }}
            <ul>
                {% for lift in day.list %}
                    <li>{{ lift }}</li>
                {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

I've used a ul here so that it's easier to compare to the example in the docs, but obviously you can change the markup to whatever you need. It's important to know that regroup doesn't order its input, so you need to order by created_date in your view.

If you're using Django's dev version you can use this instead:

{% regroup lifts_by_user by created_date.created_date as lift_list %}
<ul>
    {% for day, lifts in lift_list %}
        <li>Date: {{ day }}
            <ul>
                {% for lift in lifts %}
                    <li>{{ lift }}</li>
                {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

Which I think is a little clearer.

As an aside, none of this relies on having dates stored as a foreign key, but that's up to you.

Questions from comments:

  1. order_by('-created_date__created_date') is joining Lifts to Dates through the Lifts.created_date foreign key and ordering by the Dates.created_date field. Have a look at https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships for details.

  2. for day, lifts in lift_list is using tuple unpacking. As a quick example:

    t = (1, 2, 3)
    # first, second, third will have values 1, 2, 3 respectively
    first, second, third = t
    

    {% regroup lifts_by_user by created_date.created_date as lifts_list %} produces a list of namedtuples (again, only in the dev version, if you're using 1.10 or earlier it's a list of dicts so you can't use this trick) so as you're iterating through lift_list you can unpack the date and list of lifts into separate variables.

  3. If you have a Lift instance called lift, you can get the pk for its date by using lift.created_date_id. Accessing it where you have the date URL in your example template is a little trickier because you have to get a lift out of the regrouped date's list. Something like this:

    {% regroup lifts_by_user by created_date.created_date as lifts %}
    <ul>
        {% for day in lifts %}
            <li>Date: {{ day.grouper }}
                {# day.list.0 gets the first lift for this day #}
                Date PK: {{ day.list.0.created_date_id }}
                <ul>
                    {% for lift in day.list %}
                        <li>{{ lift }}</li>
                    {% endfor %}
                </ul>
            </li>
        {% endfor %}
    </ul>
    
Comments