Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

查询我关注的及屏蔽的节点 #11

Open
codetalks-new opened this issue Feb 23, 2019 · 1 comment
Open

查询我关注的及屏蔽的节点 #11

codetalks-new opened this issue Feb 23, 2019 · 1 comment

Comments

@codetalks-new
Copy link
Owner

codetalks-new commented Feb 23, 2019

查询功能要求

查询我关注的节点
返回完整的我关注的节点。
排序注意点:

  1. 如果按时间排序的话,则按关注的时间排序。即按 UserNodeStar.created 排序。
  2. 如果按节点活跃时间排序则需要按 Node.updated 排序。
  3. 如果按活跃度排序,则需要按 Node.active_degree 来排序。

从排序需求来看,原 Node 节点需要增加 active_degree 这个字段。
对于查询语句来说,查询语句不能使用单表查询+子查询的方式。
因为排序需要用到关联表的 UserNodeStar.created 字段。
从逻辑上来说以 Node 作为主表,而以 UserNodeStar 作为右关联表。

那么下面是一个以关注时间进行排序的 SQL:

select node.* ,link.created,link.user_id
from wepost_posts_node as node 
left outer join wepost_posts_usernodestar as link on node.id = link.node_id and link.user_id == 43
where link.user_id = 43
order by  link.created

这里使用了 left outer join 是因为如果使用内联接的话,查询集将太大。
在关联条件中的 on node.id = link.node_id and link.user_id == 43 这是为了减少 关联集的大小。
因为wepost_posts_usernodestar 是记录所有人对所有节点的关注

@codetalks-new
Copy link
Owner Author

翻译成 Django ORM 对应语句

如果尝试手动使用 Django ORM 来构造左外联接,一般会是失败的。因为 Django ORM 提交使用其提供的关联管理。

  1. Node 数据模型下添加一个多对多的关联声明
  users = models.ManyToManyField(WepostUser, through='UserNodeStar', through_fields=('node', 'user'))

through_fields 指定了在关联中间表中唯一确定这种关联关系所需要的字段。
添加了上面的关联之后,Django ORM 会自动在 user 对应添加 node_set 属性。从语义上来说是关联用户所有关注的节点(具体类型是:ManyRelatedManager)。值得注意的是,through_fields 的字段的指定顺序是有要求的。否则会报下面的错误。

AttributeError: 'ManyToManyField' object has no attribute '_m2m_reverse_name_cache'

相关文档引用如下:

through_fields accepts a 2-tuple ('field1', 'field2'), where field1 is the name of the foreign key to the model the ManyToManyField is defined on (node in this case), and field2 the name of the foreign key to the target model (user in this case).

最后实现的代码如下:

  def _query_related_nodes(self, state: UserNodeStarState, sort_type: UserNodeSortType = UserNodeSortType.CREATED,
                           keyword: str = None):
    if sort_type == UserNodeSortType.ACTIVE_DEGREE:
      sort = SortField("active_degree", SortDirection.DESC)
    elif sort_type == UserNodeSortType.UPDATED:
      sort = SortField("updated", SortDirection.DESC)
    else:
      sort = SortField("usernodestar__created", SortDirection.DESC)
    node_set = self.user.node_set
    qs = node_set.all().filter(usernodestar__state=state).order_by(sort.ordering)
    if keyword:
      qs = qs.filter(Q(name__icontains=keyword) | Q(brief__icontains=keyword))
    return qs

  def query_star_nodes(self, sort_type: UserNodeSortType = UserNodeSortType.CREATED, keyword: str = None):
    """查询已关注的节点"""
    return self._query_related_nodes(UserNodeStarState.FOLLOWING, sort_type=sort_type, keyword=keyword)

  def query_blocked_nodes(self, keyword: str = None):
    """查询已屏蔽的节点"""
    return self._query_related_nodes(UserNodeStarState.BLOCKING, keyword=keyword)

单元测试代码如下:

def test_query_nodes(ut1, node_c, node_py, node_js, node_flask, node_vue):
  service = UserNodeService(ut1)
  nodes = [node_c, node_js, node_py, node_flask]
  for node in nodes:
    service.star_node(node)
  service.block_node(node_vue)

  ret_nodes1 = list(service.query_star_nodes())
  assert len(ret_nodes1) == len(nodes)
  ret_nodes1.reverse()
  assert ret_nodes1 == nodes

  ret_nodes2 = service.query_star_nodes(keyword="py")
  assert list(ret_nodes2) == [node_py]

  blocked_nodes = list(service.query_blocked_nodes())
  assert blocked_nodes == [node_vue]

@codetalks-new codetalks-new changed the title 查询我关注的节点 查询我关注的及屏蔽的节点 Feb 27, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant