Search code examples
djangodjango-modelsdjango-viewsdjango-templates

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. The workstation, printer and laptop have foreign of site ID. I want to list all the workstation, laptop and printer filter by a site.

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

Sample output


Solution

  • You are essentially looking for a union, filter all three models by sitename then union them together. Make sure to change in sitename according to your requirements.

    # Here we loop over the three models and add the subqueries to the query list
    queries = []
    for model in [Printer, Laptop, Workstation]:
        queries.append(
            model.objects.filter(sitename=1) 
            .values("name", "serial")
        )
    # We are unpacking the previously added queries, p stands for printer, same for l and w
    p, l, w = queries
    print(p)
    # Here we are doing union of the three models,i.e
    # Printer union Laptop union Workstation
    plw = p.union(l, w, all=True)
    
    <QuerySet [{'name': 'Epson', 'serial': '2'}, {'name': 'printer1', 'serial': '23'}, {'name': 'Asus', 'serial': '1'}, {'name': 'Dell', 'serial': '56'}, {'name': 'Tas', 'serial': '1'}, {'name': 'WS2', 'serial': '234'}]>
    

    Django doesn't give much control in union queries so you might want to use raw queries if you need more control. Or you can query all 3 models separately and chain them together using itertools.chain

    from itertools import chain
    print(list(chain(*queries)))
    

    Views

    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"]
            queries = []
            for model in [Printer, Laptop, Workstation]:
                queries.append(model.objects.filter(sitename=site).values("name", "serial"))
            p, l, w = queries
            print(p)
            plw = p.union(l, w, all=True)
            context["site"] = plw
            return context
    

    The url should look like this http://127.0.0.1:8000/report/1