#python #django
#python #django
Вопрос:
У меня есть эта модель:
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'}
]
}
В настоящее время я не могу создать запрос или изменить список dicts, чтобы найти запись, где источник и цель совпадают с текущей записью (итерация по списку), и изменить поле типа.
Я использую Django 3.1 с Python 3.8.
С уважением
Комментарии:
1. Этот код не имеет смысла. ссылки — это список, но вы обрабатываете его как dict в одном месте с помощью метода «replace», которого нет ни у list, ни у dict. Однако вы никогда не нажимаете на этот код, поскольку вы добавляете dicts с 3 ключами к ссылкам и проверяете, имеет ли links dict с 2 ключами.
2. Это была именно моя проблема: найти «часть» (2 из 3 значений) dict для изменения третьего значения
3. Да, позже я понял, что это были результаты нескольких попыток, когда в какой-то момент ссылки были строкой. FWIW, я предпочитаю решение defaultdict (я также принимал это решение, когда увидел, что aneroid работает над ним). И я бы не стал объединять типы в string, а сохранил бы их в виде списка, чтобы потребитель мог выбирать, как он хочет форматировать, не прибегая к разделению.
Ответ №1:
Проблема возникает в этих строках:
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
использоваться, поскольку у него есть дополнительный ключ ‘type’.
С другой стороны, вы не можете напрямую проверять {'source': X, 'target': Y, 'type': Z1}
ссылки in, потому что тогда они не будут совпадать для then case when 'type': Z2
.
Чтобы обойти это, выполните одно из:
1. (предпочтительно) используйте словарь с ключами в качестве namedtuple
или просто кортеж источника и цели. Поскольку кортежи и namedtuples являются хешируемыми, их можно использовать в качестве ключей dict.
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) По-прежнему необходимо использовать tuple или namedtuple в качестве ключей dict, но затем использовать defaultdict
with list
для продолжения добавления типов соединений.
Вам не понадобится 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-блока намного более понятными, поскольку становится очевидным, что вы добавляете объект, если он не существует; без необходимости читать всю длинную строку.
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
Комментарии:
1. Потрясающе! Это было именно то, что я искал. Большое вам спасибо! Я использовал первый вариант, и он работает как шарм.
2. Мне нравится 2-й вариант, потому что он использует списки, в которых элементы хранятся отдельно (и не только с помощью a
/
, поэтому, если вам нужно было выполнять другие проверки, со списком элементов проще['a', 'b', 'c']
, чем со строкойa/b/c
. И используете ли вы кортежи или именованные кортежи, действительно зависит от варианта использования, и если многие кортежи используются повсюду, и становится непонятно, с каким типом кортежей выполняется операция. Если вы используете namedtuples, не забудьте добавить ключи как namedtuples; хотя ключи могут быть «проверены» на соответствие кортежам, как я показал выше. И «только добавить» безif/then
означает меньше кода и понятнее.3. Я добавил редактирование для некоторой очистки кода, которую вы можете / должны выполнить, используя выражения присваивания Python 3.8. Делает код намного более читаемым.