[python] Django ModelForm에서 ForeignKey 선택을 어떻게 필터링합니까?

내 안에 다음이 있다고 가정 해보십시오 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는 각각의 범위를 가지는, RatesClients. 각각 Client은 다른 것이 아니라 Rate부모로부터 선택된 기초 를 가져야합니다 .Company's RatesCompany'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어쨌든 관리 인터페이스 외부에서 작동하는지 확실하지 않습니다.

감사. (이것은 꽤 기본적인 요청처럼 보이지만 무언가를 다시 디자인해야한다면 제안에 개방적입니다.)



답변

ForeignKey는 django.forms.ModelChoiceField로 표시되며, 이는 선택 사항이 모델 QuerySet 인 ChoiceField입니다. ModelChoiceField에 대한 참조를 참조 하십시오 .

따라서 필드의 queryset속성에 QuerySet을 제공하십시오 . 양식 작성 방법에 따라 다릅니다. 명시 적 양식을 작성하면 직접 이름이 지정된 필드가 생깁니다.

form.rate.queryset = Rate.objects.filter(company_id=the_company.id)

기본 ModelForm 객체를 사용하면 form.fields["rate"].queryset = ...

이것은 뷰에서 명시 적으로 수행됩니다. 해킹이 없습니다.


답변

S.Lott의 답변 외에도 주석에서 언급 된 Guru와 마찬가지로 ModelForm.__init__함수 를 재정 의하여 쿼리 세트 필터를 추가 할 수 있습니다 . (일반 양식에 쉽게 적용 할 수 있음) 재사용에 도움이되고보기 기능을 깔끔하게 유지합니다.

class ClientForm(forms.ModelForm):
    def __init__(self,company,*args,**kwargs):
        super (ClientForm,self ).__init__(*args,**kwargs) # populates the post
        self.fields['rate'].queryset = Rate.objects.filter(company=company)
        self.fields['client'].queryset = Client.objects.filter(company=company)

    class Meta:
        model = Client

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

        if request.POST:
            form = ClientForm(the_company,request.POST)  #<-- Note the extra arg
            if form.is_valid():
                form.save()
                return HttpResponseRedirect(the_company.get_clients_url())
        else:
            form = ClientForm(the_company)

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

많은 모델에 필요한 공통 필터가있는 경우 재사용에 유용 할 수 있습니다 (일반적으로 추상 폼 클래스를 선언합니다). 예 :

class UberClientForm(ClientForm):
    class Meta:
        model = UberClient

def view(request):
    ...
    form = UberClientForm(company)
    ...

#or even extend the existing custom init
class PITAClient(ClientForm):
    def __init__(company, *args, **args):
        super (PITAClient,self ).__init__(company,*args,**kwargs)
        self.fields['support_staff'].queryset = User.objects.exclude(user='michael')

그 외에는 장고 블로그 자료를 잘 정리하고 있습니다.


답변

이것은 간단하며 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
    ....

Django는 이미 ModelAdmin에서이 내장 메소드를 문서에서 제공하므로 폼 클래스에서이를 지정할 필요는 없지만 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을 서브 클래 싱 한 다음 아래 방법을 변경하면됩니다. 최종 결과는 사용자와 관련된 콘텐츠 만 표시하고 수퍼 유저는 모든 것을 볼 수있는 사용자 인터페이스입니다.

네 가지 방법을 재정의했으며 처음 두 가지 방법으로 사용자가 아무것도 삭제할 수 없으며 관리 사이트에서 삭제 버튼도 제거합니다.

세 번째 재정의는 참조 (예 : ‘user’또는 ‘porcupine'(예시))가 포함 된 모든 쿼리를 필터링합니다.

마지막 재정의는 모델의 외래 키 필드를 필터링하여 기본 쿼리 집합과 동일한 선택 항목을 필터링합니다.

이러한 방식으로, 사용자가 자신의 객체를 망칠 수있는 관리하기 쉬운 전면 관리 사이트를 제시 할 수 있으며 위에서 언급 한 특정 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

관리 사이트에서 볼 수있는 개체를 필터링합니다.

    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

관리 사이트의 모든 외래 키 필드에 대한 선택을 필터링합니다.

    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(...)

일반 뷰를 사용할 때 매우 유용합니다!


답변

그래서, 나는 이것을 정말로 이해하려고 노력했지만 장고는 여전히 이것을 매우 간단하게하지 않는 것 같습니다. 나는 그 바보가 아니지만 간단한 해결책을 볼 수는 없습니다.

나는 이런 종류의 일에 대해 관리자보기를 재정의 해야하는 것이 일반적으로 꽤 추악하다는 것을 알았습니다. 내가 찾은 모든 예제는 관리자보기에 완전히 적용되지 않습니다.

이것은 내가 만든 모델과 같은 일반적인 상황으로, 이것에 대한 명확한 해결책이 없다는 것을 알게됩니다 …

나는이 수업을 가지고 있습니다 :

# 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에 배치되었는지는 중요하지 않습니다. 인라인에 배치하는 것이 더 합리적이지만 기본 계약을 ‘자기’로 참조하기가 어렵습니다.

이 나쁘게 필요한 바로 가기만큼 간단한 것을 아는 사람이 있습니까? 내가 이런 종류의 PHP 관리자를 만들었을 때, 이것은 기본 기능으로 간주되었습니다! 실제로, 그것은 항상 자동적이며, 당신이 정말로 원하지 않는다면 비활성화되어야했습니다!


답변

보다 공용적인 방법은 관리 클래스에서 get_form을 호출하는 것입니다. 데이터베이스 이외의 필드에서도 작동합니다. 예를 들어 여기에는 get_list (request)에서 여러 터미널 항목을 선택한 다음 request.user를 기준으로 필터링하는 특별한 경우에 사용할 수있는 양식에 ‘_terminal_list’라는 필드가 있습니다.

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