Цикл для получения полей модели, создания новых экземпляров модели на основе возвращаемых значений API - PullRequest
2 голосов
/ 12 мая 2019

Я использую несколько различных вызовов API для заполнения (create_or_update) полей конкретной модели. Вместо того, чтобы создавать новую серию полей create_or_update для каждого вызова API, я пытаюсь создать цикл, чтобы избавиться от стандартного кода. Существует небольшое предостережение в том, что некоторые из полей, возвращаемых API, перекрываются с зарезервированными ключевыми словами Python (например, id, type). Мне нужно быть в состоянии выбрать их и переименовать в dict во время циклического сравнения.

  1. Ищет модель и получает ее поля, создавая список этих полей, например:
['id', 'organizationId', 'name', ... ]
  1. Прокручивает ответ 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.

Следующее, что нужно сделать, это превратить это в полностью повторно используемую функцию ванили. что могут быть переданы все необходимые переменные, но, к сожалению, это вне моего Диапазон навыков. Было бы здорово увидеть кого-то еще, если это возможно.

Надеюсь, это было полезно. Спасибо за чтение!

...