models.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. # -*- coding: utf-8 -*-
  2. """
  3. @author: Allen
  4. @Created on: 2023/10/18
  5. @Remark: 公共基础model类
  6. """
  7. import uuid
  8. from datetime import date, timedelta
  9. from django.apps import apps
  10. from django.db import models, transaction, connection, ProgrammingError
  11. from django.core.exceptions import ObjectDoesNotExist
  12. from application import settings
  13. from application.dispatch import is_tenants_mode
  14. table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
  15. class SoftDeleteQuerySet(models.query.QuerySet):
  16. @transaction.atomic
  17. def delete(self, cascade=True):
  18. if cascade: # delete one by one if cascade
  19. for obj in self.all():
  20. obj.delete(cascade=cascade)
  21. return self.update(is_deleted=True)
  22. def hard_delete(self):
  23. return super().delete()
  24. class SoftDeleteManager(models.Manager):
  25. """支持软删除"""
  26. def __init__(self, *args, **kwargs):
  27. self.__add_is_del_filter = False
  28. super(SoftDeleteManager, self).__init__(*args, **kwargs)
  29. def filter(self, *args, **kwargs):
  30. # 考虑是否主动传入is_deleted
  31. if not kwargs.get("is_deleted") is None:
  32. self.__add_is_del_filter = kwargs.get("is_deleted")
  33. return super(SoftDeleteManager, self).filter(*args, **kwargs)
  34. def get_queryset(self):
  35. if self.__add_is_del_filter:
  36. return SoftDeleteQuerySet(self.model, using=self._db).exclude(is_deleted=False)
  37. return SoftDeleteQuerySet(self.model).exclude(is_deleted=True)
  38. def get_by_natural_key(self, name):
  39. return SoftDeleteQuerySet(self.model).get(username=name)
  40. def get_month_range(start_day, end_day):
  41. months = (end_day.year - start_day.year) * 12 + end_day.month - start_day.month
  42. month_range = [
  43. "%s-%s-01" % (start_day.year + mon // 12, str(mon % 12 + 1).zfill(2))
  44. for mon in range(start_day.month - 1, start_day.month + months)
  45. ]
  46. return month_range
  47. class SoftDeleteModel(models.Model):
  48. """
  49. 软删除模型
  50. 一旦继承,就将开启软删除
  51. """
  52. is_deleted = models.BooleanField(verbose_name="是否软删除", help_text="是否软删除", default=False, db_index=True)
  53. objects = SoftDeleteManager()
  54. class Meta:
  55. abstract = True
  56. verbose_name = "软删除模型"
  57. verbose_name_plural = verbose_name
  58. @transaction.atomic
  59. def delete(self, using=None, cascade=True, *args, **kwargs):
  60. """
  61. 重写删除方法,直接开启软删除
  62. """
  63. self.is_deleted = True
  64. self.save(using=using)
  65. if cascade:
  66. self.delete_related_objects(raise_exception=True)
  67. # raise Exception("delete_related_objects")
  68. def hard_delete(self):
  69. return super().delete()
  70. soft_delete_kwargs = {
  71. "related_names": [],
  72. }
  73. @classmethod
  74. def _get_kwargs(cls):
  75. return cls.soft_delete_kwargs
  76. @classmethod
  77. def _get_relations(cls):
  78. relations = {"foreign": [], "self": []}
  79. related_fields = cls._get_kwargs().get("related_names", [])
  80. if not related_fields:
  81. fields = cls._meta.get_fields(include_hidden=True)
  82. mutated_fields = [field for field in fields if field.is_relation and hasattr(field, "related_name")]
  83. m2m_models = [field.through for field in mutated_fields if field.many_to_many]
  84. related_fields = [
  85. field.related_name
  86. for field in mutated_fields
  87. if not field.many_to_many and field.related_model not in m2m_models and field.related_name
  88. ]
  89. tree_model_field = [
  90. field.field.name
  91. for field in mutated_fields
  92. if not field.many_to_many and field.related_model is field.model
  93. ]
  94. relations["self"] = f"{tree_model_field[0]}_id" if len(tree_model_field) == 1 else None
  95. relations["foreign"] = related_fields
  96. return relations
  97. def _is_cascade(self, relation):
  98. on_delete_case = self._meta.get_field(relation).on_delete.__name__
  99. return on_delete_case == "CASCADE"
  100. def _get_related_objects(self, relation):
  101. qs = getattr(self, relation)
  102. if isinstance(qs, models.Manager):
  103. return qs
  104. return
  105. def related_objects(self, raise_exception=False, use_soft_manager=False):
  106. relations = self._get_relations()
  107. objects = {}
  108. for relation in relations["foreign"]:
  109. try:
  110. qs = self._get_related_objects(relation)
  111. except ObjectDoesNotExist as e:
  112. if raise_exception:
  113. raise e
  114. continue
  115. else:
  116. objects[relation] = qs
  117. if relations["self"]:
  118. objects["self"] = self.__class__.objects.filter(**{relations["self"]: self.id})
  119. print(f"related_objects: {objects}", flush=True)
  120. return objects
  121. def delete_related_objects(self, raise_exception=False):
  122. for relation, qs in self.related_objects(raise_exception=raise_exception).items():
  123. if relation == "self":
  124. qs.delete()
  125. continue
  126. if self._is_cascade(relation):
  127. print(f"model {self.__class__} : cascade delete {relation} objects {qs.all()}", flush=True)
  128. qs.all().delete()
  129. else:
  130. print(f"model {self.__class__} : protect delete {relation} objects {qs.all()}", flush=True)
  131. if qs.all().exists():
  132. self.hard_delete()
  133. qs.all().hard_delete()
  134. # raise Exception("xxxxxxxxxxx for test xxxxxxxxxxx")
  135. class CoreModel(models.Model):
  136. """
  137. 核心标准抽象模型模型,可直接继承使用
  138. 增加审计字段, 覆盖字段时, 字段名称请勿修改, 必须统一审计字段名称
  139. """
  140. id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
  141. description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
  142. creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
  143. verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL,
  144. db_constraint=False)
  145. modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
  146. dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True,
  147. verbose_name="数据归属部门")
  148. update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间",
  149. verbose_name="修改时间")
  150. create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
  151. verbose_name="创建时间")
  152. class Meta:
  153. abstract = True
  154. verbose_name = '核心模型'
  155. verbose_name_plural = verbose_name
  156. class AddPostgresPartitionedBase:
  157. """
  158. pgsql表分表基类
  159. """
  160. @classmethod
  161. def add_hash_partition(cls, number=36):
  162. """
  163. 创建分区表
  164. :return:
  165. """
  166. if cls.PartitioningMeta.method != 'hash':
  167. raise ProgrammingError("表分区错误,无法进行分区")
  168. schema_editor = connection.schema_editor()
  169. if is_tenants_mode():
  170. schema_editor.sql_add_hash_partition = f'CREATE TABLE "{connection.tenant.schema_name}".%s PARTITION OF "{connection.tenant.schema_name}".%s FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
  171. for item in range(number):
  172. try:
  173. schema_editor.add_hash_partition(
  174. model=cls,
  175. name="_" + str(item),
  176. modulus=number,
  177. remainder=item,
  178. )
  179. except ProgrammingError as e:
  180. print(f"{cls.__name__}分表失败:" + str(e).rstrip('\n'))
  181. return
  182. @classmethod
  183. def add_range_day_partition(cls, day=7):
  184. """
  185. 按照创建时间"天"分表
  186. :return:
  187. """
  188. if cls.PartitioningMeta.method != 'range':
  189. raise ProgrammingError("表分区错误,无法进行分区")
  190. day_before = date.today().strftime("%Y-%m-%d")
  191. schema_editor = connection.schema_editor()
  192. if is_tenants_mode():
  193. schema_editor.sql_add_range_partition = (
  194. f'CREATE TABLE "{connection.tenant.schema_name}".%s PARTITION OF "{connection.tenant.schema_name}".%s FOR VALUES FROM (%s) TO (%s)'
  195. )
  196. for index in range(day):
  197. try:
  198. day_following = (date.today() + timedelta(days=index + 1)).strftime("%Y-%m-%d")
  199. schema_editor.add_range_partition(
  200. model=cls,
  201. name=f"{day_before}_{day_following}",
  202. from_values=day_before,
  203. to_values=day_following,
  204. )
  205. day_before = day_following
  206. except ProgrammingError as e:
  207. print(f"{cls.__name__}分表失败:" + str(e).rstrip('\n'))
  208. return
  209. @classmethod
  210. def add_range_month_partition(cls, start_date, end_date):
  211. """
  212. 按照创建时间"月"分表
  213. :return:
  214. """
  215. if cls.PartitioningMeta.method != 'range':
  216. raise ProgrammingError("表分区错误,无法进行分区")
  217. range_month_partition_list = get_month_range(start_date, end_date)
  218. schema_editor = connection.schema_editor()
  219. if is_tenants_mode():
  220. schema_editor.sql_add_range_partition = (
  221. f'CREATE TABLE "{connection.tenant.schema_name}".%s PARTITION OF "{connection.tenant.schema_name}".%s FOR VALUES FROM (%s) TO (%s)'
  222. )
  223. for index, ele in enumerate(range_month_partition_list):
  224. if index == 0:
  225. continue
  226. try:
  227. schema_editor.add_range_partition(
  228. model=cls,
  229. name=f"{range_month_partition_list[index - 1][:-3]}_{ele[:-3]}",
  230. from_values=range_month_partition_list[index - 1],
  231. to_values=ele,
  232. )
  233. except ProgrammingError as e:
  234. print(f"{cls.__name__}分表失败:" + str(e).rstrip('\n'))
  235. return
  236. @classmethod
  237. def add_list_partition(cls, unique_value):
  238. """
  239. 按照某个值进行分区
  240. :param unique_value:
  241. :return:
  242. """
  243. if cls.PartitioningMeta.method != 'list':
  244. raise ProgrammingError("表分区错误,无法进行分区")
  245. schema_editor = connection.schema_editor()
  246. if is_tenants_mode():
  247. schema_editor.sql_add_list_partition = (
  248. f'CREATE TABLE "{connection.tenant.schema_name}".%s PARTITION OF "{connection.tenant.schema_name}".%s FOR VALUES IN (%s)'
  249. )
  250. try:
  251. schema_editor.add_list_partition(
  252. model=cls,
  253. name=f"_{unique_value}",
  254. values=[unique_value],
  255. )
  256. except ProgrammingError as e:
  257. print(f"{cls.__name__}分表失败:" + str(e).rstrip('\n'))
  258. return
  259. def get_all_models_objects(model_name=None):
  260. """
  261. 获取所有 models 对象
  262. :return: {}
  263. """
  264. settings.ALL_MODELS_OBJECTS = {}
  265. if not settings.ALL_MODELS_OBJECTS:
  266. all_models = apps.get_models()
  267. for item in list(all_models):
  268. table = {
  269. "tableName": item._meta.verbose_name,
  270. "table": item.__name__,
  271. "tableFields": []
  272. }
  273. for field in item._meta.fields:
  274. fields = {
  275. "title": field.verbose_name,
  276. "field": field.name
  277. }
  278. table['tableFields'].append(fields)
  279. settings.ALL_MODELS_OBJECTS.setdefault(item.__name__, {"table": table, "object": item})
  280. if model_name:
  281. return settings.ALL_MODELS_OBJECTS[model_name] or {}
  282. return settings.ALL_MODELS_OBJECTS or {}