Django admin:单击ForeignKey下拉列表旁边的add-another按钮时的预填充数据

时间:2014-01-22 01:54:17

标签: django django-admin

在Django Admin中,当你修改一个对象属性时,如果有一个ForeignKey,它将有一个包含所有选项的下拉框,另外还有一个“+”按钮来添加更多选项。当我点击它时,我想预先填写一些数据。

我注意到如果我可以修改网址,我可以这样做(例如:http://localhost:8000/admin/app/model/add/?field=value其中fieldvalue是以编程方式修改的。)

我想我必须覆盖forms.ModelForm使用的admin.ModelAdmin中的内容,但我不确定是什么。

4 个答案:

答案 0 :(得分:7)

Django允许您替换请求的GET dict(用于预填充管理表单)。

如果要在url中发送模型表单的字段值,Django将自动从url get参数中获取值。例如,考虑 "http://myhost/admin/app/model/add/?name=testname",它会在管理添加视图模板中预填充表单的'name'字段,其值为'testname'。

但是如果你要在你的网址中发送任何id,那么你需要通过覆盖add_view函数来修改get参数。

取自stackoverflow回答

class ArticleAdmin(admin.ModelAdmin):
    // ...

    def add_view(self, request, form_url='', extra_context=None):
        source_id = request.GET.get('source',None)
        if source_id != None:
            source = FeedPost.objects.get(id=source_id)
            // any extra processing can go here...
            g = request.GET.copy()
            g.update({
                'title':source.title,
                'contents':source.description + u"... \n\n[" + source.url + "]",
            })

            request.GET = g

        return super(ArticleAdmin, self).add_view(request, form_url, extra_context)

这只是一个例子。用你的模型和字段来表达:)

答案 1 :(得分:1)

这很老,但这可能是一个难题,被接受的答案并没有解决我的真正问题。

据我所知,问题是如何在相关模型的管理表单上预填充数据,该表单在您单击小部件旁边的绿色+(“添加其他”)后弹出。 ForeignKeyManyToManyField。如下所示。

image showing admin form with add-another and related pop-up form

根据(currently) accepted answerhere的建议,可以通过为要预填充的每个字段添加URL参数来实现。图片中也对此进行了说明。

但是,尚未解决的问题是:

究竟如何将这些URL参数添加到+(“添加其他”)链接使用的URL中?

假设预填充值取决于客户端上表单的当前状态,例如上图中选择的organization,我想使用JavaScript是合理的。

注意:如果预填充的值不依赖于表单状态也不依赖于当前请求,则other options可能更合适。

以下是用于创建上面图像的最小示例:

models.py :(为了方便起见,我们也将管理内容放在这里)

from django.db import models
from django.contrib import admin


class Organization(models.Model):
    pass


class Participant(models.Model):
    organization = models.ForeignKey(to=Organization, on_delete=models.CASCADE)


class Event(models.Model):
    organization = models.ForeignKey(to=Organization, on_delete=models.CASCADE)
    participants = models.ManyToManyField(to=Participant)


admin.site.register(Organization)
admin.site.register(Participant)
admin.site.register(Event)

现在,我们为change_form模型扩展了Django admin Event模板(基于docs),并添加了一些JavaScript。

按照these的说明设置模板文件夹,不要忘记将模板文件夹添加到DIRS中的settings.TEMPLATES

templates / admin / my_app / event / change_form.html

{% extends 'admin/change_form.html' %}
{% load static %}

{% block admin_change_form_document_ready %}
{{ block.super }}

<script type="text/javascript">
var add_participant_url = document.getElementById("add_id_participants").href;
var organization = document.getElementById("id_organization")
organization.onchange = function() {
    document.getElementById("add_id_participants").href =
        add_participant_url + "&organization=" + organization.value;
};
</script>

{% endblock %}

免责声明:我对JavaScript的了解非常有限,因此欢迎提出任何改进建议。

基本上,每当用户在“更改事件”表单上更改所选组织时,“添加其他参与者”(+)链接的URL都会更新为所选值,因此“添加参与者”上的组织字段表格将被预先填写。

答案 2 :(得分:1)

Aks 的回答对我不起作用,因为我在 source 字典中找不到 request.GET 字段。但是,我能够通过将以下函数添加到我的 TestAdmin 类来使其工作 - 这是单击添加按钮时打开的管理员

def get_form(self, request, obj=None, **kwargs):
    form = super(TestAdmin, self).get_form(request, obj, **kwargs)

    // using URL pattern to get the refrrer object id 
    admin_referer = re.match(".*(/adminame/)(.+)(/change/)", 
                                        request.META.get('HTTP_REFERER'))

    if request.GET.get('_popup', None) and admin_referer:
        test_model = TestModel.objects.get(id=admin_referer[2])
        form.base_fields['prepopulate_field'].initial = test_model
        form.base_fields['prepopulate_field'].widget.can_add_related = False
        form.base_fields['prepopulate_field'].widget.can_change_related = False
        form.base_fields['prepopulate_field'].widget.can_delete_related = False
        
    return form 

我希望有一种更好的方式来获取引用者的对象 ID,但我只能通过这种方式来工作。

答案 3 :(得分:0)

这是我的other answer的替代方法。

此替代方法不使用JavaScript,因此只能在服务器端使用,这有很多缺点。不过,难题的某些部分可能对某人有用。

基本思想是:

“添加其他”(+)按钮提供了到相关模型管理员视图的链接。

此链接的URL在related_widget_wrapper.html模板中构建。

此模板使用url_params变量,该变量是通过RelatedFieldWidgetWrapper.get_context()方法创建的。

要预先填写相关表格,我们需要在小部件的上下文中将自定义URL参数附加到此url_params变量中,以便我们可以利用默认的小部件模板。

据我所知,最简单的方法是为RelatedFieldWidgetWrapper.get_context()方法创建包装。

然后,我们扩展ModelAdmin.formfield_for_dbfield()方法,并在此处使用我们的自定义包装,以便它可以访问当前请求。

据我所知,ModelAdmin docs ...中没有提到formfield_for_dbfield方法,但这是where Django applies the RelatedFieldWidgetWrapper

这是一个最小的例子:

from django.db import models
from django.contrib import admin


class Organization(models.Model):
    pass


class Participant(models.Model):
    organization = models.ForeignKey(to=Organization, on_delete=models.CASCADE)


class Event(models.Model):
    organization = models.ForeignKey(to=Organization, on_delete=models.CASCADE)
    participants = models.ManyToManyField(to=Participant)


def wrap_extra_url_params(original_get_context, extra_url_params: str):
    def get_context_with_extra_url_params(*args, **kwargs):
        context = original_get_context(*args, **kwargs)
        if 'url_params' in context:
            context['url_params'] += '&{}'.format(extra_url_params)
        return context
    return get_context_with_extra_url_params


class EventAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, request, **kwargs):
        formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
        if db_field.name == 'participants':
            # get event organization from current request
            event_id = request.resolver_match.kwargs.get('object_id', None)
            if event_id:
                event = Event.objects.get(id=event_id)
                # override the "add another participant" link
                formfield.widget.get_context = wrap_extra_url_params(
                    original_get_context=formfield.widget.get_context,
                    extra_url_params='organization={}'.format(
                        event.organization_id))
        return formfield


admin.site.register(Organization)
admin.site.register(Participant)
admin.site.register(Event, EventAdmin)

请注意,服务器端无法知道用户是否更改了所选组织,因此仅在修改现有Event实例时有效。

在某种程度上这是可行的,但我会选择JavaScript选项。