sourcecode

Django ModelForm에서 ForeignKey 선택을 필터링하려면 어떻게 해야 합니까?

copyscript 2022. 9. 23. 00:05
반응형

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는 각각 입니다Rates ★★★★★★★★★★★★★★★★★」Clients. 각각ClientRateCompany's Rates, 다른 것이 아니다.Company's Rates.

「」를 하는 폼을 .Client , 을하겠습니다.Company의 [클라이언트 추가(에서 이미 )튼을사사사사사사택택기기)))))))))Company 및 :Rate 「」의 선택지를 지정합니다.Company뿐만 아니라.

장고 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을 합니다.queryset.폼이 어떻게 만들어지느냐에 따라 다르죠.명시적 양식을 작성할 경우 필드 이름이 직접 지정됩니다.

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

개체를 사용할 ModelForm 개체입니다.form.fields["rate"].queryset = ...

이 작업은 뷰에서 명시적으로 수행됩니다.해킹은 금물입니다.

S. be 로서 Lott의 응답은 Lott, Lott의 응답보다 우선하여 할 수 .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
    ....

폼 클래스에서 지정할 필요는 없지만 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을 서브클래스로 분류한 후 다음 메서드를 변경하는 것입니다.그 결과, 유저(슈퍼 유저)가 모든 것을 볼 수 있도록 하면서, 유저에게 관련하는 컨텐츠만을 표시하는 유저 인터페이스가 됩니다.

4개의 메서드를 덮어썼습니다.처음 2개의 메서드는 사용자가 어떤 것도 삭제할 수 없게 되어 있습니다.또한 관리 사이트에서 삭제 버튼을 삭제합니다.

세 번째 재정의는 에 대한 참조를 포함하는 쿼리를 필터링합니다(예: '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(...)

일반 보기를 사용할 때 매우 유용합니다.

그래서 정말 이해하려고 노력했지만 장고는 아직도 이 문제를 쉽게 설명하지 못하는 것 같아요.나는 그렇게 멍청하지는 않지만, 간단한 해결책이 보이지 않는다.

이런 종류의 경우 Admin 뷰를 덮어쓰는 것은 일반적으로 매우 보기 흉하며, 발견된 모든 예는 Admin 뷰에 완전히 적용되지 않습니다.

제가 만든 모델들이 흔히 겪는 일이라 이에 대한 명확한 해결책이 없다는 것이 끔찍하다고 생각합니다.

나는 다음과 같은 수업을 듣는다.

# 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에 배치되어 있어도, 계약에 배치되어 있어도 상관없습니다.Inline. (인라인에 배치하는 것이 더 효과적이지만 기본 계약을 'self'로 참조하기가 어렵습니다.)

이만큼 간단한 걸 아는 사람이 있을까?예전에 PHP 관리자를 만들었을 때, 이것은 기본 기능으로 간주되었습니다!사실, 그것은 항상 자동이었고, 만약 당신이 그것을 원하지 않는다면 비활성화 시켜야만 했다!

보다 퍼블릭한 방법은 Admin 클래스에서 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

실행 시(예를 들어 CreateView에서) ModelForm의 ForeignKey 필드의 선택을 제한하는 좋은 방법은 다음과 같습니다.limit_choices_to위해서base_fields['field_name']우선함으로써get_form_class()뷰에 표시됩니다.

예를 들어 클라이언트를 작성할 때 Rate의 선택을 URL에서 지정된 회사의 선택으로 제한하려면 다음 절차를 따릅니다.

class ClientCreateView(LoginRequired, CreateView):
    model = Client
    fields = '__all__'
    
    def get_form_class(self):
        modelform = super().get_form_class()
        modelform.base_fields['rate'].limit_choices_to = {'company': self.kwargs['company']}
        return modelform

Django 설명서에 따르면 필터를 기본 쿼리셋에 적용할 수 있습니다.__init__모델 폼의 메서드.

https://docs.djangoproject.com/en/3.1/ref/contrib/admin/ #django.contrib.admin.ModelAdmin.formfield_for_foreignkey

class CountryAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['capital'].queryset = self.instance.cities.all()

class CountryAdmin(admin.ModelAdmin):
    form = CountryAdminForm

언급URL : https://stackoverflow.com/questions/291945/how-do-i-filter-foreignkey-choices-in-a-django-modelform

반응형