Django,级联移动到单独的表,而不是级联删除

编程入门 行业动态 更新时间:2024-10-28 12:17:08
本文介绍了Django,级联移动到单独的表,而不是级联删除的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

当我们删除

而不是软时,我想保留数据 - 删除(使用is_deleted字段),我想将数据移动到另一个表(删除的行)

instead of soft-delete (which uses is_deleted field), I'd like to move the data to another table (for deleted rows)

stackoverflow/a/26125927/433570

我不知道战略的名称是什么。叫归档?两张表删除?

I don't know what is the name of the strategy either. called archiving? two-table delete?

为了使这项工作,

我需要能够做

I need to be able to do

  • ,找到具有外键或一对一键的所有其他对象目的。 (这可以通过 stackoverflow/a/2315053/433570 完成,实际上比这更难代码不够)

  • for a given object(which will be deleted), find all other objects that has foreign key or one-to-one key to the object. (this can be done via stackoverflow/a/2315053/433570, actually harder than that, that code isn't sufficient)

    插入一个新对象,并将#1中找到的所有对象指向这个新对象

    insert a new object and have all the objects found in #1 to point to this new object

    删除对象

    (essentiall我正在进行级联移动级联删除,1〜3步应该以递归的方式进行)

    (essentiall I'm doing cascading move instead of cascading delete, 1~3 step should be done in recursive fashion)

    最为方便的是为此支持 delete ()和 undelete()为一个对象和一个查询器。

    It would be most convenient to make a mixin for this that supports delete() and undelete() for a object and for a queryset.

    任何人创建了这样的一个?

    Has anyone created one like this?

    推荐答案

    我自己实现了这一点,我正在分享我的发现。

    I implemented this myself and I'm sharing my findings.

    首先归档很简单,因为我放宽了归档表上的外键限制。

    First archiving is rather easy since I relaxed foreignkey constraints on archive tables.

    你不能在arch中保留所有约束ive世界就像在现实世界中一样,因为你所要删除的对象所引用的内容不会在档案世界中。 (因为它不会被删除)

    You can't keep all constraints in the archive world as you would have in the live world, because what your to-be-deleted object is refering to won't be in the archive world. (because it won't be deleted)

    这可以通过mixin(系统地)完成

    This can be done via mixin (systematically)

    您将使用级联创建归档对象,然后删除原始文件。

    Basically, you create archive objects with cascade then delete the original.

    另一方面,取消归档更难,因为您需要确认外键约束。 这不能系统地完成。

    On the other hand, unarchiving is harder, because you need to confirm to the foreign key constraints. This can't be done systematically.

    Django休息框架之类的序列化程序不会神奇地创建相关对象,这也是一样的原因。你必须知道对象图和约束。

    It's the same reason why serializers such as Django rest framework won't create related objects magically. You have to know the object graph and constraints.

    所以这就是为什么没有图书馆或者混合在这里来支持这个。

    So that's why there's no library or mixin out there to support this.

    无论如何,我在下面分享我的mixin代码。

    Anyway, I'm sharing my mixin code below.

    class DeleteModelQuerySet(object): ''' take a look at django.db.models.deletion ''' def hard_delete(self): super().delete() def delete(self): if not self.is_archivable(): super().delete() return archive_object_ids = [] seen = [] collector = NestedObjects(using='default') # or specific database collector.collect(list(self)) collector.sort() with transaction.atomic(): for model, instances in six.iteritems(collector.data): if model in self.model.exclude_models_from_archive(): continue assert hasattr(model, "is_archivable"), { "model {} doesn't know about archive".format(model) } if not model.is_archivable(): # just delete continue for instance in instances: if instance in seen: continue seen.append(instance) for ptr in six.itervalues(instance._meta.parents): # add parents to seen if ptr: seen.append(getattr(instance, ptr.name)) archive_object = model.create_archive_object(instance) archive_object_ids.append(archive_object.id) # real delete super().delete() archive_objects = self.model.get_archive_model().objects.filter(id__in=archive_object_ids) return archive_objects def undelete(self): with transaction.atomic(): self.unarchive() super().delete() def is_archivable(self): # if false, we hard delete instead of archive return self.model.is_archivable() def unarchive(self): for obj_archive in self: self.model.create_live_object(obj_archive) class DeleteModelMixin(models.Model): @classmethod def is_archivable(cls): # override if you don't want to archive and just delete return True def get_deletable_objects(self): collector = NestedObjects(using='default') # or specific database collector.collect(list(self)) collector.sort() deletable_data = collector.data return deletable_data @classmethod def create_archive_object(cls, obj): # stackoverflow/q/21925671/433570 # d = cls.objects.filter(id=obj.id).values()[0] d = obj.__dict__.copy() remove_fields = [] for field_name, value in six.iteritems(d): try: obj._meta.get_field(field_name) except FieldDoesNotExist: remove_fields.append(field_name) for remove_field in remove_fields: d.pop(remove_field) cls.convert_to_archive_dictionary(d) # print(d) archive_object = cls.get_archive_model().objects.create(**d) return archive_object @classmethod def create_live_object(cls, obj): # index error, dont know why.. # d = cls.objects.filter(id=obj.id).values()[0] d = obj.__dict__.copy() remove_fields = [cls.convert_to_archive_field_name(field_name) + '_id' for field_name in cls.get_twostep_field_names()] for field_name, value in six.iteritems(d): try: obj._meta.get_field(field_name) except FieldDoesNotExist: remove_fields.append(field_name) for remove_field in remove_fields: d.pop(remove_field) cls.convert_to_live_dictionary(d) live_object = cls.get_live_model().objects.create(**d) return live_object @classmethod def get_archive_model_name(cls): return '{}Archive'.format(cls._meta.model_name) @classmethod def get_live_model_name(cls): if cls._meta.model_name.endswith("archive"): length = len("Archive") return cls._meta.model_name[:-length] return cls._meta.model_name @classmethod def get_archive_model(cls): # stackoverflow/a/26126935/433570 return apps.get_model(app_label=cls._meta.app_label, model_name=cls.get_archive_model_name()) @classmethod def get_live_model(cls): return apps.get_model(app_label=cls._meta.app_label, model_name=cls.get_live_model_name()) @classmethod def is_archive_model(cls): if cls._meta.model_name.endswith("Archive"): return True return False @classmethod def is_live_model(cls): if cls.is_archive_model(): return False return True def make_referers_point_to_archive(self, archive_object, seen): instance = self for related in get_candidate_relations_to_delete(instance._meta): accessor_name = related.get_accessor_name() if accessor_name.endswith('+') or accessor_name.lower().endswith("archive"): continue referers = None if related.one_to_one: referer = getattr(instance, accessor_name, None) if referer: referers = type(referer).objects.filter(id=referer.id) else: referers = getattr(instance, accessor_name).all() refering_field_name = '{}_archive'.format(related.field.name) if referers: assert hasattr(referers, 'is_archivable'), { "referers is not archivable: {referer_cls}".format( referer_cls=referers.model ) } archive_referers = referers.delete(seen=seen) if referers.is_archivable(): archive_referers.update(**{refering_field_name: archive_object}) def hard_delete(self): super().delete() def delete(self, *args, **kwargs): self._meta.model.objects.filter(id=self.id).delete() def undelete(self, commit=True): self._meta.model.objects.filter(id=self.id).undelete() def unarchive(self, commit=True): self._meta.model.objects.filter(id=self.id).unarchive() @classmethod def get_archive_field_names(cls): raise NotImplementedError('get_archive_field_names() must be implemented') @classmethod def convert_to_archive_dictionary(cls, d): field_names = cls.get_archive_field_names() for field_name in field_names: field_name = '{}_id'.format(field_name) archive_field_name = cls.convert_to_archive_field_name(field_name) d[archive_field_name] = d.pop(field_name) @classmethod def convert_to_live_dictionary(cls, d): field_names = list(set(cls.get_archive_field_names()) - set(cls.get_twostep_field_names())) for field_name in field_names: field_name = '{}_id'.format(field_name) archive_field_name = cls.convert_to_archive_field_name(field_name) d[field_name] = d.pop(archive_field_name) @classmethod def convert_to_archive_field_name(cls, field_name): if field_name.endswith('_id'): length = len('_id') return '{}_archive_id'.format(field_name[:-length]) return '{}_archive'.format(field_name) @classmethod def convert_to_live_field_name(cls, field_name): if field_name.endswith('_archive_id'): length = len('_archive_id') return '{}_id'.format(field_name[:-length]) if field_name.endswith('archive'): length = len('_archive') return '{}'.format(field_name[:-length]) return None @classmethod def get_twostep_field_names(cls): return [] @classmethod def exclude_models_from_archive(cls): # excluded model can be deleted if referencing to me # or just lives if I reference him return [] class Meta: abstract = True
  • 更多推荐

    Django,级联移动到单独的表,而不是级联删除

    本文发布于:2023-10-16 05:08:47,感谢您对本站的认可!
    本文链接:https://www.elefans.com/category/jswz/34/1496590.html
    版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
    本文标签:级联   而不是   Django

    发布评论

    评论列表 (有 0 条评论)
    草根站长

    >www.elefans.com

    编程频道|电子爱好者 - 技术资讯及电子产品介绍!