Как фильтровать варианты ForeignKey в модели Django?

скажем, у меня есть следующее в моем models.py:

class Company(models.Model):
   name = ...

class Rate(models.Model):
   company = models.ForeignKey(Company)
   name = ...

class Client(models.Model):
   name = ...
   company = models.ForeignKey(Company)
   base_rate = models.ForeignKey(Rate)

т. е. есть несколько Companies, каждый из которых имеет ряд Rates и Clients. Каждый Client должен иметь базу Rate это выбрано из его родителя Company's Rates, ни Company's Rates.

при создании формы для добавления Client Я хотел бы убрать Company выбор (как это уже было выбрано с помощью кнопки "Добавить клиента" на Company страница) и ограничить Rate выбор это Company как хорошо.

как мне это сделать в Django 1.0?

мой forms.py файл просто шаблонный на данный момент:

from models import *
from django.forms import ModelForm

class ClientForm(ModelForm):
    class Meta:
        model = Client

и views.py основное:

from django.shortcuts import render_to_response, get_object_or_404
from models import *
from forms import *

def addclient(request, company_id):
    the_company = get_object_or_404(Company, id=company_id)

    if request.POST:
        form = ClientForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(the_company.get_clients_url())
    else:
        form = ClientForm()

    return render_to_response('addclient.html', {'form': form, 'the_company':the_company})

в Django 0.96 я смог взломать это, делая что-то вроде следующего перед рендерингом шаблона:

manipulator.fields[0].choices = [(r.id,r.name) for r in Rate.objects.filter(company_id=the_company.id)]

ForeignKey.limit_choices_to кажется многообещающим, но я не знаю, как передать the_company.id и я не ясно если это будет работать вне интерфейса администратора в любом случае.

спасибо. (Это похоже на довольно простой запрос, но если я должен что-то изменить, я открыт для предложений.)

7 ответов


ForeignKey представлен django.формы.ModelChoiceField, который является ChoiceField, выбор которого является набором запросов модели. См. ссылку для ModelChoiceField.

Итак, предоставьте QuerySet для поля


В дополнение к ответу S. Lott и как becomingguru упоминалось в комментариях, его можно добавить фильтры queryset, переопределив


это просто, и работает с Django 1.4:

class ClientAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ClientAdminForm, self).__init__(*args, **kwargs)
        # access object through self.instance...
        self.fields['base_rate'].queryset = Rate.objects.filter(company=self.instance.company)

class ClientAdmin(admin.ModelAdmin):
    form = ClientAdminForm
    ....

вам не нужно указывать это в классе формы, но можете сделать это непосредственно в ModelAdmin, так как Django уже включает этот встроенный метод в ModelAdmin (из документов):

ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)¶
'''The formfield_for_foreignkey method on a ModelAdmin allows you to 
   override the default formfield for a foreign keys field. For example, 
   to return a subset of objects for this foreign key field based on the
   user:'''

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "car":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

еще более изящный способ сделать это (например, при создании интерфейсного интерфейса администратора, к которому пользователи могут получить доступ)-подкласс ModelAdmin, а затем изменить методы ниже. Конечный результат - это пользовательский интерфейс, который только показывает им контент, который связан с ними, позволяя вам (суперпользователю) видеть все.

Я переопределил четыре метода, первые два делают невозможным для пользователя удалить что-либо, а также удаляет кнопки удаления с сайта администратора.

третье переопределение фильтрует любой запрос, содержащий ссылку на (в примере "пользователь" или "дикобраз" (просто как иллюстрация).

последнее переопределение фильтрует любое поле foreignkey в модели для фильтрации вариантов, доступных так же, как и в базовом queryset.

таким образом, вы можете представить простой в управлении фронтальный сайт администратора, который позволяет пользователям возиться со своими объектами, и вам не нужно помнить, чтобы ввести конкретные фильтры ModelAdmin мы говорили выше.

class FrontEndAdmin(models.ModelAdmin):
    def __init__(self, model, admin_site):
        self.model = model
        self.opts = model._meta
        self.admin_site = admin_site
        super(FrontEndAdmin, self).__init__(model, admin_site)

"удалить ""удалить" кнопки:

    def get_actions(self, request):
        actions = super(FrontEndAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

предотвращает удаление разрешения

    def has_delete_permission(self, request, obj=None):
        return False

фильтрует объекты, которые можно просмотреть в admin сайт:

    def get_queryset(self, request):
        if request.user.is_superuser:
            try:
                qs = self.model.objects.all()
            except AttributeError:
                qs = self.model._default_manager.get_queryset()
            return qs

        else:
            try:
                qs = self.model.objects.all()
            except AttributeError:
                qs = self.model._default_manager.get_queryset()

            if hasattr(self.model, ‘user’):
                return qs.filter(user=request.user)
            if hasattr(self.model, ‘porcupine’):
                return qs.filter(porcupine=request.user.porcupine)
            else:
                return qs

выбор фильтров для всех полей foreignkey на сайте администратора:

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if request.employee.is_superuser:
            return super(FrontEndAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

        else:
            if hasattr(db_field.rel.to, 'user'):
                kwargs["queryset"] = db_field.rel.to.objects.filter(user=request.user)
            if hasattr(db_field.rel.to, 'porcupine'):
                kwargs["queryset"] = db_field.rel.to.objects.filter(porcupine=request.user.porcupine)
            return super(ModelAdminFront, self).formfield_for_foreignkey(db_field, request, **kwargs)

чтобы сделать это с общим представлением, например CreateView...

class AddPhotoToProject(CreateView):
    """
    a view where a user can associate a photo with a project
    """
    model = Connection
    form_class = CreateConnectionForm


    def get_context_data(self, **kwargs):
        context = super(AddPhotoToProject, self).get_context_data(**kwargs)
        context['photo'] = self.kwargs['pk']
        context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user)
        return context
    def form_valid(self, form):
        pobj = Photo.objects.get(pk=self.kwargs['pk'])
        obj = form.save(commit=False)
        obj.photo = pobj
        obj.save()

        return_json = {'success': True}

        if self.request.is_ajax():

            final_response = json.dumps(return_json)
            return HttpResponse(final_response)

        else:

            messages.success(self.request, 'photo was added to project!')
            return HttpResponseRedirect(reverse('MyPhotos'))

самая важная часть этого...

    context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user)

, читай мой пост здесь


Если вы не создали форму и хотите изменить набор запросов, вы можете сделать:

formmodel.base_fields['myfield'].queryset = MyModel.objects.filter(...)

Это довольно полезно, когда вы используете общие представления!


Итак, я действительно пытался понять это, но кажется, что Django все еще не делает это очень простым. Я не настолько глуп, но я просто не вижу никакого (несколько) простого решения.

Я считаю, что обычно довольно уродливо переопределять представления администратора для такого рода вещей, и каждый пример, который я нахожу, никогда полностью не применяется к представлениям администратора.

это такое обычное обстоятельство с моделями, которые я делаю, что я нахожу ужасным, что нет очевидного решение этого...

у меня есть эти классы:

# models.py
class Company(models.Model):
    # ...
class Contract(models.Model):
    company = models.ForeignKey(Company)
    locations = models.ManyToManyField('Location')
class Location(models.Model):
    company = models.ForeignKey(Company)

это создает проблему при настройке администратора для компании, потому что у него есть строки для контракта и местоположения, а параметры M2M контракта для местоположения не фильтруются должным образом в соответствии с компанией, которую вы в настоящее время редактируете.

короче говоря, мне нужен какой-то вариант администратора, чтобы сделать что-то вроде этого:

# admin.py
class LocationInline(admin.TabularInline):
    model = Location
class ContractInline(admin.TabularInline):
    model = Contract
class CompanyAdmin(admin.ModelAdmin):
    inlines = (ContractInline, LocationInline)
    inline_filter = dict(Location__company='self')

в конечном счете мне было бы все равно, если бы процесс фильтрации был размещены на базе CompanyAdmin, или если он был выставлен на ContractInline. (Размещение его на inline имеет больше смысла, но это затрудняет ссылку на базовый контракт как на "себя".)

есть ли кто-нибудь, кто знает что-то столь же простое, как этот крайне необходимый ярлык? Когда я делал PHP-администраторов для такого рода вещей, это считалось базовой функциональностью! Фактически, он всегда был автоматическим и должен был быть отключен, если вы действительно этого не хотели!


более общедоступным способом является вызов get_form в классах администратора. Он также работает для полей без базы данных. Например, здесь у меня есть поле под названием '_terminal_list' в форме, которое может использоваться в особых случаях для выбора нескольких терминальных элементов из get_list(request), а затем фильтрации на основе запроса.пользователь:

class ChangeKeyValueForm(forms.ModelForm):  
    _terminal_list = forms.ModelMultipleChoiceField( 
queryset=Terminal.objects.all() )

    class Meta:
        model = ChangeKeyValue
        fields = ['_terminal_list', 'param_path', 'param_value', 'scheduled_time',  ] 

class ChangeKeyValueAdmin(admin.ModelAdmin):
    form = ChangeKeyValueForm
    list_display = ('terminal','task_list', 'plugin','last_update_time')
    list_per_page =16

    def get_form(self, request, obj = None, **kwargs):
        form = super(ChangeKeyValueAdmin, self).get_form(request, **kwargs)
        qs, filterargs = Terminal.get_list(request)
        form.base_fields['_terminal_list'].queryset = qs
        return form