python - 如何在某些字段中聚合具有相同值的多个对象?
问题描述
我有这个模型:
class Connection(models.Model):
CONNECTION_CHOICES = [
('REST-API', 'REST-API'),
('SSH', 'SSH'),
('SFTP', 'SFTP'),
('SOAP-API', 'SOAP-API'),
]
endpoint = models.CharField(max_length=240, blank=True, null=True)
port = models.IntegerField(blank=True, null=True)
connection_type = models.CharField(max_length=240, choices=CONNECTION_CHOICES)
source_tool = models.ForeignKey(Tool, on_delete=models.CASCADE, related_name='source-tool+')
target_tool = models.ForeignKey(Tool, on_delete=models.CASCADE, related_name='target-tool+')
def __str__(self):
return self.source_tool.name + " to " + self.target_tool.name
def get_absolute_url(self):
return reverse('tools:connection-detail', kwargs={'pk': self.pk})
在一个视图中,我正在尝试组合对象,其中 source_tool 和 target_tool 相同,但 connection_type 不同。
目前,我有这样的看法:
def api_map_view(request):
json = {}
nodes = []
links = []
connections = Connection.objects.all()
for connection in connections:
if {'name': connection.source_tool.name, 'id': connection.source_tool.id} not in nodes:
nodes.append({'name': connection.source_tool.name, 'id': connection.source_tool.id})
if {'name': connection.target_tool.name, 'id': connection.target_tool.id} not in nodes:
nodes.append({'name': connection.target_tool.name, 'id': connection.target_tool.id})
if {'source': connection.source_tool.id, 'target': connection.target_tool.id} in links:
links.replace({'source': connection.source_tool.id, 'target': connection.target_tool.id, 'type': links['type'] + '/' + connection_type})
else:
links.append({'source': connection.source_tool.id, 'target': connection.target_tool.id, 'type': connection.connection_type})
json['nodes'] = nodes
json['links'] = links
print(json)
return JsonResponse(data=json)
这返回,例如
{
'nodes':
[
{'name': 'Ansible', 'id': 1 },
{'name': 'Terraform', 'id': 2},
{'name': 'Foreman', 'id': 3}
],
'links':
[
{'source': 1, 'target': 2, 'type': 'SSH'},
{'source': 2, 'target': 3, 'type': 'REST-API'}
{'source': 1, 'target': 2, 'type': 'REST-API'}
]
}
我的用例是,我想修改连接,但我没有为同一连接获得 2 个不同的列表条目,只是类型不同。而不是上面的 JSON,我想实现这个:
{
'nodes':
[
{'name': 'Ansible', 'id': 1 },
{'name': 'Terraform', 'id': 2},
{'name': 'Foreman', 'id': 3}
],
'links':
[
{'source': 1, 'target': 2, 'type': 'SSH/REST-API'},
{'source': 2, 'target': 3, 'type': 'REST-API'}
]
}
目前我无法创建查询或修改字典列表以查找条目,其中源和目标与当前条目相同(遍历列表),并修改类型字段。
我将 Django 3.1 与 Python 3.8 一起使用。
问候
解决方案
问题出现在这些行中:
if {'source': connection.source_tool.id, 'target': connection.target_tool.id} in links:
links.replace({'source': connection.source_tool.id, 'target': connection.target_tool.id, 'type': links['type'] + '/' + connection_type})
else:
links.append({'source': connection.source_tool.id, 'target': connection.target_tool.id, 'type': connection.connection_type})
您正在检查是否{'source': X, 'target': Y}
在,links
但对于第一次出现,您正在添加 {'source': X, 'target': Y, 'type': Z1}
到links
. 所以你添加的项目永远不会True
因为in links
它有一个额外的键“类型”。
另一方面,您不能直接检查{'source': X, 'target': Y, 'type': Z1}
in 链接,因为 then case when 不匹配'type': Z2
。
要解决此问题,请执行以下操作之一:
1.(首选)使用带有键的字典作为namedtuple
源和目标的元组或只是一个元组。由于元组和命名元组是可散列的,它们可以用作字典键。
import collections # at the top
links = {} # links is now a dict, not a list
SourceTarget = collections.namedtuple('SourceTarget', 'source target')
# >>> SourceTarget('X', 'Y') # to show how they work
# SourceTarget(source='X', target='Y')
像这样使用:
if (connection.source_tool.id, connection.target_tool.id) in links: # tuples can match with namedtuples
links[SourceTarget(connection.source_tool.id, connection.target_tool.id)] += '/' + connection.connection_type
else:
links[SourceTarget(connection.source_tool.id, connection.target_tool.id)] = connection.connection_type
最后,您希望它们作为对象/字典列表:
json['links'] = [{'source': st.source, 'target': st.target, 'type': type_}
for st, type_ in links.items()]
# I used `type_` so that it doesn't conflict with Python own `type()`
2.(1 的变体)仍然需要使用元组或命名元组作为您的 dict 键,然后使用defaultdict
withlist
来继续附加连接类型。
您不需要该if/else
零件,您可以这样做:
import collections
links = collections.defaultdict(list)
...
# using tuples as the key instead of namedtuples....
links[(connection.source_tool.id, connection.target_tool.id)].append(connection.connection_type)
这将为每个条目创建新条目,(source, target)
并将其type
作为列表中的单个值,或者将附加到该列表中。无需if
检查。
并将其转换为您的 json obj:
json['links'] = [{'source': st[0], 'target': st[1], 'type': '/'.join(types)}
for st, types) in links.items()]
顺便说一句,由于您使用的是Python 3.8,因此您可以使用赋值表达式,即“海象运算符”,以减少重复并使代码更简洁。
以选项 1 为例,它会使你的 if-block 的第一部分更加清晰,因为很明显你正在添加不存在的东西;无需阅读整条长线。
if (src := {'name': connection.source_tool.name, 'id': connection.source_tool.id}) not in nodes:
nodes.append(src)
if (trg := {'name': connection.target_tool.name, 'id': connection.target_tool.id}) not in nodes:
nodes.append(trg)
if (st := (connection.source_tool.id, connection.target_tool.id)) in links:
# used a tuple to update _existing_ NT element
links[st] += '/' + connection.connection_type
else:
# but must use namedtuples when adding new elements
links[SourceTarget(*st)] = connection.connection_type
推荐阅读
- javascript - 带有两个复选框的 Gridview - 需要在任一复选框更改时设置匹配行的值
- image - 使用无法在 Safari 中工作的图像转换 SVG
- c# - 如何使用 selenium/C# 在下拉菜单上移动光标
- video - 将 HLS M3U8 转换为 MP4 后,如何找出是否缺少 TS 段?
- inheritance - Liskov 原理和矩阵设计
- php - 如何获取 Storage::put 返回 false 的原因?
- python - 具有分类值和非数字值的 Python Pandas VLOOKUP 函数
- asp.net-mvc - 饼图中的多个查询
- navbar - 如何在导航栏中重叠之前显示汉堡图标
- java - 骰子滚轮值