简体   繁体   中英

Pulling several Django models together into a single list

I have a MySQL database with four related tables: project, unit, unit_equipment, and equipment. A project can have many units; a unit can have many related equipment entries. A single unit can only belong to one project, but there is a many-to-many between equipment and unit (hence the unit_equipment bridge table in the DB). I'm using Django and trying to create a view (or a list?) that shows all 3 models on the same page, together. So it would list all projects, all units, and all equipment. Ideally, the display would be like this:

    Project --------- Unit ------------- Equipment
    Project 1         first_unit         some_equipment1, some_equipment2
    Project 1         second_unit        more_equipment1, more_equipment2
    Project 2         another_unit       some_equipment1, more_equipment1
    Project 2         and_another_unit   some_equipment2, more_equipment2

but at this point I'd also be happy with just having a separate line for each piece of equipment, if comma-separating them is a pain.

Although it seems straightforward to create a form where I can add a new project and add related unit and equipment data (using the TabularInline class), I cannot for the life of me figure out how to bring this data together and just display it. I just want a "master list" of everything in the database, basically.

Here's the code I have so far:

models.py

class Project(models.Model):
    name = models.CharField(max_length=255, blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'project'

    def __str__(self):
        return self.name

class Unit(models.Model):
    project = models.ForeignKey(Project, models.DO_NOTHING, blank=True, null=True)
    name = models.CharField(max_length=255, blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'unit'

    def __str__(self):
        return self.name

class UnitEquipment(models.Model):
    unit = models.ForeignKey(Unit, models.DO_NOTHING, blank=True, null=True)
    equipment = models.ForeignKey(Equipment, models.DO_NOTHING, blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'unit_equipment'

class Equipment(models.Model):
    name = models.CharField(max_length=100, blank=True, null=True)
    description = models.CharField(max_length=255, blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'equipment'

    def __str__(self):
        return self.name

views.py

def project_detail_view(request):
    obj = Project.objects.all()
    context = {'object': obj}
    return render(request, "project/project_detail.html", context)

urls.py

urlpatterns = [
    path('project/', project_detail_view),
    path('', admin.site.urls),
]

admin.py

class UnitTabularInLine(admin.TabularInline):
    model = Unit
    extra = 0

class ProjectAdmin(admin.ModelAdmin):
    inlines = [UnitTabularInLine]

    class Meta:
        model = Project

    # a list of displayed columns name.
    list_display = ['name']
    # define search columns list, then a search box will be added at the top of list page.
    search_fields = ['name']
    # define filter columns list, then a filter widget will be shown at right side of list page.
    list_filter = ['name']
    # define model data list ordering.
    ordering = ('name')

I think I need to somehow add more entries to the list_display in the admin file, but every time I try to add unit or equipment it throws an error. I've also tried adding more attributes to Project, but I can't seem to get the syntax right, and I'm never sure which model class I'm supposed to make it.

I've also looked at FormSets, but I cannot get my head around how to alter my current code to get it to work.

How do I get these models together into a unified view?

You don't need to edit the admin view to add your own view: which you may find you are able to do in this case to get your data displayed exactly as you want.

If you do want to show the related object values in the admin list, then you can use lookups and custom columns: however in this case your list would be based upon the Unit.

# You don't need an explicit UnitEquipment model here: you can
# use a simple ManyToManyField

class Unit(models.Model):
    project = ...
    name = ...
    equipment = models.ManyToManyField(Equipment, related_name='units')


def equipment_list(admin, instance):
    return ', '.join([x.name for x in instance.equimpent.all()])


class UnitAdmin(admin.ModelAdmin):
    class Meta:
        model = Unit

    list_display = ['project__name', 'name', equipment_list]


    def get_queryset(self, request):
       return super().get_queryset(request)\
                     .select_related('project')\
                     .prefetch_related('equipment')

Note that you need to have the queryset override, otherwise there will be a bunch of extra queries as each unit also requires fetching the project and list of equipment for that unit.

There's also a further improvement you can make to your queries: you could aggregate the related equipment names using a Subquery annotation, and prevent the second query (that fetches all related equipment items for the units in the queryset). This would replace the prefetch_related()

Thanks to @Matthew Schinckel, I was able to find my way to the answer. Here's what my files look like now (only edited the Unit class in models.py ):

models.py

class Unit(models.Model):
    project = models.ForeignKey(Project, models.DO_NOTHING, blank=True, null=True)
    name = models.CharField(max_length=255, blank=True, null=True)
    equipment = models.ManyToManyField(Equipment, related_name='units')

    class Meta:
        managed = False
        db_table = 'unit'

    def __str__(self):
        return self.name

    def equipment_list(self):
        return ', '.join([x.name for x in self.equipment.all()])

admin.py

class UnitAdmin(admin.ModelAdmin):
    class Meta:
        model = Unit

    # a list of displayed columns name.
    list_display = ('project', 'name', 'equipment_list')
    # define search columns list, then a search box will be added at the top of list page.
    search_fields = ['project']
    # define filter columns list, then a filter widget will be shown at right side of list page.
    list_filter = ['project', 'name']
    # define model data list ordering.
    ordering = ('project', 'name')

    def get_queryset(self, request):
        return super().get_queryset(request)\
            .select_related('project')\
            .prefetch_related('equipment')

So the changes I made were:
1. Make list_display a tuple instead of a list.
2. Throw def equipment_list(self) into the Unit class (so it's callable as an attribute of Unit) and pass (self) instead of (admin, instance) (I kept getting an error that was looking for the instance argument).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM