List Data from Multiple Models in Single HTML Table

I am trying to render data from 3 models in single html table. I have tried an error few times but failed. Below is my code.

Models:

class Site(models.Model):
    site_name = models.CharField(max_length=30, blank=True, null=True, verbose_name="Site Name")
    site_address = models.CharField(max_length=30, blank=True, null=True, verbose_name="Site Address")

    def __str__(self):
        return self.site_name

class Workstation(models.Model):
    name = models.CharField(max_length=30, blank=True, null=True, verbose_name="Workstation Name")
    serial = models.CharField(max_length=30, blank=True, null=True, verbose_name="Serial")
    workstation_model = models.CharField(max_length=30, blank=True, null=True, verbose_name="Workstation Model")
    sitename = models.ForeignKey(Site, on_delete=models.SET_NULL, blank=True, null=True, verbose_name="Site")

    def __str__(self):
        return self.name
    
class Laptop(models.Model):
    name = models.CharField(max_length=30, blank=True, null=True, verbose_name="Laptop Name")
    serial = models.CharField(max_length=30, blank=True, null=True, verbose_name="Serial")
    laptop_model = models.CharField(max_length=30, blank=True, null=True, verbose_name="Laptop Model")
    sitename = models.ForeignKey(Site, on_delete=models.SET_NULL, blank=True, null=True, verbose_name="Site")

    def __str__(self):
        return self.name
    
class Printer(models.Model):
    name = models.CharField(max_length=30, blank=True, null=True, verbose_name="Printer Name")
    serial = models.CharField(max_length=30, blank=True, null=True, verbose_name="Serial")
    printer_model = models.CharField(max_length=30, blank=True, null=True, verbose_name="Printer Model")
    sitename = models.ForeignKey(Site, on_delete=models.SET_NULL, blank=True, null=True, verbose_name="Site")

    def __str__(self):
        return self.name

URL:
urlpatterns = [
    path('', views.SiteView.as_view(), name='site'),
    path('report/<sitename>', views.ReportView.as_view(), name='reportview'),
]

Template 1: Site List

    <table>
        <tr>
          <th>Site</th>
          <th>Address</th>
        </tr>
        {% for site in site_list %}
        <tr>
            <td><a href="{% url 'reportview' site.site_name %}">{{ site.site_name }}</a></td>
            <td>{{ site.site_address }}</td>
        </tr>
        {% endfor %}
       </table>

Template 2: Report equipment by site

   <table>
        <tr>
          <th>Device</th>
          <th>Serial</th>
        </tr>
        {% for item in site %}
        <tr>
            <td>{{ item.name }}</td>
            <td>{{ item.serial }}</td>
        </tr>
        {% endfor %}
       </table>

View for template 1: Successful output:

class SiteView(ListView):
    model = Site
    template_name = "mytestapp/site.html"

View for template 2: Failed
class ReportView(TemplateView):
    template_name = "mytestapp/report.html"

    def post(self, request):
        site = request.POST["site"]
        context = super().get_context_data(request)
        context["site"] = Workstation.objects.get(sitename=site)
        context["site"] = Laptop.objects.get(sitename=site)
        context["site"] = Printer.objects.get(sitename=site)
        return context

I am trying to list workstation, laptop and printer in single table filter by a site. Sample output:
image

Do you only have one Workstation, Laptop and Printer per site? The get function will only return one result. If you want a list of all Workstation at a site, you need to use filter to retrieve the set of all Workstation. (Same with Laptop and Printer)

Also, the context is a dict. Each time you assign to it, you’re overriding what was there before.

You’ve got a couple different options to resolve that:

  • You can create 1 list with all the objects in it.

  • You can create context['site'] as a list, where each entry is the query for each type

  • You can create different keys in context for each of the different types of objects.

Each of these three options has different effects on the template that you need to change.

Hi Ken,

Thanks for reviewed my code. I am trying to understand your notes and spent several hours but still can’t get the output in template. Could you kindly draft a sample for me please?

I have tried the chain method also but not luck:

class ReportView(TemplateView):
    template_name = "mytestapp/report.html"

    def equipmenst(request, statename):
        context1 = Workstation.objects.filter(statename=statename)
        context2 = Laptop.objects.filter(statename=statename)
        context3 = Printer.objects.filter(statename=statename)
        equipments = list(chain(context1, context2, context3))
        return equipments

Where are you using this equipmenst function in your view?

You’ve defined this function, but you’re not showing anything that would be calling it to add it to the context being rendered.

Hi Ken,

Sorry Ken, I am new to programming and simply trying an error with hopes it will work.
Anyway, someone has helped me with the code and sharing it here.

Models:
class Site(models.Model):
    site_name = models.CharField(max_length=30, blank=True, null=True, verbose_name="Site Name")
    site_address = models.CharField(max_length=30, blank=True, null=True, verbose_name="Site Address")

    def __str__(self):
        return self.site_name

class Workstation(models.Model):
    name = models.CharField(max_length=30, blank=True, null=True, verbose_name="Workstation Name")
    serial = models.CharField(max_length=30, blank=True, null=True, verbose_name="Serial")
    sitename = models.ForeignKey(Site, on_delete=models.SET_NULL, blank=True, null=True, verbose_name="Site")

    def __str__(self):
        return self.name
    
class Laptop(models.Model):
    name = models.CharField(max_length=30, blank=True, null=True, verbose_name="Laptop Name")
    serial = models.CharField(max_length=30, blank=True, null=True, verbose_name="Serial")
    sitename = models.ForeignKey(Site, on_delete=models.SET_NULL, blank=True, null=True, verbose_name="Site")

    def __str__(self):
        return self.name
    
class Printer(models.Model):
    name = models.CharField(max_length=30, blank=True, null=True, verbose_name="Printer Name")
    serial = models.CharField(max_length=30, blank=True, null=True, verbose_name="Serial")
    sitename = models.ForeignKey(Site, on_delete=models.SET_NULL, blank=True, null=True, verbose_name="Site")

    def __str__(self):
        return self.name

Views.py:

class SiteView(ListView):
    model = Site
    template_name = "mytestapp/site.html"

class ReportView(TemplateView):
    template_name = "mytestapp/report.html"

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        site = kwargs["sitename"]

        # Convert querysets to lists before combining
        printer_query = list(Printer.objects.filter(sitename=site).values(
            'name', 'serial', device_type=Value('Printer', output_field=CharField())
        ))

        workstation_query = list(Workstation.objects.filter(sitename=site).values(
            'name', 'serial', device_type=Value('Workstation', output_field=CharField())
        ))

        laptop_query = list(Laptop.objects.filter(sitename=site).values(
            'name', 'serial', device_type=Value('Laptop', output_field=CharField())
        ))

        # Combine lists
        plw = printer_query + workstation_query + laptop_query

        context["site"] = plw
        return context

Template 1:
    <table>
        <tr>
          <th>Site</th>
          <th>Address</th>
        </tr>
        {% for site in site_list %}
        <tr>
            <td><a href="{% url 'reportview' site.id %}">{{ site.site_name }}</a></td>
            <td>{{ site.site_address }}</td>
        </tr>
        {% endfor %}
       </table>

Template 2:
    <table>
      <tr>
        <th>Device</th>
        <th>Serial</th>
      </tr>
      {% for item in site %}
      <tr>
          <td>{{ item.name }}</td>
          <td>{{ item.serial }}</td>
      </tr>
      {% endfor %}
'''
1 Like