在 Django 1.11 admin 中创建新对象时评估只读字段

Posted

技术标签:

【中文标题】在 Django 1.11 admin 中创建新对象时评估只读字段【英文标题】:Evaluate a readonly field upon creating a new object in Django 1.11 admin 【发布时间】:2018-11-10 05:40:53 【问题描述】:

假设有以下模型

# models.py
class Person(models.Model):
    name = models.CharField(max_length=40)
    birthdate = models.DateField()
    age = models.CharField(max_length=3) # dont mind the type, this is just an example :)

期望的行为是在用户创建新的Person 对象时隐藏age 字段,并在用户提交表单时评估其值。此外,在管理员中查看对象实例时,age 字段应该是可见的。

为了实现这一点,我创建了一个 ModelAdmin 和一个自定义 ModelForm,如下所示

# admin.py
from django.contrib import admin
from django import forms
from .models import Person
from dateutil.relativedelta import relativedelta
import datetime

class PersonForm(forms.ModelForm):
    def clean(self):
        cleaned_data = super(PersonForm, self).clean()
        birthdate = cleaned_data.get('birthdate')
        cleaned_data['age'] = relativedelta(datetime.date.today(), birthdate).years
        return cleaned_data

class PersonAdmin(admin.ModelAdmin):
    form = PersonForm
    exclude = ('age',)

admin.site.register(Person, PersonAdmin)

但是,在提交表单后,age 字段不会被填充,可能是因为它被排除在外。如果我删除 exclude 则它可以工作,但表单显示 age 字段,这不是所需的行为。

【问题讨论】:

你应该考虑你是否真的需要一个年龄字段。它需要在每个生日时以某种方式改变。最好通过在需要时从出生日期计算当前年龄的方法或属性来提供。 【参考方案1】:

您可以使用ModelAdminsave_model 方法代替自定义表单:

 class PersonAdmin(admin.ModelAdmin):

    def save_model(self, request, obj, form, change):
        birthdate = form.cleaned_data.get('birthdate')
        age = relativedelta(datetime.date.today(), birthdate).years 
        obj.age = age
        super().save_model(request, obj, form, change)

    def get_exclude(self, request, obj=None):
        if not obj:
            return ('age',)
        return self.exclude  

你也可以使用readonly_fields:

 class PersonAdmin(admin.ModelAdmin):
    readonly_fields = ('age',)

    def save_model(self, request, obj, form, change):
        birthdate = form.cleaned_data.get('birthdate')
        age = relativedelta(datetime.date.today(), birthdate).years 
        obj.age = age
        super().save_model(request, obj, form, change)

【讨论】:

谢谢。查看对象实例时,age 字段不可见。我将编辑我的问题以包含此内容。 @Demetris 正确,表单验证不会影响age 字段。【参考方案2】:

或者您可以隐藏表单中的输入。

# admin.py
from django.contrib import admin
from django import forms
from .models import Person
from dateutil.relativedelta import relativedelta
import datetime

class PersonForm(forms.ModelForm):
    age = forms.CharField(widget=forms.HiddenInput, required=False)
    def clean(self):
        cleaned_data = super(PersonForm, self).clean()
        birthdate = cleaned_data.get('birthdate')
        cleaned_data['age'] = relativedelta(datetime.date.today(), birthdate).years
        return cleaned_data

只有当 object 为 none 时才能使用该表单(创建案例)。

class PersonAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        if not obj:
            kwargs['form'] = PersonForm
        return super().get_form(request, obj, **kwargs)

admin.site.register(Person, PersonAdmin)

【讨论】:

在管理员中查看/编辑对象实例时,这不会也隐藏它吗?

以上是关于在 Django 1.11 admin 中创建新对象时评估只读字段的主要内容,如果未能解决你的问题,请参考以下文章

Sonata Admin:无法在产品中创建新对象

在drupal的admin/config页中创建新块

Django 1.11 admin:创建 OneToOne 关系,它是 admin 中的对象

Django 1.11 admin list_filter 在另一个模型中包含字段

在 django 中创建新用户不提供散列密码

为啥我不能在 Wordpress 中创建新帖子?警告:从第 716 行 /public_html/wp-admin/includes/post.php 中的空值创建默认对象