Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/django/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何在限制选项的上下文中过滤ModelAdmin自动完成字段结果_Python_Django_Django Models_Django Admin - Fatal编程技术网

Python 如何在限制选项的上下文中过滤ModelAdmin自动完成字段结果

Python 如何在限制选项的上下文中过滤ModelAdmin自动完成字段结果,python,django,django-models,django-admin,Python,Django,Django Models,Django Admin,我希望利用Django的autocomplete管理小部件,它尊重引用模型字段限制 例如,我有以下集合模型,该模型具有带有指定选项的属性种类 class Collection(models.Model): ... COLLECTION_KINDS = ( ('personal', 'Personal'), ('collaborative', 'Collaborative'), ) name = models.CharField()

我希望利用Django的autocomplete管理小部件,它尊重引用模型字段限制

例如,我有以下
集合
模型,该模型具有带有指定选项的属性
种类

class Collection(models.Model):
    ...
    COLLECTION_KINDS = (
        ('personal', 'Personal'),
        ('collaborative', 'Collaborative'),
    )

    name = models.CharField()
    kind = models.CharField(choices=COLLECTION_KINDS)
    ...
另一个模型
ScheduledCollection
引用了
Collection
,其中包含一个
ForeignKey
字段,该字段实现了
limit\u choices\u to
选项。该模型的目的是将元数据与特定用例的
集合关联起来

class ScheduledCollection(models.Model):
    ...
    collection = models.ForeignKey(Collection, limit_choices_to={'kind': 'collaborative'})

    start_date = models.DateField()
    end_date = models.DateField()
    ...
这两个模型都在
ModelAdmin
注册。
集合
模型实现了
搜索字段

@register(models.Collection)
class CollectionAdmin(ModelAdmin):
    ...
    search_fields = ['name']
    ...
ScheduledCollection
模型实现了
autocomplete\u字段

@register(models.ScheduledCollection)
class ScheduledCollectionAdmin(ModelAdmin):
    ...
    autocomplete_fields = ['collection']
    ...
这是可行的,但并不完全如预期的那样。自动完成从
集合生成的视图中检索结果。
limit\u choices\u to
不过滤结果,仅在保存时强制执行

建议在
CollectionAdmin
模型上实现
get\u search\u results
get\u queryset
。我可以这样做并过滤结果。但是,这会全面更改
集合
搜索结果。我不知道如何在
get\u search\u results
get\u queryset
中获得更多上下文,以便根据关系有条件地筛选结果

在我的例子中,我希望有几个
集合
的选择,以及几个具有不同
的元模型将选择限制为
选项,并使自动完成功能尊重这些限制

我不希望这自动工作,也许这应该是一个功能要求。此时,我不知如何根据选择限制(或任何条件)过滤自动完成的结果


如果不使用
autocomplete\u字段
,Django管理员的默认
小部件会过滤结果。

我也遇到了同样的问题。这有点骇人,但我的解决方案是:

  • 覆盖正在搜索并要筛选的ModelAdmin的get_search_结果
  • 使用request-referer头获取需要的上下文,以便根据关系的来源应用适当的过滤器
  • 从相应的外键元中获取限制选项
  • 预过滤查询集,然后传递给超级方法
  • 因此,对于您的模型:

    @register(models.Collection)
    class CollectionAdmin(ModelAdmin):
        ...
        search_fields = ['name']
    
        def get_search_results(self, request, queryset, search_term):
            if '<app_name>/scheduledcollection/' in request.META.get('HTTP_REFERER', ''):
                limit_choices_to = ScheduledCollection._meta.get_field('collection').get_limit_choices_to()
                queryset = queryset.filter(**limit_choices_to)
            return super().get_search_results(request, queryset, search_term)
    
    
    @寄存器(models.Collection)
    类集合管理员(模型管理员):
    ...
    搜索_字段=['name']
    def获取搜索结果(自我、请求、查询集、搜索项):
    如果request.META.get('HTTP_REFERER','')中的'/scheduledcollection/':
    limit_choices_to=ScheduledCollection.\u meta.get_字段('collection')。get_limit_choices_to()
    queryset=queryset.filter(**限制选项到)
    return super().获取搜索结果(请求、查询集、搜索项)
    
    这种方法的一个缺点是,我们仅有的上下文是在管理中编辑的模型,而不是模型的哪个字段,因此如果ScheduledCollection模型有两个集合自动完成字段(例如个人集合和协作集合)对于不同的限制选项,我们无法从referer头推断出这一点,并以不同的方式对待它们。此外,内联管理员将根据其内联的父对象拥有referer url,而不是反映他们自己的模型。但它适用于基本情况


    希望Django的新版本将有一个更干净的解决方案,例如autocomplete select小部件发送一个带有它正在编辑的模型和字段名的额外查询参数,以便get_search_结果可以准确地查找所需的过滤器,而不是(可能不准确地)从referer头进行推断。

    触发http referer很难看,因此我制作了一个更好的版本:将AutocompleteSelect子类化,并发送额外的查询参数,以允许get_搜索结果自动查找正确的限制选项。只需在ModelAdmin中包含此mixin(对于源模型和目标模型)。作为奖励,它还增加了ajax请求的延迟,这样您就不会在键入过滤器时向服务器发送垃圾邮件,使选择范围更广,并设置搜索字段属性(设置为“translations\uuu name”,这对于我的系统是正确的,为您的系统定制,或者像以前一样在ModelAdmins上单独忽略和设置):


    这里是另一个只获取自动完成字段中选项子集的解决方案。此解决方案不会更改主模型(
    Collection
    )的默认行为,因此您仍然可以在应用程序中使用autocomplete with the full set查看其他视图

    以下是它的工作原理:

    具有管理器的集合的代理模型 创建代理模型以表示
    集合
    的子集,例如
    CollaborativeCollection
    以表示“协作”类型的集合。您还需要一个管理器将代理模型的初始查询集限制为预期的子集

    class CollaborativeCollectionManager(models.Manager):
    def get_queryset(自我):
    返回(
    超级()
    .get_queryset()
    .filter(种类=“协作”)
    )
    类CollaborativeCollection(models.Model):
    类元:
    proxy=True
    对象=CollaborativeCollectionManager()
    
    更新外键以使用代理模型 接下来更新
    ScheduledCollection
    中的外键以改用代理模型。请注意,如果您不需要将
    limit\u choices\u to
    功能用于其他用途,则可以将其删除

    class ScheduledCollection(models.Model):
    ...
    collection=models.ForeignKey(CollaborativeCollection)
    开始日期=models.DateField()
    结束日期=models.DateField(
    
    from django.contrib.admin import widgets
    from django.utils.http import urlencode
    from django.contrib.admin.options import ModelAdmin
    
    class AutocompleteSelect(widgets.AutocompleteSelect):
        """
        Improved version of django's autocomplete select that sends an extra query parameter with the model and field name
        it is editing, allowing the search function to apply the appropriate filter.
        Also wider by default, and adds a debounce to the ajax requests
        """
    
        def __init__(self, rel, admin_site, attrs=None, choices=(), using=None, for_field=None):
            super().__init__(rel, admin_site, attrs=attrs, choices=choices, using=using)
            self.for_field = for_field
    
        def build_attrs(self, base_attrs, extra_attrs=None):
            attrs = super().build_attrs(base_attrs, extra_attrs=extra_attrs)
            attrs.update({
                'data-ajax--delay': 250,
                'style': 'width: 50em;'
            })
            return attrs
    
        def get_url(self):
            url = super().get_url()
            url += '?' + urlencode({
                'app_label': self.for_field.model._meta.app_label,
                'model_name': self.for_field.model._meta.model_name,
                'field_name': self.for_field.name
            })
            return url
    
    
    class UseAutocompleteSelectMixin():
        """
        To avoid ForeignKey fields to Event (such as on ReportColumn) in admin from pre-loading all events
        and thus being really slow, we turn them into autocomplete fields which load the events based on search text
        via an ajax call that goes through this method.
        Problem is this ignores the limit_choices_to of the original field as this ajax is a general 'search events'
        without knowing the context of what field it is populating. Someone else has exact same problem:
        https://stackoverflow.com/questions/55344987/how-to-filter-modeladmin-autocomplete-fields-results-with-the-context-of-limit-c
        So fix this by adding extra query parameters on the autocomplete request,
        and use these on the target ModelAdmin to lookup the correct limit_choices_to and filter with it.
        """
    
        # Overrides django.contrib.admin.options.ModelAdmin#formfield_for_foreignkey
        # Is identical except in case db_field.name is in autocomplete fields it constructs our improved AutocompleteSelect
        # instead of django's and passes it extra for_field parameter
        def formfield_for_foreignkey(self, db_field, request, **kwargs):
            if db_field.name in self.get_autocomplete_fields(request):
                db = kwargs.get('using')
                kwargs['widget'] = AutocompleteSelect(db_field.remote_field, self.admin_site, using=db, for_field=db_field)
                if 'queryset' not in kwargs:
                    queryset = self.get_field_queryset(db, db_field, request)
                    if queryset is not None:
                        kwargs['queryset'] = queryset
    
                return db_field.formfield(**kwargs)
    
            return super().formfield_for_foreignkey(db_field, request, **kwargs)
    
        # In principle we could add this override in a different mixin as adding the formfield override above is needed on
        # the source ModelAdmin, and this is needed on the target ModelAdmin, but there's do damage adding everywhere so combine them.
        def get_search_results(self, request, queryset, search_term):
            if 'app_label' in request.GET and 'model_name' in request.GET and 'field_name' in request.GET:
                from django.apps import apps
                model_class = apps.get_model(request.GET['app_label'], request.GET['model_name'])
                limit_choices_to = model_class._meta.get_field(request.GET['field_name']).get_limit_choices_to()
                if limit_choices_to:
                    queryset = queryset.filter(**limit_choices_to)
            return super().get_search_results(request, queryset, search_term)
    
        search_fields = ['translations__name']
    
    
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
            if db_field.name in self.get_autocomplete_fields(request) or\
                    db_field.name in self.get_autocomplete_cb_fields(request):
                db = kwargs.get('using')
                if db_field.name in self.get_autocomplete_cb_fields(request):
                    kwargs['widget'] = AutocompleteSelectCb(
                        db_field, self.admin_site, using=db, for_field=db_field)
                else:
                    kwargs['widget'] = AutocompleteSelect(
                        db_field, self.admin_site, using=db, for_field=db_field)
                if 'queryset' not in kwargs:
                    queryset = self.get_field_queryset(db, db_field, request)
                    if queryset is not None:
                        kwargs['queryset'] = queryset
    
                return db_field.formfield(**kwargs)
    
            return super().formfield_for_foreignkey(db_field, request, **kwargs)