skip to Main Content

I am trying to access data from a parent to a child via a foreign key.

WHAT WORKS – the views

The data in the child is not "ready to be used" and need to be processed, to be represented in a progress bar in %.

The data processing is handled in the views. When I print it on the console, it seems to work and stored into a variable reward_positions.

Reward positions = [(<Venue: Venue_name>, reward_points, reward_position_on_bar)]

So this part works.

The plan is therefore to access reward_position_on_bar by calling {{reward_positions.2}}

WHAT DOESNT WORK – the template

But something is not working to plan in the template.

The template renders the last child_model (thats rewardprogram) objects of the last parent_id (thats venue) irrespective of the actual parent_id processed in the for loop.

TEST RESULT & WHERE I THINK THE PROBLEM IS

I think my problem lies in my nested forloop. The parent_id in the parent forloop does not match the ‘{{reward_position.0}}’ in the nested forloop.

Doing a verification test, {{key}} should be equal to {{reward_position.0}} as they both go through the same parent forloop.

However, if {{key}} does change based on venue.id (parent forloop id), {{reward_position.0}} is stuck to the same id irrespective of the parent forloop id.

Can anyone seem what I am doing wrong?

THE CODE

models

class Venue(models.Model):
    name = models.CharField(verbose_name="Name",max_length=100, blank=True)

class RewardProgram(models.Model):
    venue = models.ForeignKey(Venue, null = True, blank=True, on_delete=models.CASCADE, related_name="venuerewardprogram")
    title = models.CharField(verbose_name="reward_title",max_length=100, null=True, blank=True)
    points = models.IntegerField(verbose_name = 'points', null = True, blank=True, default=0)

views

def list_venues(request):
    venue_markers = Venue.objects.filter(venue_active=True)
    
    #Progress bar per venue
    
    bar_total_lenght = 100
    rewards_available_per_venue = 0
    reward_position_on_bar = 0
    venue_data = {}
    reward_positions = {}
    
    for venue in venue_markers:
        print(f'venue name ={venue}')
                
        #list all reward programs
        venue.reward_programs = venue.venuerewardprogram.all()
        reward_program_per_venue = venue.reward_programs
        
        #creates a list of reward points needed for each venue for each object
        reward_points_per_venue_test = []
        
        #appends the points to empty list from reward program from each venue
        for rewardprogram in reward_program_per_venue:
            reward_points_per_venue_test.append(rewardprogram.points)
        
        #sorts list in descending order
        reward_points_per_venue_test.sort(reverse=True)
        
        #set position of highest reward to 100 (100% of bar length)
        if reward_points_per_venue_test:
            highest_reward = reward_points_per_venue_test[0]

        if not reward_program_per_venue:
            pass
        else:    
            #counts reward program per venue
            rewards_available_per_venue = venue.reward_programs.count()
                    
            if rewards_available_per_venue == 0:
                pass
            else:   
                #position of reward on bar  
                reward_positions = []
                for rewardprogram in reward_program_per_venue:
                    #list all points needed per reward program objects
                    reward_points = rewardprogram.points
  
                    #position each reward on bar
                
                    reward_position_on_bar = reward_points/highest_reward
                    reward_positions.append((venue, reward_points, reward_position_on_bar))
                    #reward_positions[venue.id] = reward_position_on_bar
                reward_positions = reward_positions
                print(f'Reward positions = {reward_positions}')

    context = {'reward_positions':reward_positions,'venue_data':venue_data,'venue_markers':venue_markers}
    
    return render(request,'template.html',context)

template

            {%for venue in venue_markers%}
    
            {%for key, value in venue_data.items%}
            {%if key == venue.id%} #venue.id = 3
                                  {% for reward_position in reward_positions %}#test result
                                  {{reward_position.0.id}} # = id = 7 (thats not the good result)
{{key}} #id = 3 (thats the good result)
                                  {% endfor %}
                                  <div class="progress-bar bg-success" role="progressbar" style="width: {{value}}%" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="100"></div>
    
            {%endif%}
            {%endfor%}
       
            {%endfor%}

2

Answers


  1. You are overcomplicating things, which makes the template very complex (which often will only introduce extra bugs), and makes it even quite slow because of an N+1 problem in the view.

    def list_venues(request):
        venues = (
            Venue.objects.filter(venue_active=True)
            .annotate(
                total_points=Sum('venuerewardprogram__points'),
                rewards_available=Count('venuerewardprogram'),
            )
            .prefetch_related('venuerewardprogram')
        )
        for venue in venues:
            for rewardprogram in venue.venuerewardprogram.all():
                rewardprogram.position_on_bar = round(
                    100 * rewardprogram.reward_points / venue.total_points
                )
        return render(request, 'template.html', {'venues': venues})

    then in the template, we can render this as:

    {%for venue in venues %}
      <h1>{{ venue.name }}</h1>
      {% for rewardprogram in venue.venuerewardprogram.all %}
        <div class="progress-bar bg-success" role="progressbar" style="width: {{ rewardprogram.position_on_bar }}%" aria-valuenow="{{ rewardprogram.position_on_bar }}" aria-valuemin="0" aria-valuemax="100"></div>
      {% endfor %}
    {% endfor %}

    this will thus take the .position_on_bar attributes we added to the rewardPrograms that we prefetched for the Venues.

    Login or Signup to reply.
  2. The context only contains the rewards of the last venue because you are overriding reward_positions in every loop.

    
        reward_positions = {}
        
        for venue in venue_markers:
            ...
            if not reward_program_per_venue:
                pass
            else:    
                ...
                else:   
                    #position of reward on bar  
                    reward_positions = []  # !!! previous reward_positions are overwritten here !!!
                    for rewardprogram in reward_program_per_venue:
                        ...
                        reward_positions.append((venue, reward_points, reward_position_on_bar))
    
        # reward_positions is the list from the last loop
        context = {'reward_positions':reward_positions,'venue_data':venue_data,'venue_markers':venue_markers}
        
        return render(request,'template.html',context)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search