Django中使用 Closure Table 储存无限分级数据

Django中使用 Closure Table 储存无限分级数据

起步

上一篇讨论了如何用数据库存储无限分级的数据。对于数据量大的情况(比如用户之间有邀请链,有点三级分销的意思),就要用到 closure table 的结构来进行存储。那么在 Django 中如何处理这个结构的模型呢?

定义模型

至少是要两个模型的,一个是存储分类,一个储存分类之间的关系:

class Category(models.Model):
  name = models.CharField(max_length=31)
  def __str__(self):
    return self.name
class CategoryRelation(models.Model):
  ancestor = models.ForeignKey(Category, null=True, related_name='ancestors', on_delete=models.SET_NULL, db_constraint=False, verbose_name='祖先')
  descendant = models.ForeignKey(Category,null=True, related_name='descendants', on_delete=models.SET_NULL,
                  db_constraint=False, verbose_name='子孙')
  distance = models.IntegerField()
  class Meta:
    unique_together = ("ancestor", "descendant")

数据操作

获得所有后代节点

class Category(models.Model):
  ...
  def get_descendants(self, include_self=False):
    """获得所有后代节点"""
    kw = {
      'descendants__ancestor' : self
    }
    if not include_self:
      kw['descendants__distance__gt'] = 0
    qs = Category.objects.filter(**kw).order_by('descendants__distance')
    return qs获得直属下级
class Category(models.Model):
  ...
  def get_children(self):
    """获得直属下级"""
    qs = Category.objects.filter(descendants__ancestor=self, descendants__distance=1)
    return qs

节点的移动

节点的移动是比较难的,在 [ https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/][1 ] 中讲述了,利用django能够执行原生的sql语句进行:

def add_child(self, child):
    """将某个分类加入本分类,"""
    if CategoryRelation.objects.filter(ancestor=child, descendant=self).exists() \
        or CategoryRelation.objects.filter(ancestor=self, descendant=child, distance=1).exists():
      """child不能是self的祖先节点 or 它们已经是父子节点"""
      return
    # 如果表中不存在节点自身数据
    if not CategoryRelation.objects.filter(ancestor=child, descendant=child).exists():
      CategoryRelation.objects.create(ancestor=child, descendant=child, distance=0)
    table_name = CategoryRelation._meta.db_table
    cursor = connection.cursor()
    cursor.execute(f"""
      DELETE a
      FROM
        {table_name} AS a
      JOIN {table_name} AS d ON a.descendant_id = d.descendant_id
      LEFT JOIN {table_name} AS x ON x.ancestor_id = d.ancestor_id
      AND x.descendant_id = a.ancestor_id
      WHERE
        d.ancestor_id = {child.id}
      AND x.ancestor_id IS NULL;
    """)
    cursor.execute(f"""
    INSERT INTO {table_name} (ancestor_id, descendant_id, distance)
    SELECT supertree.ancestor_id, subtree.descendant_id,
    supertree.distance+subtree.distance+1
    FROM {table_name} AS supertree JOIN {table_name} AS subtree
    WHERE subtree.ancestor_id = {child.id}
    AND supertree.descendant_id = {self.id};
    """)

 节点删除

节点删除有两种操作,一个是将所有子节点也删除,另一个是将自己点移到上级节点中。

扩展阅读

[ https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/][2 ]
[ http://technobytz.com/closure_table_store_hierarchical_data.html][3 ]

完整代码

class Category(models.Model):
name = models.CharField(max_length=31)
def __str__(self):
  return self.name
def get_descendants(self, include_self=False):
  """获得所有后代节点"""
  kw = {
    'descendants__ancestor' : self
  }
  if not include_self:
    kw['descendants__distance__gt'] = 0
  qs = Category.objects.filter(**kw).order_by('descendants__distance')
  return qs
def get_children(self):
  """获得直属下级"""
  qs = Category.objects.filter(descendants__ancestor=self, descendants__distance=1)
  return qs
def get_ancestors(self, include_self=False):
  """获得所有祖先节点"""
  kw = {
    'ancestors__descendant': self
  }
  if not include_self:
    kw['ancestors__distance__gt'] = 0
  qs = Category.objects.filter(**kw).order_by('ancestors__distance')
  return qs
def get_parent(self):
  """分类仅有一个父节点"""
  parent = Category.objects.get(ancestors__descendant=self, ancestors__distance=1)
  return parent
def get_parents(self):
  """分类仅有一个父节点"""
  qs = Category.objects.filter(ancestors__descendant=self, ancestors__distance=1)
  return qs
def remove(self, delete_subtree=False):
  """删除节点"""
  if delete_subtree:
    # 删除所有子节点
    children_queryset = self.get_descendants(include_self=True)
    for child in children_queryset:
      CategoryRelation.objects.filter(Q(ancestor=child) | Q(descendant=child)).delete()
      child.delete()
  else:
    # 所有子节点移到上级
    parent = self.get_parent()
    children = self.get_children()
    for child in children:
      parent.add_chile(child)
    # CategoryRelation.objects.filter(descendant=self, distance=0).delete()
    CategoryRelation.objects.filter(Q(ancestor=self) | Q(descendant=self)).delete()
    self.delete()
def add_child(self, child):
  """将某个分类加入本分类,"""
  if CategoryRelation.objects.filter(ancestor=child, descendant=self).exists() \
      or CategoryRelation.objects.filter(ancestor=self, descendant=child, distance=1).exists():
    """child不能是self的祖先节点 or 它们已经是父子节点"""
    return
  # 如果表中不存在节点自身数据
  if not CategoryRelation.objects.filter(ancestor=child, descendant=child).exists():
    CategoryRelation.objects.create(ancestor=child, descendant=child, distance=0)
  table_name = CategoryRelation._meta.db_table
  cursor = connection.cursor()
  cursor.execute(f"""
    DELETE a
    FROM
      {table_name} AS a
    JOIN {table_name} AS d ON a.descendant_id = d.descendant_id
    LEFT JOIN {table_name} AS x ON x.ancestor_id = d.ancestor_id
    AND x.descendant_id = a.ancestor_id
    WHERE
      d.ancestor_id = {child.id}
    AND x.ancestor_id IS NULL;
  """)
  cursor.execute(f"""
  INSERT INTO {table_name} (ancestor_id, descendant_id, distance)
  SELECT supertree.ancestor_id, subtree.descendant_id,
  supertree.distance+subtree.distance+1
  FROM {table_name} AS supertree JOIN {table_name} AS subtree
  WHERE subtree.ancestor_id = {child.id}
  AND supertree.descendant_id = {self.id};
  """)class CategoryRelation(models.Model): ancestor = models.ForeignKey(Category, null=True, related_name='ancestors', on_delete=models.SET_NULL, db_constraint=False, verbose_name='祖先') descendant = models.ForeignKey(Category,null=True, related_name='descendants', on_delete=models.SET_NULL, db_constraint=False, verbose_name='子孙') distance = models.IntegerField()
class Meta:
  unique_together = ("ancestor", "descendant")[1]: https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/
 [2]: https://www.percona.com/blog/2011/02/14/moving-subtrees-in-closure-table/
 [3]: http://technobytz.com/closure_table_store_hierarchical_data.html

总结

以上所述是小编给大家介绍的Django中使用 Closure Table 储存无限分级数据,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Django中使用 Closure Table 储存无限分级数据

    Django中使用 Closure Table 储存无限分级数据 起步 上一篇讨论了如何用数据库存储无限分级的数据.对于数据量大的情况(比如用户之间有邀请链,有点三级分销的意思),就要用到 closure table 的结构来进行存储.那么在 Django 中如何处理这个结构的模型呢? 定义模型 至少是要两个模型的,一个是存储分类,一个储存分类之间的关系: class Category(models.Model): name = models.CharField(max_length=31) d

  • PHP数组无限分级数据的层级化处理代码

    复制代码 代码如下: /** * 创建父节点树形数组 * 参数 * $ar 数组,邻接列表方式组织的数据 * $id 数组中作为主键的下标或关联键名 * $pid 数组中作为父键的下标或关联键名 * 返回 多维数组 **/function find_parent($ar, $id='id', $pid='pid') {   foreach($ar as $v) $t[$v[$id]] = $v;  foreach ($t as $k => $item){    if( $item[$pid] )

  • 无限分级和tree结构数据增删改【附DEMO下载】

    阅读目录 •无限分级 •jstree插件 •Demo •创建Region实体 •满足jstree插件的数据对象Dto •数据转换 •初始化获取转换后的数据 •前台数据加载 •其他操作 •通过按钮来操作增删改 无限分级 很多时候我们不确定等级关系的层级,这个时候就需要用到无限分级了. 说到无限分级,又要扯到递归调用了.(据说频繁递归是很耗性能的),在此我们需要先设计好表机构,用来存储无限分级的数据.当然,以下都是自己捣鼓的结果,非标准.谁有更好的设计望不吝啬赐教. 说来其实也简单,就是一个ID和父

  • jstree创建无限分级树的方法【基于ajax动态创建子节点】

    本文实例讲述了jstree创建无限分级树的方法.分享给大家供大家参考,具体如下: 首先来看一下效果 页面加载之初 节点全部展开后 首先数据库的表结构如下 其中Id为主键,PId为关联到自身的外键 两个字段均为GUID形式 层级关系主要靠这两个字段维护 其次需要有一个类型 public class MenuType { public Guid Id { get; set; } public Guid PId { get; set; } public string MenuName { get; s

  • Django中ORM表的创建和增删改查方法示例

    前言 Django作为重量级的Python web框架,在做项目时肯定少不了与数据库打交道,编程人员对数据库的语法简单的还行,但过多的数据库语句不是编程人员的重点对象.因此用ORM来操作数据库相当快捷.今天来介绍一下用ORM操作数据库. 一.创建Django项目 可以使用pycharme专业版直接快速创建.如果不是专业版也可以使用命令进行创建.下面列出命令行创建方式: django-admin startproject orm_test 这时会在当前目录创建文件夹名为orm_test,接下来进入

  • 解决Django中多条件查询的问题

    tags: django中对条件查询 一些cms项目都会使用到多条件查询,我们后端如何处理请求的条件呢? 满足一个条件 满足两个条件 满足多个条件 -------. 这样处理起来会非常的恼火. 其实有多方法比如(传参数,传字典,传Q对象,传F对象-)陷入深深的思考中-怎么用做简单的方法把这个需求解决了. 个人觉得.把我们的查询的所有条件来构建一个字典来查询起来比较高效.具体如何操作见下面的代码: 视图函数. def order_list(request): if request.method =

  • 在TP5数据库中四个字段实现无限分类的示例

    效果: CREATE TABLE `NewTable` ( `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT , `pid` int(10) UNSIGNED ZEROFILL NOT NULL , `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , `path` varchar(200) CHARACTER SET utf8 COLLATE utf8_gene

  • Django中使用Celery的教程详解

    Django教程 Python下有许多款不同的 Web 框架.Django是重量级选手中最有代表性的一位.许多成功的网站和APP都基于Django. Django是一个开放源代码的Web应用框架,由Python写成. Django遵守BSD版权,初次发布于2005年7月, 并于2008年9月发布了第一个正式版本1.0 . Django采用了MVC的软件设计模式,即模型M,视图V和控制器C. 一.前言 Celery是一个基于python开发的分布式任务队列,如果不了解请阅读笔者上一篇博文Celer

  • Django中使用Celery的方法示例

    起步 在 <分布式任务队列Celery使用说明> 中介绍了在 Python 中使用 Celery 来实验异步任务和定时任务功能.本文介绍如何在 Django 中使用 Celery. 安装 pip install django-celery 这个命令使用的依赖是 Celery 3.x 的版本,所以会把我之前安装的 4.x 卸载,不过对功能上并没有什么影响.我们也完全可以仅用Celery在django中使用,但使用 django-celery 模块能更好的管理 celery. 使用 可以把有关 C

  • 如何在Django中添加没有微秒的 DateTimeField 属性详解

    前言 今天在项目中遇到一个Django的大坑,一个很简单的分页问题,造成了数据重复.最后排查发现是DateTimeField 属性引起的. 下面描述下问题,下面是我需要用到的一个 Task Model 基本定义: class Task(models.Model): # ...... 省略了其他字段 title = models.CharField(max_length=256, verbose_name=u'标题') created_at = models.DateTimeField(auto_

随机推荐