5

I have two models Project and Group. My groups belong to a specific project. My groups have the fields project = ForeignKey(Project) and parent = ForeignKey('self').

Can I use limit_choices_to to make sure the options in foreign key parent only consist of groups inside the same project?

I'm thinking of something like

def limit_choices_to(self):
    return {'project': self.project}
Jamgreen
  • 10,329
  • 29
  • 113
  • 224

3 Answers3

9

This is impossible to do at the model level but you can change the queryset for this field in the form's constructor.

class GroupForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(GroupForm, self).__init__(*args, **kwargs)
        if self.instance.project:
            self.fields['parent'].queryset = Group.objects.filter(
                                                project=self.instance.project)

UPDATE: To do it in the admin you have to set the form attribute of the ModelAdmin:

class GroupAdmin(admin.ModelAdmin):
    form = GroupForm
catavaran
  • 44,703
  • 8
  • 98
  • 85
  • Thank you! But how can I accomplish the same result in the admin section? – Jamgreen Mar 06 '15 at 16:03
  • I think, this should be possible to do on model level with `limit_choices_to={'project__id': Value('id')}` but I am getting errors like `psycopg2.errors.InvalidCursorName: cursor "_django_curs_140361007625984_sync_1" does not exist` – Petr Dlouhý Sep 11 '20 at 11:15
1

For the admin section I found hacky way to do it. My example is with admin.StackedInline, but almost sure it will work with plain ModelAdmin. I left it here just in case someone likes this solution:

class ProfessionalInline(admin.StackedInline):
model = Professional

fieldsets = [
    ('Research information', {
        'fields': ('team', 'projects')
    }),
    ('Profile information', {
        'fields': ('age', 'gender', 'receive_emails')
    })]

def get_parent_object_from_request(self, request):
    """
    Returns the parent object from the request or None.

    Note that this only works for Inlines, because the `parent_model`
    is not available in the regular admin.ModelAdmin as an attribute.
    """
    resolved = resolve(request.path_info)
    if resolved.kwargs:
        return self.parent_model.objects.get(pk=resolved.kwargs['object_id'])
    return None

def formfield_for_manytomany(self, db_field, request, **kwargs):
    '''Extremely hacky'''
    account = self.get_parent_object_from_request(request)
    if db_field.name == 'projects':
        kwargs['queryset'] = Project.objects.filter(team=account.professional.team)
    return super().formfield_for_manytomany(db_field, request, **kwargs)
j4n7
  • 590
  • 7
  • 9
0

For the admin you can override get_form() from admin.ModelAdmin if you don't want to make separate for. This is working for me with Django 3.2:

    def get_form(self, request, obj=None, change=False, **kwargs):
        form = super().get_form(request, obj, change, **kwargs)
        queryset = form.base_fields["<field>"].queryset
        form.base_fields["<field>"].queryset = queryset.filter(...)
        return form
Ben Konrath
  • 423
  • 4
  • 8