Django:如何在管理界面中伪造字段?

18

我有一个模型Foo,它有几个数据库属性和几个基于组合因素计算的属性。我想把这些计算属性呈现给用户,就好像它们是数据库属性一样(后台因素将根据用户输入而更改)。在Django管理界面中是否有实现此功能的方法?


你想要在管理员更改列表或表单中呈现它们吗? - Bernhard Vallant
@lazerscience 我不确定管理员更改清单是什么。肯定是表格。 - Nick Heiner
如果你想在表单中展示它们(而不是可编辑的),并且它们作为模型上的方法可用,那么你可以将文件“change_form.html”复制到你的${TEMPLATE_ROOT}/admin/APPNAME/MODELNAME/change_form.html,并对其进行编辑。 - Elf Sternberg
@Elf 我确实希望它们是可编辑的。我希望它们像普通字段一样运作,只是不保存到数据库,而是可以进行处理以相应地更新数据。 - Nick Heiner
正如Django手册所说,“管理员仅适用于受信任的用户编辑结构化内容。”到此为止。如果您超出了管理员的结构化内容描述,JKM等人的建议是编写自己的视图以执行所需操作。试图“按照管理员方式”进行可能会让您感到痛苦。管理员是一个不错的工具,但它不应该是您管理工具箱的全部和结束。如果您的需求超出了它的范围,请编写自己的工具,并使用管理员演示层将其链接提供给用户。 - Elf Sternberg
4个回答

34

我建议您为Foo创建一个模型表单的子类(FooAdminForm),以添加自己的不由数据库支持的字段。您的自定义验证可以在ModelForm的clean_*方法中完成。

FooAdminsave_model方法中,您可以获取请求(request)、Foo实例和表单数据,因此您可以在保存实例之前/之后处理所有数据。

这是一个在Django管理界面注册了自定义表单的模型的示例:

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


class Foo(models.Model):
    name = models.CharField(max_length=30)


class FooAdminForm(forms.ModelForm):
    # custom field not backed by database
    calculated = forms.IntegerField()

    class Meta:
        model = Foo 


class FooAdmin(admin.ModelAdmin):
    # use the custom form instead of a generic modelform
    form = FooAdminForm

    # your own processing
    def save_model(self, request, obj, form, change):
        # for example:
        obj.name = 'Foo #%d' % form.cleaned_data['calculated'] 
        obj.save()


admin.site.register(Foo, FooAdmin)

根据实例数据为自定义字段提供初始值

(我不确定这是否是最佳解决方案,但它应该可以工作。)

当构建一个现有模型实例的模型表单时,会将该实例传递给它。因此,在FooAdminForm的__init__中,可以基于实例数据更改字段属性。

    def __init__(self, *args, **kwargs):
        super(FooAdminForm, self).__init__(*args, **kwargs)
        # only change attributes if an instance is passed            
        instance = kwargs.get('instance')
        if instance:
            self.fields['calculated'].initial = (instance.bar == 42)

使用BooleanField(它默认使用CheckboxInput小部件)。在Django中,表单字段接受initial关键字参数,例如:bar = forms.BooleanField(initial=True) - Reiner Gerecke
很好知道。我如何将初始值设置为支持模型实例的函数? - Nick Heiner
1
+1,非常好!唯一的评论是:使用super(FooAdminForm, self).__init_(...)而不是ModelForm.__init__(self,...)。两者都可以,但是super更受欢迎。 - brianmearns
天啊,请不要使用 base_fields。那是一个类属性!相反,首先调用 __init__(并请使用 super),然后使用 self.fields - DylanYoung
还要注意,您可以在模型表单上覆盖save,只需使用commit=False调用super(这将返回未保存的对象),根据需要修改您的模型,对对象调用save,然后在表单上调用self.save_m2m() - DylanYoung
显示剩余2条评论

7
很容易在更改列表中显示任意数据或使字段出现在表单中:list_display 随意接受实际模型属性或在模型或模型管理器上定义的方法,您可以子类化 forms.ModelForm 以向更改表单添加任何字段类型。
更难/几乎不可能的是将两者组合起来,即在更改列表上具有可以通过指定 list_editable 进行原地编辑的任意数据。 Django 似乎只接受与数据库字段相对应的真实模型属性。(即使在模型定义中使用 @property 方法也不够)。
有没有人找到一种方法,可以直接从更改列表页面编辑模型上实际不存在的字段?

4
我很乐意为此获得答案。 - haki

1

您可以在模型中使用@property装饰器(Python >= 2.4):

class Product(models.Model):

    @property
    def ranking(self):
        return 1

"

ranking" 可以在 list_display 中使用:

"
class ProductAdmin(admin.ModelAdmin):
    list_display = ('ranking', 'asin', 'title')

1
@property 不是必需的。list_display 将接受模型(或 ModelAdmin)上的任何方法。 - Chris Pratt

1
在编辑表单中,将属性名称放入readonly_fields中(仅适用于1.2及以上版本)。
在更改列表中,将其放入list_display中。

2
但我确实希望该属性是可编辑的 - 它并不直接对应于数据库行,我需要自己处理以相应地更改后端数据。 - Nick Heiner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接