Adding a ManyToManyWidget to the reverse of a ManyToManyField in the Django Admin

前端 未结 5 1037
我寻月下人不归
我寻月下人不归 2020-12-13 07:08

Let\'s say I have a simple blog app in Django 1.4:

class Post(models.Model):
    title = …
    published_on = …
    tags = models.ManyToManyField(\'Tag\')

c         


        
相关标签:
5条回答
  • 2020-12-13 07:17

    This is possible to do with a custom form.

    from django.contrib import admin
    from django import forms
    
    from models import Post, Tag
    
    class PostAdminForm(forms.ModelForm):
        tags = forms.ModelMultipleChoiceField(
            Tag.objects.all(),
            widget=admin.widgets.FilteredSelectMultiple('Tags', False),
            required=False,
        )
    
        def __init__(self, *args, **kwargs):
            super(PostAdminForm, self).__init__(*args, **kwargs)
            if self.instance.pk:
                self.initial['tags'] = self.instance.tags.values_list('pk', flat=True)
    
        def save(self, *args, **kwargs):
            instance = super(PostAdminForm, self).save(*args, **kwargs)
            if instance.pk:
                instance.tags.clear()
                instance.tags.add(*self.cleaned_data['tags'])
            return instance
    
    class PostAdmin(admin.ModelAdmin):
        form = PostAdminForm
    
    admin.site.register(Post, PostAdmin)
    

    That False in there can be replaced with a True if you want vertically stacked widget.

    0 讨论(0)
  • 2020-12-13 07:26

    You can add a symmetrical many to many filter this way.

    Credit goes to https://gist.github.com/Grokzen/a64321dd69339c42a184

    from django.db import models
    
    class Pizza(models.Model):
      name = models.CharField(max_length=50)
      toppings = models.ManyToManyField(Topping, related_name='pizzas')
    
    class Topping(models.Model):
      name = models.CharField(max_length=50)
    
    ### pizza/admin.py ###
    
    from django import forms
    from django.contrib import admin
    from django.utils.translation import ugettext_lazy as _
    from django.contrib.admin.widgets import FilteredSelectMultiple
    
    from .models import Pizza, Topping
    
    class PizzaAdmin(admin.ModelAdmin):
      filter_horizonal = ('toppings',)
    
    class ToppingAdminForm(forms.ModelForm):
      pizzas = forms.ModelMultipleChoiceField(
        queryset=Pizza.objects.all(), 
        required=False,
        widget=FilteredSelectMultiple(
          verbose_name=_('Pizzas'),
          is_stacked=False
        )
      )
    
      class Meta:
        model = Topping
    
      def __init__(self, *args, **kwargs):
        super(ToppingAdminForm, self).__init__(*args, **kwargs)
    
        if self.instance and self.instance.pk:
          self.fields['pizzas'].initial = self.instance.pizzas.all()
    
      def save(self, commit=True):
        topping = super(ToppingAdminForm, self).save(commit=False)
    
        if commit:
          topping.save()
    
        if topping.pk:
          topping.pizzas = self.cleaned_data['pizzas']
          self.save_m2m()
    
        return topping
    
    class ToppingAdmin(admin.ModelAdmin):
      form = ToppingAdminForm
    
    admin.site.register(Pizza, PizzaAdmin)
    admin.site.register(Topping, ToppingAdmin)
    
    0 讨论(0)
  • 2020-12-13 07:29

    A bit late to the party, but this is the solution that works for me (no magic):

    # admin.py
    
    from django.contrib import admin
    from models import Post
    
    class TagPostInline(admin.TabularInline):
        model = Post.tags.through
        extra = 1
    
    class PostAdmin(admin.ModelAdmin):
        inlines = [TagPostInline]
    
    admin.site.register(Post, PostAdmin)
    

    Reference: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-models

    0 讨论(0)
  • 2020-12-13 07:33

    Matthew's solution didn't work for me (Django 1.7) when creating a new entry, so I had to change it a bit. I hope it's useful for someone :)

    class PortfolioCategoriesForm(forms.ModelForm):
        items = forms.ModelMultipleChoiceField(
            PortfolioItem.objects.all(),
            widget=admin.widgets.FilteredSelectMultiple('Portfolio items', False),
            required=False
        )
    
        def __init__(self, *args, **kwargs):
            super(PortfolioCategoriesForm, self).__init__(*args, **kwargs)
            if self.instance.pk:
                initial_items = self.instance.items.values_list('pk', flat=True)
                self.initial['items'] = initial_items
    
        def save(self, *args, **kwargs):
            kwargs['commit'] = True
            return super(PortfolioCategoriesForm, self).save(*args, **kwargs)
    
        def save_m2m(self):
            self.instance.items.clear()
            self.instance.items.add(*self.cleaned_data['items'])
    
    0 讨论(0)
  • 2020-12-13 07:40

    Modify your models to add reverse field:

    # models.py
    from django.db import models
    
    class Post(models.Model):
        title = models.CharField(max_length=100)
        published_on = models.DateTimeField()
        tags = models.ManyToManyField('Tag')
    
    class Tag(models.Model):
        name = models.CharField(max_length=10)
        posts = models.ManyToManyField('blog.Post', through='blog.post_tags')
    

    Then in standard way add field to ModelAdmin:

    #admin.py
    from django.contrib import admin
    
    class TagAdmin(admin.ModelAdmin):
        list_filter = ('posts', )
    
    admin.site.register(Tag, TagAdmin)
    
    0 讨论(0)
提交回复
热议问题