Postgres在models.py中不遵循Django的“ on_delete = models.CASCADE”。为什么?

时间:2020-07-18 22:08:10

标签: django postgresql sqlite django-models pgloader

我刚刚从SQLite3迁移到了Postgres 12(使用pgloader)。

我无法删除某些模型对象,因为还有其他引用它的模型对象。

但是,我非常确定我的模型对象在引用模型对象中设置了“ on_delete = models.CASCADE”(在我的Django代码的models.py中)。

所以我得到了Postgres生成的错误:

...
django.db.utils.IntegrityError: update or delete on table "app_reply" violates foreign key constraint "app_replyimage_reply_id_fkey" on table "app_replyimage"
DETAIL:  Key (id)=(SRB6Nf98) is still referenced from table "app_replyimage".

有没有一种方法(希望不用手动在Postgres中编辑表模式)来解决这个问题?

编辑:在下面添加一些代码...

models.py

class ReplyImage(models.Model):
    id = models.CharField(default = make_id(), unique = True, primary_key = True, max_length = 8)
    
    file = models.ImageField(upload_to = customer_images_directory_path)
    
    reply = models.ForeignKey(Reply, on_delete = models.CASCADE, related_name = 'reply_images')
    
    #Meta
    created = models.DateTimeField(auto_now_add = True)
    updated = models.DateTimeField(auto_now = True)
    created_by = models.ForeignKey(User, on_delete = models.CASCADE, related_name = '+', null = True)
    updated_by = models.ForeignKey(User, on_delete = models.CASCADE, related_name = '+', null = True)
    
    def __str__(self):
        return str(self.id)
    
    def delete(self, *args, **kwargs):
        self.file.delete()
        super(ReplyImage, self).delete(*args, **kwargs)

@receiver(post_delete, sender = ReplyImage)
def reply_image_delete(sender, instance, **kwargs):
    instance.file.delete()

这是引用的模型,回复:

class Reply(models.Model):
    id = models.CharField(default = make_id(), unique = True, primary_key = True, max_length = 8)
    
    #Content
    content = models.CharField(max_length = 560, blank = True, null = True)
    replies = GenericRelation('self')
    link = models.ForeignKey(Link, on_delete = models.SET_NULL, related_name = 'reply', blank = True, null = True)
    
    #Drip
    drip_interval_quantity = models.IntegerField(default = 0, blank = True, null = True) #Custom quantity
    drip_interval_calendar_unit = models.CharField(default = 'minute', max_length = 6, choices = DRIP_INTERVAL_CALENDAR_UNIT_CHOICES, blank = True, null = True) #Custom calendar unit
    
    post_datetime = models.DateTimeField(blank = True, null = True) #Post datetime
    
    #Twitter API
    self_status_id_str = models.CharField(max_length = 19, null = True, blank = True) #status_id_str of self
    in_reply_to_status_id_str = models.CharField(max_length = 19, null = True, blank = True) #status_id_str of tweet replied to
    
    #Meta
    created = models.DateTimeField(auto_now_add = True)
    updated = models.DateTimeField(auto_now = True)
    created_by = models.ForeignKey(User, on_delete = models.CASCADE, related_name = '+', null = True)
    updated_by = models.ForeignKey(User, on_delete = models.CASCADE, related_name = '+', null = True)
    
    #Mandatory fields for generic relation
    content_type = models.ForeignKey(ContentType, on_delete = models.CASCADE)
    object_id = models.CharField(default = make_id(), unique = True, max_length = 8)
    content_object = GenericForeignKey()
    
    customer = models.ForeignKey(Customer, on_delete = models.CASCADE, related_name = 'postreplies')
    
    def delete(self, *args, **kwargs):
        if self.reply_images.all():
            for i in self.reply_images.all():
                i.delete()
        
        if self.link:
            self.link.delete()
        
        super(Reply, self).delete(*args, **kwargs)
    
    def __str__(self):
        return self.content

@receiver(post_save, sender = Reply)
def reply_model_save(sender, instance, **kwargs):
    if kwargs['raw']:
        return
    
    recursive_reply_save_mock_post(instance)

@receiver(post_delete, sender = Reply)
def reply_model_delete(sender, instance, **kwargs):
    if instance.reply_images.all():
        for i in instance.reply_images.all():
            i.delete()

views.py

...
post.replies.all().delete()
...

(我重新定义了delete()方法,还添加了post_delete()信号,但我认为它们不应该影响任何东西,因为它与SQLite完美配合。)

出了什么问题?

编辑2:

Postgres表架构

                       Table "public.app_replyimage"
    Column     |           Type           | Collation | Nullable | Default
---------------+--------------------------+-----------+----------+---------
 file          | text                     |           |          |
 created       | timestamp with time zone |           |          |
 updated       | timestamp with time zone |           |          |
 created_by_id | bigint                   |           |          |
 reply_id      | text                     |           |          |
 updated_by_id | bigint                   |           |          |
 id            | text                     |           | not null |
Indexes:
    "idx_85696_sqlite_autoindex_app_replyimage_1" PRIMARY KEY, btree (id)
    "idx_85696_app_replyimage_created_by_id_a5974d1f" btree (created_by_id)
    "idx_85696_app_replyimage_reply_id_7a8aff2c" btree (reply_id)
    "idx_85696_app_replyimage_updated_by_id_d73f7446" btree (updated_by_id)
Foreign-key constraints:
    "app_replyimage_created_by_id_fkey" FOREIGN KEY (created_by_id) REFERENCES auth_user(id)
    "app_replyimage_reply_id_fkey" FOREIGN KEY (reply_id) REFERENCES app_reply(id)
    "app_replyimage_updated_by_id_fkey" FOREIGN KEY (updated_by_id) REFERENCES auth_user(id)

1 个答案:

答案 0 :(得分:0)

简而言之:Postgres不会处理ON DELETE触发器。 Django本身进行级联处理。

实际上,models.CASCADE是可调用的可调用,它收集需要删除的其他元素。例如,我们可以看看source code [GitHub]

def CASCADE(collector, field, sub_objs, using):
    collector.collect(
        sub_objs, source=field.remote_field.model, source_attr=field.name,
        nullable=field.null, fail_on_restricted=False,
    )
    if field.null and not connections[using].features.can_defer_constraint_checks:
        collector.add_field_update(field, None, sub_objs)

因此collector是一个收集需要删除的对象的集合。

因此,当您删除对象时,Django会确定需要删除,更新的其他对象,然后以正确的顺序进行查询。它不使用数据库中的任何触发器。

实际上,您可以通过实现具有相同参数并运行不同逻辑的可调用对象来实现自己的删除策略,尽管我建议保持简单性。

如果要让Django执行相同的操作,则应更改字段,使其看起来像这样:

ALTER TABLE app_replyimage ALTER COLUMN reply_id
    integer REFERENCES orders ON DELETE CASCADE;