"ManyToManyField with through and restrict() returns no results for related model from another plugin" #1709

Closed
opened 2026-04-06 07:20:30 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @an-adestis on 7/1/2025

Deployment Type

NetBox Cloud

NetBox Version

v4.2.9

Python Version

3.10

Steps to Reproduce

  1. In your local NetBox development environment, you have two plugins installed:
  • adestis_netbox_applications (provides the InstalledApplication model)

  • adestis_netbox_certificate_management (plugin under development, which provides the Certificate model)

  1. Install the adestis-netbox-applications plugin via pip:
pip install adestis-netbox-applications
  1. Your Certificate model contains a ManyToMany relationship to InstalledApplication via a through model ApplicationAssignment:
class Certificate(NetBoxModel):
    installedapplication = models.ManyToManyField(
        'adestis_netbox_applications.InstalledApplication',
        through='ApplicationAssignment',
        related_name='certificate',
        verbose_name='Applications',
        blank=True
    )

class ApplicationAssignment(NetBoxModel):
    installedapplication = models.ForeignKey(
        'adestis_netbox_applications.InstalledApplication',
        on_delete=models.CASCADE,
        related_name='certificate_application_assignments',
        verbose_name='Application'
    )
    certificate = models.ForeignKey('Certificate', on_delete=models.CASCADE)

    class Meta:
        unique_together = ('certificate', 'installedapplication')
  1. Create instances of InstalledApplication and Certificate and link them through ApplicationAssignment.

  2. Register the following model view with a tab:

@register_model_view(InstalledApplication, name='certificate', path="my-certificates", detail=False)
class InstalledApplicationAffectedCertificateView(generic.ObjectChildrenView):
    queryset = InstalledApplication.objects.all()
    child_model = Certificate
    table = CertificateTable
    template_name = "adestis_netbox_certificate_management/certificate_application.html"
    actions = {
        'add': {'add'},
        'export': {'view'},
        'bulk_import': {'add'},
        'bulk_edit': {'change'},
        'bulk_remove_certificate': {'change'},
    }

    tab = ViewTab(
        label=_('Certificates'),
        badge=lambda obj: obj.certificate.count(),
        hide_if_empty=False
    )

    def get_children(self, request, parent):
        return Certificate.objects.restrict(request.user, 'view').filter(installedapplication=parent)
6. Attempt to view the InstalledApplication detail or list view expecting to see the Certificates tab.

Expected Behavior

The Certificates tab should be visible on the InstalledApplication views.

The tab badge should dynamically show the count of related certificates.

The queryset in get_children should return all related Certificate instances respecting permissions.

The ManyToMany relationship with through should work properly across plugins with permission filtering.

Additional Notes:
InstalledApplication model is provided by a separate plugin.

adestis_netbox_certificate_management plugin is under development and not yet released.

The issue is likely related to how ManyToMany with through models and .restrict() interact when related models come from different plugins.

Observed Behavior

The Certificates tab does not appear in the InstalledApplication views.

The queryset in get_children returns no results if accessed directly.

The badge count is never displayed since the tab is hidden.

As a result, linked certificates cannot be viewed through this UI.

*Originally created by @an-adestis on 7/1/2025* ### Deployment Type NetBox Cloud ### NetBox Version v4.2.9 ### Python Version 3.10 ### Steps to Reproduce 1. In your local NetBox development environment, you have two plugins installed: - adestis_netbox_applications (provides the InstalledApplication model) - adestis_netbox_certificate_management (plugin under development, which provides the Certificate model) 2. Install the adestis-netbox-applications plugin via pip: ``` pip install adestis-netbox-applications ``` 3. Your Certificate model contains a ManyToMany relationship to InstalledApplication via a through model ApplicationAssignment: ``` class Certificate(NetBoxModel): installedapplication = models.ManyToManyField( 'adestis_netbox_applications.InstalledApplication', through='ApplicationAssignment', related_name='certificate', verbose_name='Applications', blank=True ) class ApplicationAssignment(NetBoxModel): installedapplication = models.ForeignKey( 'adestis_netbox_applications.InstalledApplication', on_delete=models.CASCADE, related_name='certificate_application_assignments', verbose_name='Application' ) certificate = models.ForeignKey('Certificate', on_delete=models.CASCADE) class Meta: unique_together = ('certificate', 'installedapplication') ``` 4. Create instances of InstalledApplication and Certificate and link them through ApplicationAssignment. 5. Register the following model view with a tab: ``` @register_model_view(InstalledApplication, name='certificate', path="my-certificates", detail=False) class InstalledApplicationAffectedCertificateView(generic.ObjectChildrenView): queryset = InstalledApplication.objects.all() child_model = Certificate table = CertificateTable template_name = "adestis_netbox_certificate_management/certificate_application.html" actions = { 'add': {'add'}, 'export': {'view'}, 'bulk_import': {'add'}, 'bulk_edit': {'change'}, 'bulk_remove_certificate': {'change'}, } tab = ViewTab( label=_('Certificates'), badge=lambda obj: obj.certificate.count(), hide_if_empty=False ) def get_children(self, request, parent): return Certificate.objects.restrict(request.user, 'view').filter(installedapplication=parent) 6. Attempt to view the InstalledApplication detail or list view expecting to see the Certificates tab. ``` ### Expected Behavior The Certificates tab should be visible on the InstalledApplication views. The tab badge should dynamically show the count of related certificates. The queryset in get_children should return all related Certificate instances respecting permissions. The ManyToMany relationship with through should work properly across plugins with permission filtering. Additional Notes: InstalledApplication model is provided by a separate plugin. adestis_netbox_certificate_management plugin is under development and not yet released. The issue is likely related to how ManyToMany with through models and .restrict() interact when related models come from different plugins. ### Observed Behavior The Certificates tab does not appear in the InstalledApplication views. The queryset in get_children returns no results if accessed directly. The badge count is never displayed since the tab is hidden. As a result, linked certificates cannot be viewed through this UI.
MrUnknownDE added the pending closurestatus: revisions neededtype: bugpending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurepending closurestatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededstatus: revisions neededtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bugtype: bug labels 2026-04-06 07:22:18 +02:00
Sign in to join this conversation.
No Label pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure pending closure status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed status: revisions needed type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug type: bug
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/netbox#1709