Can Django admin handle a one-to-many relationship via related_name?
The Django admin happily supports many-to-one and many-to-many relationships through an HTML <SELECT> form field, allowing selection of one or many options respectively. There's even a nice Javascript filter_horizontal
widget to help.
I'm trying t开发者_如何转开发o do the same from the one-to-many side through related_name. I don't see how it's much different from many-to-many as far as displaying it in the form is concerned, I just need a multi-select SELECT list. But I cannot simply add the related_name
value to my ModelAdmin
-derived field list.
Does Django support one-to-many fields in this way?
My Django model something like this (contrived to simplify the example):
class Person(models.Model):
...
manager = models.ForeignKey('self', related_name='staff',
null=True, blank=True, )
From the Person admin page, I can easily get a <SELECT> list showing all possible staff to choose this person's manager from. I also want to display a multiple-selection <SELECT> list of all the manager's staff.
I don't want to use inlines, as I don't want to edit the subordinates details; I do want to be able to add/remove people from the list.
(I'm trying to use django-ajax-selects to replace the SELECT widget, but that's by-the-by.)
Solution for ManyToManyField: http://code.djangoproject.com/ticket/13369#comment:2
you can set the initial values in the init method of the form.
Refer to them as self.fields['manager_staff'].initial.
Like so:
class PersonAdminForm(forms.ModelForm):
manager_staff = forms.ModelMultipleChoiceField(
queryset=Person.objects.all(),
required=False,
)
def __init__(self, *args, **kwargs):
super(PersonAdminForm, self).__init__(*args, **kwargs)
if self.instance.id is not None:
selected_items = [ values[0] for values in Person.objects.filter(
#whatever your filter criteria is
) ]
self.fields['manager_staff'].initial = selected_items
This might end up being one of those limitations. Here's what I managed to come up with. It's possible to create a custom Form and pass that to the admin site to use for rendering a page. However, what I couldn't get was for it to properly show the initial value properly. Perhaps someone better at Forms can come along with some uber-cool-secret-meta API that I don't know about and fix this.
models.py
class Person(models.Model):
# ...
manager = models.ForeignKey('self', related_name='staff', null=True, blank=True)
def manager_staff(self):
return self.manager.staff.all()
admin.py
class PersonAdminForm(forms.ModelForm):
manager_staff = forms.ModelMultipleChoiceField(
initial='manager_staff', # Can't get this to work
queryset=Person.objects.all(),
required=False,
)
class Meta:
model = Person
class PersonAdmin(admin.ModelAdmin):
form = PersonAdminForm
def save_model(self, request, obj, form, change):
for id in form.data.getlist('manager_staff'):
# This is kind of a weak way to do this, but you get the idea...
p = Person.objects.get(id=id)
p.manager = obj.manager
p.save()
super(PersonAdmin, self).save_model(request, obj, form, change)
admin.site.register(Person, PersonAdmin)
Here's what I came up with based on the answers from T. Stone and nicoechaniz. The initial data is provided in much the same way as nicoechaniz does it. For the saving you have to be careful do deal with the case of a completely new Person as well as editing an existing Person. That's what makes the save()
method complicated, depending on non-publicized parts of the ModelForm API. Really this should be built into Django. I imagine it's a commonly sought after feature.
class PersonAdminForm(forms.ModelForm):
staff = forms.ModelMultipleChoiceField(
queryset=Person.objects.all(),
required=False,
)
class Meta:
model = Person
def __init__(self, *args, **kwargs):
super(PersonAdminForm, self).__init__(*args,**kwargs)
if self.instance.pk is not None:
self.initial['staff'] = [values[0] for values in self.instance.staff.values_list('pk')]
def save(self, commit=True):
instance = super(PersonAdminForm, self).save(commit)
def save_m2m():
instance.staff = self.cleaned_data['staff']
if commit:
save_m2m()
elif hasattr(self, 'save_m2m'):
save_old_m2m = self.save_m2m
def save_both():
save_old_m2m()
save_m2m()
self.save_m2m = save_both
else:
self.save_m2m = save_m2m
return instance
save.alters_data = True
class PersonAdmin(admin.ModelAdmin):
form = PersonAdminForm
精彩评论