Django Admin - One to Many -如何确保只有一个children选择了布尔字段

qnakjoqk  于 2023-05-19  发布在  Go
关注(0)|答案(2)|浏览(136)

在Django中,我有以下模型:

class System(models.Model):
    name = models.CharField(max_length=200)
    """ ... many other fields, not useful for here ..."""
    # Would it make more sense to have the primary instance here ?  
        

class Instance(models.Model):
    name = models.CharField(max_length=200)
    url = models.UrlField(max_length=200)
    system = models.ForeignKey(System, on_delete=models.PROTECT)
    is_production = models.BooleanField()

此数据使用admin进行管理。我想要的是,当系统的一个示例被标记为is_production时,该系统的所有其他示例都将其is_production字段更新为False。
此外,我感兴趣的是如何最好地设置管理员为这种情况。我将使用内联来编辑/创建示例。
但是,我不确定如何确保每个系统在生产中只能有一个示例。

  • 我是否应该使用系统上的下拉列表来选择生产示例并使用formfield_for_foreignkey进行过滤?
  • 使用管理操作,类似于:Mark as production
  • 保存后使用信号?
  • 还有什么我没想过的方法吗
fnatzsnv

fnatzsnv1#

你问了很多问题,但我会集中在我解释为主要的一个:
我想要的是,当系统的一个示例被标记为is_production时,该系统的所有其他示例都将其is_production字段更新为False。
重写Instance模型的保存方法怎么样?

class Instance(models.Model):
    name = models.CharField(max_length=200)
    url = models.URLField(max_length=200)
    system = models.ForeignKey(System, on_delete=models.PROTECT)
    is_production = models.BooleanField()

    def save(self, *args, **kwargs):
        if self.is_production:
            self.system.instance_set.exclude(id=self.id).update(is_production=False)
        super().save(*args, **kwargs)

这确保了只要保存了is_production=True的Instance示例,链接到相关System对象的所有其他Instance示例的is_production值都将更新为False。
根据您更改Instance示例的is_production值的方式,这可能适合也可能不适合您想要执行的操作。例如,这个线程讨论了如何使用.update()方法不会导致保存()方法被调用:Django .update doesn't call override save?(也在Django文档中描述,在链接线程中引用)

uemypmqf

uemypmqf2#

在Django Admin中,你只能用保存_formset()在一对多关系中创建一个内联(子)对象boolean字段True
例如,有Person模型和Email模型,其外键为Person模型,如下所示:

# "models.py"

class Person(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

class Email(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    is_used = models.BooleanField()
    email = models.EmailField()

    def __str__(self):
        return self.email

并且,Person admin具有Email内联,如下所示:

# "admin.py"

class EmailInline(admin.TabularInline):
    model = Email
    min_num = 1
    extra = 0

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    inlines = (EmailInline,)

现在,我重写save_formset(),如下所示。* 只有第一次提交的内联对象is_used=True被接受为is_used=True

# "admin.py"

# ...

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    inlines = (EmailInline,)

    def save_formset(self, request, form, formset, change):
        for obj in formset.save(): # Saves and returns submitted inline objects 
            if obj.is_used:
                # ↓ Makes all "is_used" of the specific person False except          
                # ↓ the 1st submitted inline object which is "is_used=True"
                Email.objects.filter(person=obj.person_id) \
                             .exclude(id=obj.id) \
                             .update(is_used=False)
                return # <- Don't forget to return

然后,添加主(父)对象和3个内联(子)对象,第二个和第三个内联对象是is_used=True,如下所示:

然后,只有第二个内联对象是is_used=True,如下所示:

接下来,将第三个内联对象的is_used=False更改为is_used=True,如下所示:

然后,只有第三个内联对象是is_used=True,如下所示。* 对于内联,只有更改的对象被传递到save_formset(),然后在其中处理:

此外,下面的代码可以做更多的事情:

# "admin.py"

g_counts = 0
class EmailForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        global g_counts
        if g_counts < 1:
            kwargs['initial'] = {'is_used': True}
            g_counts += 1
        super().__init__(*args, **kwargs)

class EmailInlineFormSet(forms.BaseInlineFormSet):
    
    def save_existing_objects(self, commit=True):
        self.counts = 0
        return super().save_existing_objects(commit)

    def delete_existing(self, obj, commit=True):
        if len(self.initial_forms) == len(self.deleted_forms):
            if self.counts < 1:
                self.counts += 1
                obj.is_used = True
                obj.save()
                return
        
        if commit:
            obj.delete()

        self.counts += 1

        if self.counts == len(self.deleted_forms):
            email_counts = Email.objects.filter(person=obj.person_id, is_used=True).count()
            if email_counts == 0:
                email_obj = Email.objects.filter(person=obj.person_id).first()
                email_obj.is_used = True
                email_obj.save()

class EmailInline(admin.TabularInline):
    model = Email
    formset = EmailInlineFormSet
    min_num = 1
    extra = 0
    
    def get_formset(self, request, obj=None, **kwargs):
        if not obj:
            self.form = EmailForm
        global g_counts
        g_counts = 0
        return super().get_formset(request, obj, **kwargs)

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    inlines = (EmailInline,)

    def save_formset(self, request, form, formset, change):
        objs = formset.save()
        if not objs:
            return

        true_ids = [obj.id for obj in objs if obj.is_used == True]

        email_counts = Email.objects.filter(
            person=objs[0].person_id, is_used=True
        ).count()

        if email_counts == 1: # For "add" and "change" pages
            return
        
        if email_counts == 0: # For "add" and "change" pages
            id = Email.objects.filter(person=objs[0].person_id).first().id
            obj = Email.objects.get(person=objs[0].person_id, id=id)
            obj.is_used = True
            obj.save()
            return

        if email_counts > 1 and not change: # For "add" page
            Email.objects.filter(
                person=objs[0].person_id
            ).exclude(id=true_ids[0]).update(is_used=False)
            return

        if email_counts > 1 and change: # For "change" page
            id = Email.objects.filter(
                person=objs[0].person_id,
                is_used=True
            ).first().id
            Email.objects.filter(
                person=objs[0].person_id, 
            ).exclude(
                id=id if id < true_ids[0] else true_ids[0]
            ).update(is_used=False)
            return

例如,add页面默认只有第一个inline对象是is_used=True,如下图所示:

并且,如果尝试添加或更改所有is_used=False的内联对象,如下所示:

然后,第一个内联对象是is_used=True,如下所示:

并且,如果尝试添加或更改多个is_used=True内联对象,如下所示:

然后,只有第一个内联对象is_used=True被接受为is_used=True,如下所示:

并且,如果尝试删除内联对象is_used=True,如下所示:

然后,第一个内联对象是is_used=True,如下所示:

并且,如果尝试删除所有内联对象,包括is_used=True对象,如下所示:

然后,第一个内联对象是未删除的is_used=True,如下所示。* 必须至少有一个内联对象:

相关问题