django admin 中的依赖多对象验证

dependent multi object validation in django admin

TimeClass 的每个 "time range" 条目相互依赖。

它们不能重叠并且 start_time < end_time。

models.py

class Xyz(models.Model):
    ...   

class TimeRangeClass(models.Model)
    start_time = models.TimeField()
    end_time = models.TimeField()
    xyz = models.ForeignKey(Xyz)
    # other fields here

    def clean(self):
        # Here I loop through TimeRangeClass.objects.all() and 
        # check for conflicts through my custom "my_validator_method".
        # If there is a conflict I throw an error
        #(I've since modified it to just be one single query as per Titusz advice)             
        for each in TimeRangeClass.objects.filter(xyz=self.xyz).exclude(id=self.id):
            my_validator_method(start_time1=self.start_time, 
                                end_time1=self.end_time, 
                                start_time2=each.start_time, 
                                end_time2=each.end_time)

admin.py

from .models import TimeRangeClass, Xyz
class TimeRangeClassInLine(admin.TabularInline):
    model = TimeRangeClass
    extra = 3

@admin.register(Xyz)
class Xyz(admin.ModelAdmin):
    exclude = []
    inlines = [TimeRangeClassInLine]

问题:我可以通过管理员一次 edit/add 多个 TimeRangeClass。但是考虑到 models.Model clean 方法一次只评估 1 个更改,我无法相互验证多个编辑。

示例:

  1. 保存 Entry1 和 Entry2 不冲突

  2. 更改 Entry2 以产生验证错误

  3. 调整 Entry1(而不是 #2),使它们不重叠

  4. 这没有注册,因为两个更改都没有写入数据库。

我正在寻找解决方法。

问题的一些提示:

检查重叠行时,不应遍历整个 table。只需过滤有问题的行...类似于:

overlaps = TimeRangeClass.objects.filter(
    Q(start_time__gte=self.start_time, start_time__lt=self.end_time) | 
    Q(end_time__gt=self.start_time, end_time__lte=self.end_time)
)

overlaps 现在是一个查询集,它会在您迭代它时进行评估,并且仅 returns 冲突的对象。

如果您将 Django 与 postgres 一起使用,您应该查看 https://docs.djangoproject.com/es/1.9/ref/contrib/postgres/fields/#datetimerangefield

一旦您有了冲突的对象,您应该能够在函数内更改它们的开始和结束时间并保存更改。 Model.save() 不会自动调用 model.clean() 方法。但请注意,如果您从 Django 管理中保存一个对象,它 在保存之前调用 model.clean() 方法。

类似这样的事情:

def clean():
    overlaps = TimeRangeClass.overlaps.for_trc(self)
    for trc_object in overlaps:
        fixed_object = fix_start_end(trc_object, self)
        fixed_object.save()

如果您有勇气,您还应该阅读事务,使数据库中的多个对象的变更全部成功或全部失败,并且没有介于两者之间。

def clean():
    with transaction.atomic():
        # do your multi object magic here ...

关于澄清问题的更新:

如果您想要验证或 pre/process 来自管理员内联的数据,您必须连接到相应的 ModelAdmin 方法。有多种方法可以解决这个问题。我想最简单的方法是覆盖 ModelAdmin.save_fromset。在保存之前,您可以在此处访问所有内联表单。