django管理员更改列表中的自定义list_editable字段,它不直接对应于模型字段

时间:2022-09-12 09:00:52

Assuming my model looks like this (this is a simplified example):

假设我的模型看起来像这样(这是一个简化的例子):

class Person(Model):
  first_name = CharField(...)
  last_name = CharField(...)

  def name():
    return first_name + ' ' + last_name

Displaying the name as a single column in the admin change list is easy enough. However, I need a single, editable "name" field that is editable from the list page, which I can then parse to extract and set the model field values. The parsing isn't a concern. I am just wondering how to have an editable form field on the list page that doesn't correspond directly to a model field.

在管理更改列表中将名称显示为单个列非常简单。但是,我需要一个可编辑的“名称”字段,该字段可从列表页面进行编辑,然后我可以解析该字段以提取和设置模型字段值。解析不是问题。我只是想知道如何在列表页面上有一个不直接对应模型字段的可编辑表单字段。

3 个解决方案

#1


21  

You should be able to do this in pure Python with a bit of work. Basically, you need to use the get_changelist_form method on the admin class to tell it to use a custom form rather than a default ModelForm for your instances, then initialize the custom field's value properly (most conveniently in the form's __init__ method) and specialize the save behavior of that form to set the first_name and last_name values.

你应该能够在纯Python中做一些工作。基本上,您需要在admin类上使用get_changelist_form方法告诉它使用自定义表单而不是默认的ModelForm用于您的实例,然后正确初始化自定义字段的值(最方便地在表单的__init__方法中)并专门保存该表单的行为设置first_name和last_name值。

Something like this should be a start:

这样的事情应该是一个开始:

class PersonChangeListForm(forms.ModelForm):
    class Meta:
        model = Person
    name = forms.CharField()

    def __init__(self, *args, **kwargs):
        instance = kwargs.get('instance')
        if instance:
            initial = kwargs.get('initial', {})
            initial['name'] = '%s %s' % (instance.first_name, instance.last_name)
            kwargs['initial'] = initial
        super(PersonChangeListForm, self).__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        # use whatever parsing you like here
        first_name, last_name = self.cleaned_data['name'].split(None, 1)
        self.cleaned_data['first_name'] = first_name
        self.cleaned_data['last_name'] = last_name
        super(PersonChangeListForm, self).save(*args, **kwargs)

class PersonAdmin(admin.ModelAdmin):
    def get_changelist_form(self, request, **kwargs):
        return PersonChangeListForm

You will also need to declare a list_editable value that evaluates to True when tested as a boolean - some of the admin processing short-circuits without using the formset if list_editable does not evaluate as True.

您还需要声明一个list_editable值,当测试为布尔值时,该值的值为True - 如果list_editable未计算为True,则不使用formset的一些管理处理短路。

If you have no other fields you want to be editable, this gets more complicated. The class validation requires that everything in the list_editable sequence be an editable field that's declared in list_display as well and is not a display link field. I think the options there are either to override the admin class's changelist_view method to use the full processing even if list_editable is not true, or to define a custom subclass of list or tuple that evaluates to True even when empty so it can pass validation. The former would require repeating a great deal of standard code and significantly increase your maintenance burden if you ever upgrade, while the latter is a counterintuitive hack and would not at all surprise me if it had unexpected consequences.

如果您没有其他字段可以编辑,则会变得更复杂。类验证要求list_editable序列中的所有内容都是一个可编辑的字段,该字段也在list_display中声明,而不是显示链接字段。我认为那里的选项要么覆盖admin类的changelist_view方法,即使list_editable不为true也要使用完整的处理,或者定义list或tuple的自定义子类,即使在为空时也可以通过验证来计算为True。前者需要重复大量的标准代码,并且如果你升级会显着增加你的维护负担,而后者是一个违反直觉的黑客,如果它有意想不到的后果,我一点都不会感到惊讶。

Neither are good options, so I hope you have at least one other field that makes sense to include in list_editable.

这两个都不是很好的选择,所以我希望你至少有一个其他字段可以包含在list_editable中。

#2


2  

I just tried a quick mock-up of the problem in the admin. It seems that the admin validation fails for a field which is in list_editable that is not defined on the model. In short, the answer to your question seems to be no.

我刚尝试在管理员中快速模拟问题。对于未在模型上定义的list_editable中的字段,管理验证似乎失败。简而言之,你的问题的答案似乎是否定的。

However, that doesn't mean it's not doable. With a bit of Javascript, you could use X-editable

但是,这并不意味着它不可行。使用一些Javascript,您可以使用X-editable

(or roll your own), and make the "Name" column editable. Create a view to validate the data and save it to the model. Set X-editable field 'url' parameter to post to this URL. Obviously decorate your view with login_required / permissions_required etc, to make sure no-one else can edit the data.

(或滚动您自己的),并使“名称”列可编辑。创建一个视图以验证数据并将其保存到模型中。设置X-editable字段'url'参数以发布到此URL。显然用login_required / permissions_required等装饰您的视图,以确保没有其他人可以编辑数据。

#3


0  

Addition to @Peter DeGlopper answer: you may add other model field in list_editable (for example last_name), then override change_list.html (look this answer How to override templates and add to the end of file some javascrip code: templates/admin/your_app/person/change_list.html:

除了@Peter DeGlopper回答:你可以在list_editable中添加其他模型字段(例如last_name),然后覆盖change_list.html(看看这个答案如何覆盖模板并添加到文件末尾一些javascrip代码:templates / admin / your_app /person/change_list.html:

{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_list %}
...

{% block content %}
...
{% endblock %}

{% block footer %}
{{ block.super }}
<script>
    var last_names = document.querySelectorAll('.field-last_name');

    last_names.forEach(function(el) {
        var input = el.children[0];
        var text = input.value;
        input.hidden = true;
        input.style.display = 'none';
        el.append(text);
    });
</script>
{% endblock %}

#1


21  

You should be able to do this in pure Python with a bit of work. Basically, you need to use the get_changelist_form method on the admin class to tell it to use a custom form rather than a default ModelForm for your instances, then initialize the custom field's value properly (most conveniently in the form's __init__ method) and specialize the save behavior of that form to set the first_name and last_name values.

你应该能够在纯Python中做一些工作。基本上,您需要在admin类上使用get_changelist_form方法告诉它使用自定义表单而不是默认的ModelForm用于您的实例,然后正确初始化自定义字段的值(最方便地在表单的__init__方法中)并专门保存该表单的行为设置first_name和last_name值。

Something like this should be a start:

这样的事情应该是一个开始:

class PersonChangeListForm(forms.ModelForm):
    class Meta:
        model = Person
    name = forms.CharField()

    def __init__(self, *args, **kwargs):
        instance = kwargs.get('instance')
        if instance:
            initial = kwargs.get('initial', {})
            initial['name'] = '%s %s' % (instance.first_name, instance.last_name)
            kwargs['initial'] = initial
        super(PersonChangeListForm, self).__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        # use whatever parsing you like here
        first_name, last_name = self.cleaned_data['name'].split(None, 1)
        self.cleaned_data['first_name'] = first_name
        self.cleaned_data['last_name'] = last_name
        super(PersonChangeListForm, self).save(*args, **kwargs)

class PersonAdmin(admin.ModelAdmin):
    def get_changelist_form(self, request, **kwargs):
        return PersonChangeListForm

You will also need to declare a list_editable value that evaluates to True when tested as a boolean - some of the admin processing short-circuits without using the formset if list_editable does not evaluate as True.

您还需要声明一个list_editable值,当测试为布尔值时,该值的值为True - 如果list_editable未计算为True,则不使用formset的一些管理处理短路。

If you have no other fields you want to be editable, this gets more complicated. The class validation requires that everything in the list_editable sequence be an editable field that's declared in list_display as well and is not a display link field. I think the options there are either to override the admin class's changelist_view method to use the full processing even if list_editable is not true, or to define a custom subclass of list or tuple that evaluates to True even when empty so it can pass validation. The former would require repeating a great deal of standard code and significantly increase your maintenance burden if you ever upgrade, while the latter is a counterintuitive hack and would not at all surprise me if it had unexpected consequences.

如果您没有其他字段可以编辑,则会变得更复杂。类验证要求list_editable序列中的所有内容都是一个可编辑的字段,该字段也在list_display中声明,而不是显示链接字段。我认为那里的选项要么覆盖admin类的changelist_view方法,即使list_editable不为true也要使用完整的处理,或者定义list或tuple的自定义子类,即使在为空时也可以通过验证来计算为True。前者需要重复大量的标准代码,并且如果你升级会显着增加你的维护负担,而后者是一个违反直觉的黑客,如果它有意想不到的后果,我一点都不会感到惊讶。

Neither are good options, so I hope you have at least one other field that makes sense to include in list_editable.

这两个都不是很好的选择,所以我希望你至少有一个其他字段可以包含在list_editable中。

#2


2  

I just tried a quick mock-up of the problem in the admin. It seems that the admin validation fails for a field which is in list_editable that is not defined on the model. In short, the answer to your question seems to be no.

我刚尝试在管理员中快速模拟问题。对于未在模型上定义的list_editable中的字段,管理验证似乎失败。简而言之,你的问题的答案似乎是否定的。

However, that doesn't mean it's not doable. With a bit of Javascript, you could use X-editable

但是,这并不意味着它不可行。使用一些Javascript,您可以使用X-editable

(or roll your own), and make the "Name" column editable. Create a view to validate the data and save it to the model. Set X-editable field 'url' parameter to post to this URL. Obviously decorate your view with login_required / permissions_required etc, to make sure no-one else can edit the data.

(或滚动您自己的),并使“名称”列可编辑。创建一个视图以验证数据并将其保存到模型中。设置X-editable字段'url'参数以发布到此URL。显然用login_required / permissions_required等装饰您的视图,以确保没有其他人可以编辑数据。

#3


0  

Addition to @Peter DeGlopper answer: you may add other model field in list_editable (for example last_name), then override change_list.html (look this answer How to override templates and add to the end of file some javascrip code: templates/admin/your_app/person/change_list.html:

除了@Peter DeGlopper回答:你可以在list_editable中添加其他模型字段(例如last_name),然后覆盖change_list.html(看看这个答案如何覆盖模板并添加到文件末尾一些javascrip代码:templates / admin / your_app /person/change_list.html:

{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_list %}
...

{% block content %}
...
{% endblock %}

{% block footer %}
{{ block.super }}
<script>
    var last_names = document.querySelectorAll('.field-last_name');

    last_names.forEach(function(el) {
        var input = el.children[0];
        var text = input.value;
        input.hidden = true;
        input.style.display = 'none';
        el.append(text);
    });
</script>
{% endblock %}