簡體   English   中英

如何限制 Django raw_id_field 的外鍵選擇

[英]How to limit choices of ForeignKey choices for Django raw_id_field

當使用raw_id_fields選項顯示時,如何限制在 Django 管理中為ForeignKey字段顯示的選項?

當呈現為選擇框時, 定義自定義ModelForm以使用所需的選項設置該字段的查詢集值很簡單。 但是,當使用raw_id_fields呈現時,這個查詢集似乎被完全忽略了。 它生成指向該ForeignKey模型的鏈接,允許您通過彈出窗口從該模型中選擇任何記錄。 您仍然可以通過自定義 URL 來過濾這些值,但我找不到通過ModelAdmin執行此操作的ModelAdmin

我在我的 Django 1.8 / Python 3.4 項目中使用了類似於 FSp 的方法:

from django.contrib import admin
from django.contrib.admin import widgets
from django.contrib.admin.sites import site
from django import forms

class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget):
    def url_parameters(self):
        res = super().url_parameters()
        res['type__exact'] = 'PROJ'
        return res

class ProjectAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['blog'].queryset = Blog.objects.filter(type='PROJ')
        self.fields['blog'].widget = BlogRawIdWidget(rel=Project._meta.get_field('blog').remote_field, admin_site=site)

    class Meta:
        # Django 1.8 convenience:
        fields = '__all__'
        model = Project

class ProjectAdmin(admin.ModelAdmin):
    form = ProjectAdminForm
    raw_id_fields = ('blog',)

只選擇blog.type == 'PROJ'為外鍵Project.blogdjango.admin 因為最終用戶可能並且願意選擇任何東西,不幸的是。

下面的方法對我有用,但它是一個查詢集,會影響需要使用 Customer 模型的每個管理員。 但是,如果您有另一個管理員,例如需要不同查詢集的發票,您可能想對模型代理進行一些試驗。

模型

class Customer(models.Model):
    name = models.CharField(max_length=100)
    is_active = models.BooleanField()

class Order(models.Model):
    cust = models.ForeignKey(Customer)

行政

class CustomerAdmin(admin.ModelAdmin):         
    def queryset(self, request):
        qs = super(CustomerAdmin, self).queryset(request)           
        return qs.filter(is_active=1)

class OrderAdmin():     
    raw_id_fields = ('cust', )    

對於實際項目,我發現給定的解決方案(自定義ModelAdmin )有點過於嚴格。

我所做的,通常如下:

  • 在我的ModelAdmin創建一個自定義過濾器(例如子類化admin.SimpleListFilter ,請參閱文檔
  • 創建我的小部件ForeignKeyRawIdWidget子類,如下所示:

     class CustomRawIdWidget(ForeignKeyRawIdWidget): def url_parameters(self): """ activate one or more filters by default """ res = super(CustomRawIdWidget, self).url_parameters() res["<filter_name>__exact"] = "<filter_value>" return res

    請注意,自定義小部件所做的唯一一件事就是“預選”過濾器,而該過濾器又負責“限制”查詢集

  • 使用自定義小部件:

     class MyForm(forms.ModelForm): myfield = forms.ModelChoiceField(queryset=MyModel.objects.all(), ... widget=CustomRawIdWidget( MyRelationModel._meta.get_field('myfield').rel, admin.site))

這種方法的一個弱點是小部件選擇的過濾器不會阻止從該模型中選擇其他一些實例。 如果需要,我會覆蓋ModelAdmin.save_model(...)方法(請參閱文檔)以檢查相關實例是否僅是允許的實例。

我發現這種方法有點復雜,但比限制整個ModelAdmin靈活得多。

如果您需要根據模型實例過濾 raw_id list_view 彈出窗口,您可以使用以下示例:

1. 編寫自定義小部件

class RawIdWidget(widgets.ForeignKeyRawIdWidget):

    def url_parameters(self):
        res = super(RawIdWidget, self).url_parameters()
        object = self.attrs.get('object', None)
        if object:
            # Filter variants by product_id
            res['product_id'] = object.variant.product_id
        return res

2.在表單init上傳遞實例

class ModelForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(ModelForm, self).__init__(*args, **kwargs)
        obj = kwargs.get('instance', None)
        if obj and obj.pk is not None:
            self.fields['variant'].widget = RawIdWidget(
                rel=obj._meta.get_field('variant').rel,
                admin_site=admin.site,
                # Pass the object to attrs
                attrs={'object': obj}
            )

我創建了一個遺傳解決方案來解決自定義參數傳遞給彈出窗口的問題。 您只需要在您的項目中復制此代碼:

from django.contrib.admin import widgets

class GenericRawIdWidget(widgets.ForeignKeyRawIdWidget):
    url_params = []

    def __init__(self, rel, admin_site, attrs=None, \
        using=None, url_params=[]):
        super(GenericRawIdWidget, self).__init__(
            rel, admin_site, attrs=attrs, using=using)
        self.url_params = url_params

    def url_parameters(self):
        """
        activate one or more filters by default
        """
        res = super(GenericRawIdWidget, self).url_parameters()
        res.update(**self.url_params)

        return res

然后,您可以像這樣使用:

field.widget = GenericRawIdWidget(YOURMODEL._meta.get_field('YOUR_RELATION').rel,
            admin.site, url_params={"<YOURMODEL>__id__exact":     object_id})

我是這樣使用的:

class ANSRuleInline(admin.TabularInline):
    model = ANSRule 
    form = ANSRuleInlineForm
    extra = 1
    raw_id_fields = ('parent',)

    def __init__(self, *args, **kwargs):
        super (ANSRuleInline,self ).__init__(*args,**kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        formfield = super(ANSRuleInline, self).formfield_for_dbfield(db_field, **kwargs)
        request = kwargs.get("request", None)
        object_id = self.get_object(request)

        if db_field.name == 'parent':
            formfield.widget = GenericRawIdWidget(ANSRule._meta.get_field('parent').rel,
                admin.site, url_params={"pathology__id__exact": object_id})

        return formfield

    def get_object(self, request):
        object_id = request.META['PATH_INFO'].strip('/').split('/')[-1]
        try:
            object_id = int(object_id)
        except ValueError:
            return None
        return object_id

當我使用GenericRawIdWidget ,我將一個字典傳遞給 url_params,它將在 url 上使用。

如果您只需要過濾靜態類型,@Dmitriy Sintsov 的回答就很好,但在我的情況下,我在兩個模型之間都有外部關系,我希望它根據我正在使用的特定 ID 進行過濾。

他的回答為基礎,假設ProjectBlog有外鍵關系,並且在選擇要過濾的Blog時,您希望它只顯示與Project相關的那些。 他的回答的這兩個變化表明:

  1. 在小部件中添加一個新變量——我將其命名為project_id
class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget):

    def __init__(self, *args, **kwargs):
        self.project_id = kwargs.pop('project_id')
        super().__init__(*args, **kwargs)
  1. 修改調用該小部件的行:
rel=Project._meta.get_field('blog').remote_field, admin_site=site,
        project_id=self.instance.project.id)

完整代碼如下:

from django.contrib import admin
from django.contrib.admin import widgets
from django.contrib.admin.sites import site
from django import forms

class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget):

    def __init__(self, *args, **kwargs):
        self.project_id = kwargs.pop('project_id')
        super().__init__(*args, **kwargs)

    def url_parameters(self):
        res = super().url_parameters()
        res['type__exact'] = 'PROJ'
        return res

class ProjectAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['blog'].queryset = Blog.objects.filter(type='PROJ')
        self.fields['blog'].widget = BlogRawIdWidget(rel=Project._meta.get_field('blog').remote_field, admin_site=site,
        project_id=self.instance.project.id)

    class Meta:
        # Django 1.8 convenience:
        fields = '__all__'
        model = Project

class ProjectAdmin(admin.ModelAdmin):
    form = ProjectAdminForm
    raw_id_fields = ('blog',)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM