0

I have a need for counting the standings of a sport team based on the total points they have their division.

Each team has a total points and division assigned to them.

views.py

def seasonstandings(request):
    divisions = Team.objects.order_by().values_list('division__name',flat=True).distinct()
    stats = WeeklyStats.objects.values('player__team__team_name').annotate(
        team=F('player__team__team_name'),
        points = Sum('finishes'),
        division = F('player__team__division__name')
    ).order_by('division','-points')

    return render(request, 'website/seasonstandings.html', {
        'divisions_and_stats': [[division, [stat for stat in stats if stat.division == division]] for division in divisions]
    })

My Django template code is as follows:

seasonstandings.html

      {% for division in divisions %}
      <h4>{{ division }}</h4>
      <table class="table">
        <tr>
          <td align="center">Place</td>
          <td>Team</td>
          <td align="center">Points</td>
        </tr>
        {% for stat in stats %}
          {% if stat.division == division %}
          <tr>
              <td width="10%" align="center">{{ forloop.counter}}</td>
              <td width="70%">{{ stat.team }}</td>
              <td width="20%" align="center">{{ stat.points }}</td>
          </tr>
          {% endif %}
        {% endfor %}
        </table>
      {% endfor %}

The problem right now is that say I have 6 teams and 3 are in Division A and 3 are in Division B.

Because it is separating the teams based on division it is showing the forloop.counter as 1 through 6 on the first forloop for divisions. What I want it to do is only do the forloop counter for the nested forloop ( the second one for stats) so that it shows places 1 through 3 for Division A and 1 through 3 for Division B.

The results I am getting are:

Division A
Place   Team                        Points
1       Timberlea Beverage Room 1   7
3       Lakeside Legion 1           4
4       Bedford Legion              3

Division B
Place   Team                        Points
2       Lakeside Legion 2           4
5       Purcells Cove Club 1        2
6       Army Navy Air-Force Club    None

When I am hoping to get get the results to look like this:

Division A
Place   Team                        Points
1       Timberlea Beverage Room 1   7
2       Lakeside Legion 1           4
3       Bedford Legion              3

Division B
Place   Team                        Points
1       Lakeside Legion 2           4
2       Purcells Cove Club 1        2
3       Army Navy Air-Force Club    None

After some more looking into this, I think it is my IF Statement causing the issue where it will skip a record if the division does not match. I can't think of a way to get it to count the numbers in order.

Zoe
  • 27,060
  • 21
  • 118
  • 148
jAC
  • 3,155
  • 3
  • 18
  • 29
  • https://stackoverflow.com/a/2376548/8150371 and docs https://docs.djangoproject.com/en/dev/ref/templates/builtins/#for – Stack Jan 14 '18 at 15:26
  • None of these suggestions work. I did search the docs and stack first to make sure it wasn't duplicating (from what i found). However, this just made me think a little bit more. I think its my IF Statement causing this as it would technically be skipping 3 from the list in each division. Any thoughts on getting it to count in order with the IF statement skipping some of them? – jAC Jan 14 '18 at 16:12
  • hey, you have to use django custom filters, check this example https://stackoverflow.com/a/8948617/8150371 , you can write one similar to this which will return the `index of an element` – Stack Jan 14 '18 at 16:33

2 Answers2

1

Even though you have an if statement to ensure that stats from other divisions don't get shown, the for loop counter still increments even when stat.division == division returns False.

Consider working out the places in Python, and use that in the template instead of trying to work it out in the template. Here's a possible solution.

Python (assuming you have sorted the stats set previously):

context = {
    # ...
    'divisions_and_stats': [[division, [stat for stat in stats if stat['division'] == division]] for division in divisions]
    # ...
}

Depending on your preference/need, you could use stat.get('division', None) so you don't run into any KeyError exceptions.

Template:

{% for division, stats in divisions_and_stats %}
<h4>{{ division }}</h4>
<table class="table">
    <tr>
        <td align="center">Place</td>
        <td>Team</td>
        <td align="center">Points</td>
    </tr>
    {% for stat in stats %}
    <tr>
        <td width="10%" align="center">{{ forloop.counter }}</td>
        <td width="70%">{{ stat.team }}</td>
        <td width="20%" align="center">{{ stat.points }}</td>
    </tr>
    {% endfor %}
</table>
{% endfor %}
Zoe
  • 27,060
  • 21
  • 118
  • 148
cnfw
  • 770
  • 2
  • 11
  • 28
  • This looks like it could work and i do have my list sorted but i am getting 'dict' object has no attribute 'division'. I have added my code for the view above. – jAC Jan 16 '18 at 00:28
  • Unless you need it elsewhere in your template, the line in your Python file ```'stats':stats,``` shouldn't be needed. Also, did you remember to update your template code (```'website/seasonstandings.html'```) with the example in the answer? – cnfw Jan 16 '18 at 09:03
  • yeah template code was updated but i get this error if i point it to a blank template. It looks to me as if it cannot find division in the dictionary. Is it an issue with my view with the stats variable? It does have a division attribute so i am not sure why it is choking there. Any thoughts? – jAC Jan 16 '18 at 11:53
  • I think i see the issue. When i debug it the resutls from your query returns "{'player__team__team_name': None, 'team': None, 'points': 14, 'division': None}" which is a player that is not associated to a team so it gives a null division because the divisions are assigned to teams not players. Not sure how to get around that as the concept here is to allow for spares that are not part of a any team. Thoughts there? – jAC Jan 16 '18 at 12:01
  • Ah, so I didn't have the full picture of how your data is structured when I initially wrote my answer, so apologies for that! From what I understand now, you want to skip an entry if the division is ```None```? You could try to simply not include a division in the list where ```division is None```. For example: ```[[division, [stat for stat in stats if stat.division == division]] for division in divisions if division is not None]``` – cnfw Jan 16 '18 at 16:34
  • Or could you use [```.exclude()```](https://docs.djangoproject.com/en/2.0/ref/models/querysets/#exclude) when getting your QuerySets? For example, ```.exclude(player__team__team_name=None)``` – cnfw Jan 16 '18 at 16:36
  • odd. Your suggestions both make sense to be but when i try them (either of them) i still get the "AttributeError at /seasonstandings/ 'dict' object has no attribute 'division'". Now looking at the debug logs it shows that its there. and the result is ".0 division 'Division A' stat {'division': 'Division A', 'player__team__team_name': 'Lakeside Legion 1', 'points': 464, 'team': 'Lakeside Legion 1'}". So it looks like it should be fine. – jAC Jan 16 '18 at 16:59
  • 1
    got it figured out. The stat.division was the attribute it was talking about and needed to be changed to stat['division']. This solution now works for me, thanks! – jAC Jan 17 '18 at 13:27
  • Hey, sorry I didn't get back to you sooner, I've been away. Excellent spot! Of course, the QuerySet is a dictionary because you are using ```.values()``` rather than model instances. My mistake intially. Glad you got it sorted. Edit: Updated answer. – cnfw Jan 17 '18 at 16:55
-1

Maybe the cleanest way to do it is via JavaScript:

{% for division in divisions %}
 <script> var counter = 0 </script>
 {% for stat in stats %}
  {% if stat.division == division %}<tr>
   <td id="{{forloop.counter}}" width="10%" align="center">{{ forloop.counter}}</td>
   <script> var counter = counter + 1
    var s{{forloop.counter}} = counter
    document.getElementById("{{forloop.counter}}").innerHTML = s{{forloop.counter}};
   </script>
   <td width="20%" align="center">{{ stat.points }}</td>
  </tr>
  {% endif %}
 {% endfor %}
{% endfor %}

I did not had the opportunity to test the code. However even if it has an error it would be a good head start.

Santiago M. Quintero
  • 1,237
  • 15
  • 22