如何在管理中间页面中重用 Django 的管理外键小部件

How to reuse Django's admin foreign key widget in admin intermediate pages

我无法在 Django 的文档中的任何地方找到答案。不过,我并不感到惊讶,因为这个问题有点太复杂了,无法向搜索引擎提问。

我处于这样一种情况,我需要能够为 Django 管理站点上的模型的一个或多个条目重新分配 ForeignKey 字段。

到目前为止,我尝试使用自定义 action so that I can select the records I'm interested in and modify them all at once. But, then, I need to select the new related object I want their fk to be reassigned to. So, what I thought to do is an intermediate page 来实现,我会在其中显示我在管理页面周围看到的 fk 小部件:

但事实证明,这个小部件确实不是为公开使用而设计的。它没有记录并且使用起来非常复杂。到目前为止,我花了几个小时深入研究 Django 的代码,试图弄清楚如何使用它。

我觉得我正在尝试在这里做一些非常非常奇特的事情,所以,如果有其他解决方案,我都会听到。

重写 YourModelAdmin 的更改列表模板 class 除了添加按钮之外再添加一个按钮。

@admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
    change_list_template = "custom_your_model_change_list.html"

在custom_your_model_change_list.html,

{% extends "admin/change_list.html" %}

{% block  object-tools-items %}
    <li>
      <a class="button" href="{% url 'your_reassign_url_name' %}">Reassign</a>
    </li>
{{ block.super }}
{% endblock %}

将视图映射到 'your_reassign_url_name' 以处理您的请求。

在urls.py,

urlpatterns = [
    path('reassign/', YourReassignView, name='your_reassign_url_name'),
]

forms.py,

class ReassignForm(forms.Form):
    # your reassign field
    reassign_field = forms.ModelChoiceField(queryset='your queryset')
    # Select multiple objects 
    updatable_objects = forms.ModelMultipleChoiceField(queryset='All objects queryset',
                                                    widget=forms.CheckboxSelectMultiple)

在 views.py 中,根据 GET 请求,您呈现一个包含必填字段的表单,然后提交您的更新数据(重新分配值),然后您重定向管理 change_list 页面。

def YourReassignView(request):
    if request.method == 'POST':
        form = ReassignForm(request.POST)
        if form.is_valid():
            # you get value after form submission
            reassign_field = form.cleaned_data.get('reassign_field')
            updatable_objects = form.cleaned_data.get('updatable_objects')

            # your update query
            YourModel.objects.filter(
                    id__in=updatable_objects.values('id')
                ).update(field_name=reassign_field)

            #after that you redirect admin change_list page with a success message
            messages.success(request, 'Successfully reassign')
            return redirect(reverse('admin:app_label_model_name_changelist'))
        else:
            context_data = {'form': form}
            return render(request, 'reassign_template.html', context=context_data)
    else:
        form = ReassignForm()
        context_data = {'form': form}
        return render(request, 'reassign_template.html', context=context_data)

在reassign_template.html,

<form method="POST" action="{% url 'your_reassign_url_name' %}">
    {% csrf_token %}
    {{ form.as_p }}
    <br>
    <input type="submit" value="submit">
</form>

As shahbaz ahmad suggested, you can use ModelAdmin's autocomplete_fields 创建一个 select 自动补全。

但是如果您受困于 Django 的外键小部件,例如,因为您的记录看起来相同并且在自动完成中无法区分,那么有一个解决方案。

事实证明 ModelAdmin 有一个 get_form 方法,您可以使用它来检索管理页面上使用的 ModelForm。此方法接受一个 fields kwargs,您可以使用它来 select 您想要在表单中检索的字段。像这样使用它:

class MyAdmin(ModelAdmin):
    # define the admin subpath to your intermediate page
    def get_urls(self):
        return [
            path(
                "intermediate_page/",
                self.admin_site.admin_view(self.intermediate_page),
                name="intermediate_page",
            ),
            *super().get_urls(),
        ]

    def intermediate_page(self, request):
        context = {
            # The rest of the context from admin
            **self.admin_site.each_context(request),
            # Retrieve the admin form
            "form": self.get_form(
                request,
                fields=[], # the fields you're interested in
            )
        }

        return render(request, "admin/intermediate_page.html", context)

如果您的字段是只读的——这是我的情况——可编辑字段有一个解决方法:您可以覆盖 get_form.

调用的 get_readonly_fields 方法

此方法接受一个 obj 参数,该参数通常采用正在编辑的对象的模型,或者在创建新条目时采用 None。您可以劫持此参数以强制 get_readonly_fields 从只读字段中排除字段:

def get_readonly_fields(self, request, obj=None):
    readony_fields = super().get_readonly_fields(request, obj)
    if not (
        isinstance(obj, dict)
        and isinstance(obj.get("exclude_from_readonly_fields"), Collection)
    ):
        return readony_fields

    return set(readony_fields) - set(obj["exclude_from_readonly_fields"])

get_form 也有这个 obj 参数,它传递给 get_readonly_fields 所以你可以这样称呼它:

# the fields you're interested in
include_fields = []

self.get_form(
    request,
    obj={"exclude_from_readonly_fields": include_fields},
    fields=include_fields
)