Search code examples
djangodjango-templatesdjango-paginationdjango-listview

How to properly display all inline fields associated with its parent model fields when paginating in Django template?


I'm a student and a Django newbie, and we have a project that we're trying to build using Django. In my journey to building the project I stumbled upon a problem and got stuck for weeks now.

I want to display all the inline fields associated with its parent field on one page as I paginate. When I tried to paginate a model with 2 additional models that have foreign keys to it I got a weird result in my template. I can't seem to figure out how to fix it. I tried several methods on the Internet and have read numerous forums and discussions but to no avail, none has worked so far. Below are my files and a few Images:


(models.py)

from django.db import models

class History(models.Model):
   BARANGAY = (
     ('Alegria','Alegria'),
     ('Bagacay','Bagacay'),
     ('Baluntay','Baluntay'),
     ('Datal Anggas','Datal Anggas'),
     ('Domolok','Domolok'),
     ('Kawas','Kawas'),
     ('Ladol','Ladol'),
     ('Maribulan','Maribulan'),
     ('Pag-Asa','Pag-Asa'),
     ('Paraiso','Paraiso'),
     ('Poblacion','Poblacion'),
     ('Spring','Spring'),
     ('Tokawal','Tokawal')
   )
  barangay_name = models.CharField(max_length=100,choices=BARANGAY,default='Alegria')
  barangay_img = models.ImageField(upload_to='history_imgs',blank=True)
  barangay_info = models.TextField()

class GeoHazard(models.Model):
  history = models.ForeignKey(History,related_name='geohazards',on_delete=models.CASCADE)
  geohazard_img = models.ImageField(upload_to='history_imgs',blank=True)
  date_published = models.CharField(max_length=100, null=True)
  geohazard_info = models.TextField()

class Assessment(models.Model):
   RATINGS = (
      ('HIGH','HIGH'),
      ('HIGH (Mitigated)','HIGH (Mitigated)'),
      ('MODERATE','MODERATE'),
      ('MODERATE (Mitigated)','MODERATE (Mitigated)'),
      ('LOW','LOW'),
      ('UNKNOWN','UNKNOWN'),
   )

   history = models.ForeignKey(History,related_name='assessment',on_delete=models.CASCADE)
   purok_name = models.CharField(max_length=50)
   purok_coordinates = models.CharField(max_length=100,default='unknown')
   flood_rating = models.CharField(max_length=100,choices=RATINGS,default='UNKNOWN')
   landslide_rating = models.CharField(max_length=100,choices=RATINGS,default='UNKNOWN')

In my models.py I have the parent model 'History' and two additional models 'GeoHazard' and 'Assessment' both having foreign keys.


admin-dashboard.png

(admin.py)

from django.contrib import admin

from auxiliary.models import (
   History, 
   GeoHazard,
   Assessment
)
class GeoHazardInline(admin.StackedInline):
   model = GeoHazard
   extra = 0

class AssessmentInline(admin.StackedInline):
   model = Assessment
   extra = 0

class HistoryAdmin(admin.ModelAdmin):
   inlines = [GeoHazardInline,AssessmentInline]

admin.site.register(History,HistoryAdmin)

In my admin.py I am using 'StackedInline'. I structured it this way so that the parent model 'History' can have multiple inline fields associated with it.


(views.py #1)

class history(ListView):
   model = History
   template_name = 'auxiliary/history.html'
   context_object_name = 'histories'
   paginate_by = 1

Initially, I used 'ListView' to take advantage on its pre-built pagination method 'paginate_by' but by doing so the template resulted to this (see image below). As u guys can see the inline fields are paginated_by '1' as well, and the other inline fields got separated from the first page.

template-views1.png)


(views.py #2)

class HistoryView(ListView):
   model = History
   template_name = 'auxiliary/history.html'
   context_object_name = 'histories'
   paginate_by = 1

def get_context_data(self, **kwargs):
       context = super(HistoryView, self).get_context_data(**kwargs)
       context.update({
           'geohazards': GeoHazard.objects.all(),
           'assessments': Assessment.objects.all()
          })

       return context

So I tried a different approach; now by having 3 models passed in my 'ListView' by overriding the 'context' using get_context_data. In this approach all the inline fields are displayed in my template template-views2-A.png , but this time it raises a new issue, even though all the inline fields are displayed in the template It wasn't with their associated parent fields. Now when selecting a new page in my pagination buttons the parent field changes template-views2-B.png but the inline fields remain the same.

In addition, I also tried 'GeoHazard.objects.filter(history_id=1)' when updating the 'context' dictionary but this is not the solution, since this only grabs the inline fields from the parent fields with the specific id. Then I tried to use custom template tags, django custom template-tags but it didn't work.


(template.html) Here's my template btw:

{% for history in histories %}
                <div class="row m-0">
                  <div class="col-md-3 col-sm-12 mt-4">
                    <div class="card bg-transparent border-0">
                      <div class="car-body text-center">
                        <h3><u>{{ history.barangay_name}}</u></h3>
                        <img src="{{ history.barangay_img.url }}" width="180" height="180" class="rounded-circle">
                      </div>
                    </div>
                    
                  </div>
                  <div class="col-md-8 col-sm-12 mt-4" style="display: grid;place-items:center;">
                    <div class="card bg-transparent border-0">
                      <div class="car-body">
                        <p style="text-align:justify;">{{ history.barangay_info}}</p>
                      </div>
                    </div>
                  </div>
                </div>
                <hr>
              {% endfor %}

            {% if geohazards %}
                {% for hazard in geohazards %}
                  <div class="row m-0">
                    <div class="col-md-3 col-sm-12 mt-4">
                      <div class="card bg-transparent border-0">
                        <div class="car-body text-center">
                          <img src="{{hazard.geohazard_img.url}}" height="200" width="300">
                        </div>
                      </div>
                      
                    </div>
                    <div class="col-md-8 col-sm-12 mt-4" style="display: grid;place-items:center;">
                      <div class="card bg-transparent border-0">
                        <div class="car-body">
                          <h4>{{hazard.date_published}}</h4>
                          <p style="text-align:justify;">{{hazard.geohazard_info}}</p>
                        </div>
                      </div>
                    </div>
                  </div>
                {% endfor %}
                <hr>

template-with-labels.png In this pic I labeled each fields that I'm trying to display in my template.

Been at it for ages and looking forward to anyone who can help. Really eager to find the solution to this for the Project’s deadline is right at our doorsteps. Thanks in advance!


Solution

  • You should be able to access the related GeoHazard and Assessment objects by their related_name:

    {% for history in histories %}
        {{ history.barangay_name}
        {# other history information #}
    
        {% for hazard in history.geohazards.all %}
            {{ hazard.geohazard_info }}
            {# other hazard information #}
        {% endfor %}
    
       {% for assessment in history.assessment.all %}
            {{ assessment.purok_name }}
            {# other assessment information #}
        {% endfor %}
    {% endfor %}
    

    Accessing the attribute that is defined by related_name will return an instance of a RelatedManager that has methods like all() (same as objects on a model).

    Note that for this to work you don't need to add any additional stuff in the context, but performance-wise it might make sense to use prefetch_related() otherwise for every History instance additional queries are performed to fetch the related objects.

    class HistoryView(ListView):
       model = History
       template_name = 'auxiliary/history.html'
       context_object_name = 'histories'
       paginate_by = 1
    
    
       def get_queryset(self):
           histories = super().get_queryset()
           histories = histories.prefetch_related("geohazards", "assessment")
           return histories