Django ModelForm'daki ForeignKey seçeneklerini nasıl filtreleyebilirim?


228

Diyelim ki benim içinde aşağıdakiler var 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)

Yani Companies, her biri bir Ratesve aralığına sahip çoklu vardır Clients. Her birinin diğerinden değil, üst öğesinden seçilen Clientbir tabanı olmalıdır .RateCompany's RatesCompany's Rates

A eklemek için bir form oluştururken Client, Companyseçenekleri kaldırmak istiyorum (zaten bir "Müşteri Ekle" düğmesiyle seçildiğinden Company) ve Rateseçenekleri de bununla sınırlamak istiyorum Company.

Bu konuda Django 1.0 nasıl giderim?

Şu anki forms.pydosyam şu anda sadece kaynak olarak görünüyor:

from models import *
from django.forms import ModelForm

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

Ve views.pyaynı zamanda temel:

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'da şablonu oluşturmadan önce aşağıdakine benzer bir şey yaparak bunu hackleyebildim:

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

ForeignKey.limit_choices_toumut verici gözüküyor ama nasıl geçeceğimi bilmiyorum the_company.idve bunun zaten Yönetici arayüzü dışında çalışıp çalışmayacağı belli değil.

Teşekkürler. (Bu oldukça basit bir istek gibi görünüyor, ancak bir şeyi yeniden tasarlamam gerekirse önerilere açığım.)


"Limit_choices_to" ile ilgili ipucu için teşekkür ederiz. Sorunuzu çözmez, ama benimki :-) Belgeler: docs.djangoproject.com/en/dev/ref/models/fields/…
guettli

Yanıtlar:


243

ForeignKey, seçimleri bir model QuerySet olan ChoiceField olan django.forms.ModelChoiceField ile temsil edilir. ModelChoiceField referansına bakın .

Bu nedenle, alanın querysetözniteliğine bir Sorgu Kümesi sağlayın . Formunuzun nasıl oluşturulduğuna bağlıdır. Açık bir form oluşturursanız, doğrudan adlandırılmış alanlarınız olur.

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

Varsayılan ModelForm nesnesini alırsanız, form.fields["rate"].queryset = ...

Bu görünümde açıkça yapılır. Etrafta saldırı yok.


Tamam, kulağa umut verici geliyor. İlgili Field nesnesine nasıl erişirim? form.company.QuerySet = Rate.objects.filter (company_id = the_company.id)? ya da bir sözlük aracılığıyla?
Tom

1
Tamam, örneği genişlettiğiniz için teşekkür ederiz, ancak form.fields ["rate"] kullanmak zorunda gibi görünüyor. Queryset "'ClientForm' nesnesinin hiçbir özellik 'oranı'" önlemek için, bir şey eksik mi? (ve örneğin tutarlı olması için form.rate.queryset de olmalıdır.)
Tom

8
Alanların sorgu kümesini formun __init__yöntemiyle ayarlamak daha iyi olmaz mıydı ?
Lakshman Prasad

1
@SLott son yorum doğru değil (veya sitem çalışmıyor olmalıdır :). Geçersiz kılma yönteminizde super (...) .__ init__ çağrısını kullanarak doğrulama verilerini doldurabilirsiniz. Bu queryset değişikliklerinden birkaçını yapıyorsanız, init yöntemini geçersiz kılarak bunları paketlemek çok daha zariftir .
michael

3
@Slott şerefe, açıklamak için 600'den fazla karakter alacağı için bir cevap ekledim. Bu soru eski olsa bile yüksek bir Google puanı alıyor.
michael

136

S.Lott'un cevabına ek olarak ve yorumlarda belirtildiği gibiGuru, ModelForm.__init__işlevi geçersiz kılarak queryset filtreleri eklemek mümkündür . (Bu normal formlara kolayca uygulanabilir) yeniden kullanımda yardımcı olabilir ve görünüm işlevini düzenli tutar.

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})

Bu, birçok modelde ortak filtreleriniz varsa (normalde soyut bir Form sınıfı beyan ederim) varsa tekrar kullanmak için yararlı olabilir. Örneğin

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')

Bunun dışında sadece orada birçok iyi olanları var Django blog malzeme yeniden yazıyorum.


, Bunun yerine args ve kwargs arasında) (__ iki kez __init args tanımlarken ilk kod parçacığı bir yazım hatası var.

6
Şerefe!

44

Bu basit ve Django 1.4 ile çalışır:

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

Bir formu sınıfında bu belirtmeniz gerekmez, ancak Django zaten içerdiğinden, ModelAdmin doğrudan bunu yapabiliyor bu yerleşik (dokümanlardan) ModelAdmin üzerine yöntemle:

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)

Bunu yapmanın daha zarif bir yolu (örneğin, kullanıcıların erişebileceği bir ön uç yönetici arayüzü oluşturmak için) ModelAdmin'i alt sınıflara ayırmak ve sonra aşağıdaki yöntemleri değiştirmektir. Net sonuç, SADECE kendileriyle ilgili içeriği gösteren ve size (süper kullanıcı) her şeyi görmenizi sağlayan bir kullanıcı arayüzüdür.

Dört yöntemi geçersiz kıldım, ilk ikisi bir kullanıcının herhangi bir şeyi silmesini imkansız kılıyor ve ayrıca silme düğmelerini yönetici sitesinden kaldırıyor.

Üçüncü geçersiz kılma, referans içeren herhangi bir sorguyu filtreler ('kullanıcı' veya 'kirpi' örneğinde (örnek olarak).

Son geçersiz kılma, temel queryset ile aynı kullanılabilir seçenekleri filtrelemek için modeldeki herhangi bir yabancı anahtar alanını filtreler.

Bu şekilde, kullanıcıların kendi nesneleriyle uğraşmasına izin veren, yönetilmesi kolay, öne bakan bir yönetici sitesi sunabilirsiniz ve yukarıda bahsettiğimiz belirli ModelAdmin filtrelerini yazmayı hatırlamanız gerekmez.

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)

'sil' düğmelerini kaldır:

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

silme iznini önler

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

yönetici sitesinde görüntülenebilecek nesneleri filtreler:

    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

yönetici sitesindeki tüm yabancı anahtar alanları için seçenekleri filtreler:

    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)

1
Ve bu benzer ilgi alanları ile birden fazla modeladmins için genel bir özel form olarak iyi çalışır eklemeliyim.
nemesisfixx

Django 1.4+
Rick Westera

16

Bunu CreateView gibi genel bir görünümle yapmak için ...

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'))

bunun en önemli kısmı ...

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

, yazımı buradan oku


4

Formu oluşturmadıysanız ve sorgu kümesini değiştirmek istiyorsanız şunları yapabilirsiniz:

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

Bu, genel görünümler kullanırken oldukça kullanışlıdır!


2

Yani, bunu gerçekten anlamaya çalıştım, ama görünüşe göre Django bunu hala çok basitleştirmiyor. Ben o kadar aptal değilim, ama basit bir çözüm göremiyorum.

Bu tür bir şey için Yönetici görünümlerini geçersiz kılmanın genellikle oldukça çirkin olduğunu düşünüyorum ve bulduğum her örnek Yönetici görünümleri için hiçbir zaman tam olarak geçerli değildir.

Yaptığım modellerde bu kadar yaygın bir durum, bunun açık bir çözümü olmadığını korkunç buluyorum ...

Şu sınıflarım var:

# 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)

Bu, Yönetici için Şirket'i ayarlarken bir sorun yaratır, çünkü hem Sözleşme hem de Konum için satırları vardır ve Sözleşme'nin Konum için m2m seçenekleri, şu anda düzenlemekte olduğunuz Şirkete göre düzgün bir şekilde filtrelenmez.

Kısacası, böyle bir şey yapmak için bazı yönetici seçeneği gerekir:

# 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')

Sonuçta, filtreleme işleminin CompanyAdmin tabanına mı yoksa ContractInline'a mı yerleştirildiği umurumda değil. (Satır içi olarak yerleştirmek daha mantıklıdır, ancak temel Sözleşmeye 'benlik' olarak atıfta bulunmayı zorlaştırmaktadır.)

Dışarıda, bu çok ihtiyaç duyulan kısayol kadar basit bir şey bilen biri var mı? Bu tür şeyler için PHP yöneticileri yaptığımda, bu temel işlevsellik olarak kabul edildi! Aslında, her zaman otomatikti ve gerçekten istemediyseniz devre dışı bırakılmak zorunda kaldı!


0

Daha genel bir yol Yönetici sınıflarında get_form'u çağırmaktır. Ayrıca veritabanı olmayan alanlar için de çalışır. Örneğin, burada get_list (istek) birkaç terminal öğeleri seçmek, sonra request.user dayalı filtreleme için özel durumlarda kullanılabilecek '_terminal_list' adlı bir alan var:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.