Interfaz de administración de Django: usando horizontal_filter con el campo en línea ManyToMany

Tengo un campo de modelo de Django que me gustaría alinear. El campo es una relación de muchos a muchos. Así que hay “Proyectos” y “Perfiles de usuario”. Cada perfil de usuario puede seleccionar cualquier número de proyectos.

Actualmente, tengo la vista en línea “tabular” funcionando. ¿Hay alguna forma de tener un “filtro horizontal” para poder agregar y eliminar proyectos fácilmente de un perfil de usuario?

Por favor, vea la imagen adjunta para un ejemplo. introduzca la descripción de la imagen aquí

Aquí está el código de modelo para el perfil de usuario:

class UserProfile(models.Model): user = models.OneToOneField(User, unique=True) projects = models.ManyToManyField(Project, blank=True, help_text="Select the projects that this user is currently working on.") 

Y el código modelo para un proyecto:

 class Project(models.Model): name = models.CharField(max_length=100, unique=True) application_identifier = models.CharField(max_length=100) type = models.IntegerField(choices=ProjectType) account = models.ForeignKey(Account) principle_investigator = models.ForeignKey(User) active = models.BooleanField() 

Y el código de administrador para la vista:

 class UserProfileInline(admin.TabularInline): model = UserProfile.projects.through extra = 0 verbose_name = 'user' verbose_name_plural = 'users' class ProjectAdmin(admin.ModelAdmin): list_display = ('name', 'application_identifier', 'type', 'account', 'active') search_fields = ('name', 'application_identifier', 'account__name') list_filter = ('type', 'active') inlines = [UserProfileInline,] admin.site.register(Project, ProjectAdmin) 

El problema no es tener líneas internas; Es de la forma en que funciona ModelForm , en general. Solo crean campos de formulario para campos reales en el modelo, no atributos de administrador relacionados. Sin embargo, puede agregar esta funcionalidad al formulario:

 from django.contrib.admin.widgets import FilteredSelectMultiple class ProjectAdminForm(forms.ModelForm): class Meta: model = Project userprofiles = forms.ModelMultipleChoiceField( queryset=UserProfile.objects.all(), required=False, widget=FilteredSelectMultiple( verbose_name='User Profiles', is_stacked=False ) ) def __init__(self, *args, **kwargs): super(ProjectAdminForm, self).__init__(*args, **kwargs) if self.instance.pk: self.fields['userprofiles'].initial = self.instance.userprofile_set.all() def save(self, commit=True): project = super(ProjectAdminForm, self).save(commit=False) if commit: project.save() if project.pk: project.userprofile_set = self.cleaned_data['userprofiles'] self.save_m2m() return project class ProjectAdmin(admin.ModelAdmin): form = ProjectAdminForm ... 

Un pequeño recorrido está probablemente en orden. Primero, definimos un campo de formulario de userprofiles . Utilizará un ModelMultipleChoiceField , que de manera predeterminada dará como resultado un cuadro de selección múltiple. Dado que este no es un campo real en el modelo, no podemos simplemente agregarlo a filter_horizontal , así que en su lugar le decimos que simplemente use el mismo widget, FilteredSelectMultiple , que usaría si estuviera listado en filter_horizontal .

Inicialmente, establecimos el conjunto de consultas como el conjunto completo de perfiles de usuario, no puede filtrarlo aquí, sin embargo, porque en esta etapa de la definición de la clase, el formulario no se ha instanciado y, por lo tanto, aún no se ha configurado su instance . Como resultado, reemplazamos __init__ para que podamos establecer el conjunto de consultas filtradas como el valor inicial del campo.

Finalmente, anulamos el método de save , de modo que podamos establecer el contenido del administrador relacionado en la misma forma que lo que estaba en los datos POST del formulario, y listo.

Una adición menor cuando se trata de una relación de muchos a muchos consigo mismo. Uno podría querer excluirse de las opciones:

 if self.instance.pk: self.fields['field_being_added'].queryset = self.fields['field_being_added'].queryset.exclude(pk=self.instance.pk) self.fields['field_being_added'].initial = """Corresponding result queryset"""