Python Django admin不调用对象';Let’我们要尽早保存方法

Python Django admin不调用对象';Let’我们要尽早保存方法,python,django,Python,Django,我在Django有两个应用程序,其中一个应用程序的模型(ScopeItem)在创建实例时必须同时创建另一个应用程序模型的实例(Workflow);i、 e.ScopeItem包含其工作流程 当从外壳上尝试时,这很好地工作。创建新的ScopeItem将创建一个工作流,并将其存储在ScopeItem中。在admin中,我得到一个错误,工作流属性是必需的。该属性未填充,模型定义要求对其进行设置。覆盖的save方法会执行此操作。因此,我的问题是,如何在签入管理发生之前调用save 如果我在admin中选

我在Django有两个应用程序,其中一个应用程序的模型(
ScopeItem
)在创建实例时必须同时创建另一个应用程序模型的实例(
Workflow
);i、 e.
ScopeItem
包含其工作流程

当从外壳上尝试时,这很好地工作。创建新的
ScopeItem
将创建一个
工作流
,并将其存储在
ScopeItem
中。在admin中,我得到一个错误,
工作流
属性是必需的。该属性未填充,模型定义要求对其进行设置。覆盖的
save
方法会执行此操作。因此,我的问题是,如何在签入管理发生之前调用
save

如果我在admin中选择一个现有的
工作流
实例并保存(然后成功),那么我可以看到稍后调用了我的
保存
方法,并创建了一个新的
工作流
,并将其附加到
ScopeItem
实例。只是说太晚了

我知道我可以允许
ScopeItem
中的
workflow
属性为空,或者合并
ScopeItem
workflow
类,以避免与管理员发生问题。不过,这两种方法以后都会带来麻烦,我希望避免这种黑客行为

另外,我不想在
保存项目中重复代码。仅仅从那里调用
save
,显然并不能解决问题

以下是
scopeitems/models.py
中的代码:

class ScopeItem(models.Model):
    title = models.CharField(max_length=64)
    description = models.CharField(max_length=4000, null=True)
    workflow = models.ForeignKey(Workflow)

    def save(self, *args, **kwargs):
        if not self.id:
            workflow = Workflow(
                description='ScopeItem %s workflow' % self.title,
                status=Workflow.PENDING)
            workflow.save()
            self.workflow = workflow
        super(ScopeItem, self).save(*args, **kwargs)
from django.utils.timezone import now

class Workflow(models.Model):
    PENDING = 0
    APPROVED = 1
    CANCELLED = 2
    STATUS_CHOICES = (
        (PENDING, 'Pending'),
        (APPROVED, 'Done'),
        (CANCELLED, 'Cancelled'),
    )
    description = models.CharField(max_length=4000)
    status = models.IntegerField(choices=STATUS_CHOICES)
    approval_date = models.DateTimeField('date approved', null=True)
    creation_date = models.DateTimeField('date created')
    update_date = models.DateTimeField('date updated')

    def save(self, *args, **kwargs):
        if not self.id:
            self.creation_date = now()
        self.update_date = now()
        super(Workflow, self).save(*args, **kwargs)
def create_workflow(title="N/A"):
    workflow = Workflow(
        description='ScopeItem %s workflow' % title,
        status=Workflow.PENDING)
    workflow.save()
    return workflow
工作流/模型.py

class ScopeItem(models.Model):
    title = models.CharField(max_length=64)
    description = models.CharField(max_length=4000, null=True)
    workflow = models.ForeignKey(Workflow)

    def save(self, *args, **kwargs):
        if not self.id:
            workflow = Workflow(
                description='ScopeItem %s workflow' % self.title,
                status=Workflow.PENDING)
            workflow.save()
            self.workflow = workflow
        super(ScopeItem, self).save(*args, **kwargs)
from django.utils.timezone import now

class Workflow(models.Model):
    PENDING = 0
    APPROVED = 1
    CANCELLED = 2
    STATUS_CHOICES = (
        (PENDING, 'Pending'),
        (APPROVED, 'Done'),
        (CANCELLED, 'Cancelled'),
    )
    description = models.CharField(max_length=4000)
    status = models.IntegerField(choices=STATUS_CHOICES)
    approval_date = models.DateTimeField('date approved', null=True)
    creation_date = models.DateTimeField('date created')
    update_date = models.DateTimeField('date updated')

    def save(self, *args, **kwargs):
        if not self.id:
            self.creation_date = now()
        self.update_date = now()
        super(Workflow, self).save(*args, **kwargs)
def create_workflow(title="N/A"):
    workflow = Workflow(
        description='ScopeItem %s workflow' % title,
        status=Workflow.PENDING)
    workflow.save()
    return workflow
scopeitems/admin.py
中,我有:

from django.contrib import admin

from .models import ScopeItem
from workflow.models import Workflow


class ScopeItemAdmin(admin.ModelAdmin):
    list_display = ('title', 'description', 'status')
    list_filter = ('workflow__status', )
    search_fields = ['title', 'description']

    def save_model(self, request, obj, form, change):
        obj.save()

    def status(self, obj):
        return Workflow.STATUS_CHOICES[obj.workflow.status][1]

admin.site.register(ScopeItem, ScopeItemAdmin)

您可以在
workflow
上设置字段
blank=True

您说过您不想允许在
范围项目
中使用“空
工作流
属性”设置
blank=True
。因此,在后端
工作流
仍将是
非空
。从Django文档:

如果字段为blank=True,则表单验证将允许输入空值

参考您的示例,您应该能够使用:

workflow = models.ForeignKey(Workflow, blank=True)

您可以在
workflow
上设置字段
blank=True

您说过您不想允许在
范围项目
中使用“空
工作流
属性”设置
blank=True
。因此,在后端
工作流
仍将是
非空
。从Django文档:

如果字段为blank=True,则表单验证将允许输入空值

参考您的示例,您应该能够使用:

workflow = models.ForeignKey(Workflow, blank=True)

您需要从管理中使用的表单中排除该字段,以便它不会被验证

class ScopeItemForm(forms.ModelForm):
    class Meta:
        exclude = ('workflow',)
        model = ScopeItem

class ScopeItemAdmin(admin.ModelAdmin):
    form = ScopeItemForm
    ...

admin.site.register(ScopeItem, ScopeItemAdmin)

您需要从管理中使用的表单中排除该字段,以便它不会被验证

class ScopeItemForm(forms.ModelForm):
    class Meta:
        exclude = ('workflow',)
        model = ScopeItem

class ScopeItemAdmin(admin.ModelAdmin):
    form = ScopeItemForm
    ...

admin.site.register(ScopeItem, ScopeItemAdmin)

@Daniel Roseman的答案是正确的,只要您在任何时候都不需要在admin中编辑工作流字段。如果确实需要编辑,则需要在管理表单上编写一个自定义的
clean()
方法

forms.py

class ScopeItemAdminForm(forms.ModelForm):
    class Meta:
        model = ScopeItem

    def clean(self):
        cleaned_data = super(ScopeItemAdminForm, self).clean()
        if 'pk' not in self.instance:
            workflow = Workflow(
                description='ScopeItem %s workflow' % self.title,
                status=Workflow.PENDING)
            workflow.save()
            self.workflow = workflow
        return cleaned_data
管理员

class ScopeItemAdmin(admin.ModelAdmin):
    form = ScopeItemAdminForm
    ...

admin.site.register(ScopeItem, ScopeItemAdmin)

@Daniel Roseman的答案是正确的,只要您在任何时候都不需要在admin中编辑工作流字段。如果确实需要编辑,则需要在管理表单上编写一个自定义的
clean()
方法

forms.py

class ScopeItemAdminForm(forms.ModelForm):
    class Meta:
        model = ScopeItem

    def clean(self):
        cleaned_data = super(ScopeItemAdminForm, self).clean()
        if 'pk' not in self.instance:
            workflow = Workflow(
                description='ScopeItem %s workflow' % self.title,
                status=Workflow.PENDING)
            workflow.save()
            self.workflow = workflow
        return cleaned_data
管理员

class ScopeItemAdmin(admin.ModelAdmin):
    form = ScopeItemAdminForm
    ...

admin.site.register(ScopeItem, ScopeItemAdmin)

回答我自己的问题:

正如@pcoronel所建议的那样,
ScopeItem
中的
workflow
属性必须设置为
blank=True
才能首先脱离表单

创建和存储新的
工作流
,还需要按照@hellsgate的建议覆盖表单的
clean
方法

为了防止代码重复,我在
workflow/models.py
中添加了一个函数:

class ScopeItem(models.Model):
    title = models.CharField(max_length=64)
    description = models.CharField(max_length=4000, null=True)
    workflow = models.ForeignKey(Workflow)

    def save(self, *args, **kwargs):
        if not self.id:
            workflow = Workflow(
                description='ScopeItem %s workflow' % self.title,
                status=Workflow.PENDING)
            workflow.save()
            self.workflow = workflow
        super(ScopeItem, self).save(*args, **kwargs)
from django.utils.timezone import now

class Workflow(models.Model):
    PENDING = 0
    APPROVED = 1
    CANCELLED = 2
    STATUS_CHOICES = (
        (PENDING, 'Pending'),
        (APPROVED, 'Done'),
        (CANCELLED, 'Cancelled'),
    )
    description = models.CharField(max_length=4000)
    status = models.IntegerField(choices=STATUS_CHOICES)
    approval_date = models.DateTimeField('date approved', null=True)
    creation_date = models.DateTimeField('date created')
    update_date = models.DateTimeField('date updated')

    def save(self, *args, **kwargs):
        if not self.id:
            self.creation_date = now()
        self.update_date = now()
        super(Workflow, self).save(*args, **kwargs)
def create_workflow(title="N/A"):
    workflow = Workflow(
        description='ScopeItem %s workflow' % title,
        status=Workflow.PENDING)
    workflow.save()
    return workflow
这使得
ScopeItemAdminForm
看起来像这样:

class ScopeItemAdminForm(forms.ModelForm):
    class Meta:
        model = ScopeItem

    def clean(self):
        cleaned_data = super(ScopeItemAdminForm, self).clean()
        cleaned_data['workflow'] = create_workflow(cleaned_data['title'])
        return cleaned_data
此外,我将
scopeitems/models.py
中的
save
方法更改为:

def save(self, *args, **kwargs):
    if not self.id:
        if not self.workflow:
            self.workflow = create_workflow(self.title)
    super(ScopeItem, self).save(*args, **kwargs)

回答我自己的问题:

正如@pcoronel所建议的那样,
ScopeItem
中的
workflow
属性必须设置为
blank=True
才能首先脱离表单

创建和存储新的
工作流
,还需要按照@hellsgate的建议覆盖表单的
clean
方法

为了防止代码重复,我在
workflow/models.py
中添加了一个函数:

class ScopeItem(models.Model):
    title = models.CharField(max_length=64)
    description = models.CharField(max_length=4000, null=True)
    workflow = models.ForeignKey(Workflow)

    def save(self, *args, **kwargs):
        if not self.id:
            workflow = Workflow(
                description='ScopeItem %s workflow' % self.title,
                status=Workflow.PENDING)
            workflow.save()
            self.workflow = workflow
        super(ScopeItem, self).save(*args, **kwargs)
from django.utils.timezone import now

class Workflow(models.Model):
    PENDING = 0
    APPROVED = 1
    CANCELLED = 2
    STATUS_CHOICES = (
        (PENDING, 'Pending'),
        (APPROVED, 'Done'),
        (CANCELLED, 'Cancelled'),
    )
    description = models.CharField(max_length=4000)
    status = models.IntegerField(choices=STATUS_CHOICES)
    approval_date = models.DateTimeField('date approved', null=True)
    creation_date = models.DateTimeField('date created')
    update_date = models.DateTimeField('date updated')

    def save(self, *args, **kwargs):
        if not self.id:
            self.creation_date = now()
        self.update_date = now()
        super(Workflow, self).save(*args, **kwargs)
def create_workflow(title="N/A"):
    workflow = Workflow(
        description='ScopeItem %s workflow' % title,
        status=Workflow.PENDING)
    workflow.save()
    return workflow
这使得
ScopeItemAdminForm
看起来像这样:

class ScopeItemAdminForm(forms.ModelForm):
    class Meta:
        model = ScopeItem

    def clean(self):
        cleaned_data = super(ScopeItemAdminForm, self).clean()
        cleaned_data['workflow'] = create_workflow(cleaned_data['title'])
        return cleaned_data
此外,我将
scopeitems/models.py
中的
save
方法更改为:

def save(self, *args, **kwargs):
    if not self.id:
        if not self.workflow:
            self.workflow = create_workflow(self.title)
    super(ScopeItem, self).save(*args, **kwargs)

这样做会给我一个
ValueError
,消息是“无法分配无:”ScopeItem.workflow“不允许空值”。来自集合中的
django/db/models/fields/related.py
,第335行(django 1.6.4)。从代码和异常中,看起来好像表单首先尝试保存空值,但没有对实例调用
save()
。这样做会给我一个
ValueError
,消息是“cannotassignnone:”ScopeItem.workflow“notallownull values.”从集合中的
django/db/models/fields/related.py
,第335行(Django 1.6.4)。从代码和异常情况来看,表单似乎首先尝试保存空值,而没有对实例调用
save()
。我需要能够在admin中更改工作流。否则这就行了。那是在管理中。确实是这样。我试图写的是,根据您的建议,我再也不能在Django admin中更改
ScopeItem
workflow
属性了。虽然这与创建时的意图相同,但我希望在更改s
ScopeItem
时,能够创建另一个现有的
Workflow
实例,并将其附加到管理表单中的
ScopeItem
。不幸的是,似乎没有选择来区分实例的创建和更改。将
工作流
仅从创建表单中排除将是完美的