Many-to-many example using "through" to augment M2M relationships ¶
On one side of the relation, we do the following:
file models.py
class Device(models.Model): ... users = models.ManyToManyField( User, through='DeviceUsers', related_name='devices' ) class DeviceUsers(models.Model): class Meta: verbose_name = _("Device/User") verbose_name_plural = _("Device/Users") device = models.ForeignKey(Device, verbose_name=_('Device'), on_delete=models.CASCADE) user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) enabled = models.BooleanField(_('Enabled'), null=False, default=True, blank=True) expiration_data = models.DateField(_('Expiration date'), null=True, blank=True) def __str__(self): return ''
while nothing is required in the related Model.
In the admin, we can:
- add inlines to edit the relation (in both ModelAdmins),
- (optionally) list the related models in the listing
- note that we also add a search_fields attribute to have autocomplete_fields working (Django 2.x)
file admin.py
class UserForDeviceTabularInline(admin.TabularInline): model = Device.users.through extra = 0 autocomplete_fields = ['user', ] @admin.register(Device) class DeviceAdmin(BaseModelAdmin): list_display = [..., 'list_users', ] search_fields = ['=id', 'description', 'code', ] inlines = [UserForDeviceTabularInline, ] def list_users(self, obj): return mark_safe(', '.join([ '<a href="%s">%s</a>' % (user.get_admin_url(), str(user)) for user in obj.users.all() # .order_by('panelregisters__position') ])) list_users.short_description = _('users') class DeviceTabularInline(admin.TabularInline): from backend.models import Device model = Device.users.through #exclude = ['position', ] extra = 0 autocomplete_fields = ['device', ] @admin.register(User) class UserAdmin(AuthUserAdmin, HijackUserAdminMixin): list_display = [..., 'list_devices', ] inlines = [DeviceTabularInline, ] def list_devices(self, obj): return mark_safe(', '.join([ '<a href="%s">%s</a>' % (device.get_admin_url(), str(device)) for device in obj.devices.all() # .order_by('panelregisters__position') ])) list_devices.short_description = _('devices')
TODO: add select_related() as required