Я использую несколько различных вызовов API для заполнения (create_or_update) полей конкретной модели. Вместо того, чтобы создавать новую серию полей create_or_update для каждого вызова API, я пытаюсь создать цикл, чтобы избавиться от стандартного кода. Существует небольшое предостережение в том, что некоторые из полей, возвращаемых API, перекрываются с зарезервированными ключевыми словами Python (например, id, type). Мне нужно быть в состоянии выбрать их и переименовать в dict во время циклического сравнения.
- Ищет модель и получает ее поля, создавая список этих полей, например:
['id', 'organizationId', 'name', ... ]
- Прокручивает ответ API и создает словарь ключей: значений для каждого объекта ответа. Если значение списка найдено в словаре, присвойте ключу: значение то, что найдено в ответе API, в противном случае установите для поля значение Нет.
{ 'id': 'N_543265', 'organizationId': '43675', *name field missing* , ...}
Так как 'id' и 'organizationId' найдены, я хочу, чтобы их значения были обновлены в экземпляре модели, но, поскольку 'name' не найдено, я хочу, чтобы это было пропущено
Основная проблема, с которой я сталкиваюсь, заключается в том, что когда она запускается (это задача сельдерея), она говорит мне следующее:
database.models.Network.DoesNotExist: запрос на сопоставление сети не существует.
models.py
class Device(models.Model):
networkId = models.ForeignKey(Network, on_delete=models.CASCADE, blank=True, null=True, related_name='network_device')
name = models.CharField(max_length=100, default='', blank=True, null=True)
organizationId = models.CharField(max_length=100, default='', blank=True)
devices_status.py
...**API call happens here**
result = response.text
job = json.loads(result)
l = []
s = []
x = Device._meta.get_fields()
for f in x:
fields = []
try:
l.append(str(f).split('.'))
for a in l:
s.append(a[-1])
for q in s:
if '<' in q:
continue
elif '>' in q:
continue
else:
fields.append(q)
except AttributeError as e:
print(e)
pass
for line in job:
d = {}
for k, v in line.items():
if k in fields:
d[k] = v
else:
continue
# Have to delete this from the auto field pull otherwise it gets
# confused with python factory vars
try:
del d['type']
except KeyError as e:
print('Delete type: {}'.format(e))
pass
try:
Device.objects.update_or_create(
networkId = Network.objects.get(networkId=d['networkId']),
name = d['name'],
serial = d['organizationId'],
defaults = d
)
except KeyError as e:
print(e)
pass
return result
По сути, я хочу, чтобы мой экземпляр модели заполнялся на основе сравнения полей, возвращаемых в API, чтобы избежать выброса KeyError и без длинного списка попыток /, за исключением того, что вручную выбирались поля, которые могут или не могут существовать в Данные API.
Любая помощь была бы великолепной, даже если бы она касалась того, как я решил проблему. Я думаю, что многие люди могли бы извлечь пользу из этого фрагмента, если бы я мог заставить его работать правильно.
EDIT
Мне удалось найти решение этой проблемы, поэтому я подробно изложил его здесь для всех, кто заинтересован. Я обнаружил, что это решение избавляет меня от головной боли при импорте данных в базу данных из API с различными полями.
Я буду использовать сокращенную версию данных, возвращаемых из API панели инструментов Meraki (https://dashboard.meraki.com/api_docs):
...**API returned data**
[
{
"lat": 1.0,
"lng": 1.0,
"address": "123 fake street",
"serial": "2222-2222-2222",
"mac": "22:22:22:22:22:22",
"lanIp": "10.1.1.2",
"tags": "tag1 tag2",
"networkId": "5678",
"name": "accesspoint01",
"model": "ap01"
},
{
"lat": 2.0,
"lng": 2.0,
"address": "123 fake street",
"serial": "5555-5555-5555",
"mac": "44:44:44:44:44:44",
"wan1Ip": "1.1.1.1",
"wan2Ip": null,
"lanIp": "10.10.10.10",
"networkId": "2468",
"name": "gateway01",
"model": "gw01"
},
]
...
Это тип данных API, которые мы ожидаем обработать и добавить в базу данных.
models.py
class Device(models.Model):
# FK
parent_network = models.ForeignKey(Network, on_delete=models.CASCADE, blank=True, null=True, related_name='network_device')
name = models.CharField(max_length=100, default='', blank=True, null=True)
lat = models.CharField(max_length=100, default='', blank=True, null=True)
lng = models.CharField(max_length=100, default='', blank=True, null=True)
tags = models.CharField(max_length=200, default='', blank=True, null=True)
serial = models.CharField(max_length=100, default='', blank=True, null=True)
mac = models.CharField(max_length=100, default='', blank=True, null=True)
publicIp = models.CharField(max_length=100, default='', blank=True, null=True)
lanIp = models.CharField(max_length=100, default='', blank=True, null=True)
wan1Ip = models.CharField(max_length=25, default='', blank=True, null=True)
wan2Ip = models.CharField(max_length=25, default='', blank=True, null=True)
model = models.CharField(max_length=100, default='', blank=True, null=True)
address = models.CharField(max_length=100, default='', blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Created')
updated_at = models.DateTimeField(auto_now=True, verbose_name='Last Updated')
def __str__(self):
return str(self.name)
class Meta:
ordering = ['name']
device.py (у меня есть несколько похожих функций в папке, названных в честь их функций для простоты управления)
def device(apikey):
excludes = ['serial', 'parent_network']
## Once the request has been made, the returned data is assigned
...
response = requests.request("GET", url, headers=headers)
result = response.text
job = json.loads(result)
# Create the dictionary that will be used in defaults of update_or_create
d = {}
# Iterate over the key:value items returned from the API call
for k, v in job.items():
try:
# This part checks the keys in the returned dataset to create dictionary entries based on those returned values
if k in excludes:
continue
else:
d[k] = v
d['parent_network'] = Network.objects.get(parent_network=job['networkId'])
except Exception as e:
print('[*] device.py - Dictionary error: {}'.format(e))
continue
# Add the object to the database, based on the serial we return from the API
try:
Device.objects.update_or_create(
serial=job['serial'],
defaults=d,
)
except KeyError as e:
print('[*] device.py - Device KeyError: {}'.format(e))
pass
Зачем использовать массив, исключающий некоторые элементы из словаря ???
'serial' - исключено, потому что мы хотим использовать это как уникальное поле для поиска объекта для обновления
'parent_network' - Это поле внешнего ключа, поэтому его необходимо передать объекту 'Network'
Теперь, если у нас есть другие подобные функции, которые имеют дело с данными API, у нас есть
немного шаблона, который мы можем скопировать. Все, что нам нужно сделать, чтобы применить его к новому
набор данных API - это изменение массива исключений и обработка любых полей ForeignKey.
вне петли k, v.
Следующее, что нужно сделать, это превратить это в полностью повторно используемую функцию ванили.
что могут быть переданы все необходимые переменные, но, к сожалению, это вне моего
Диапазон навыков. Было бы здорово увидеть кого-то еще, если это возможно.
Надеюсь, это было полезно. Спасибо за чтение!