Есть ли способ проверить, что связанный объект уже выбран?

Я хотел бы иметь возможность проверить, был ли связанный объект уже выбран, используя либо select_related или же prefetch_related, так что я могу соответствующим образом сериализовать данные. Вот пример:

class Address(models.Model):
    street = models.CharField(max_length=100)
    zip = models.CharField(max_length=10)

class Person(models.Model):
    name = models.CharField(max_length=20)
    address = models.ForeignKey(Address)

def serialize_address(address):
    return {
        "id": address.id,
        "street": address.street,
        "zip": address.zip
    }

def serialize_person(person):
    result = {
        "id": person.id,
        "name": person.name
    }
    if is_fetched(person.address):
        result["address"] = serialize_address(person.address)
    else:
        result["address"] = None

######

person_a = Person.objects.select_related("address").get(id=1)
person_b = Person.objects.get(id=2)

serialize_person(person_a) #should be object with id, name and address
serialize_person(person_b) #should be object with only id and name

В этом примере функция is_fetched это то, что я ищу. Я хотел бы определить, есть ли у объекта person адрес разрешения, и только если он есть, он также должен быть сериализован. Но если это не так, дальнейший запрос к базе данных не должен выполняться.

Так есть ли способ добиться этого в Джанго?

3 ответа

Решение

Если address отношение было получено, тогда объект Person будет иметь заполненный атрибут с именем _address_cache; Вы можете проверить это.

def is_fetched(obj, relation_name):
    cache_name = '_{}_cache'.format(relation_name)
    return getattr(obj, cache_name, False)

Обратите внимание, что вам нужно вызвать это с объектом и именем отношения:

is_fetched(person, 'address')

с тех пор person.address немедленно вызовет выборку.

Изменить обратное или отношения "многие ко многим" можно только с помощью prefetch_related; который заполняет один атрибут, _prefetched_objects_cache, который является списком списков, где ключ - это название связанной модели. Например, если вы делаете:

addresses = Address.objects.prefetch_related('person_set')

затем каждый элемент в addresses будет иметь _prefetched_objects_cache Дикт, содержащий "person' ключ.

Обратите внимание, что оба они - атрибуты с одним подчеркиванием, что означает, что они являются частью частного API; вы можете использовать их, но Django также может изменить их в будущих выпусках.

Начиная с Django 2.0, вы можете легко проверить все извлеченные отношения:

obj._state.fields_cache

ModelStateFieldsCacheDescriptor отвечает за хранение ваших кэшированных отношений.

>>> Person.objects.first()._state.fields_cache
{}
>>> Person.objects.select_related('address').first()._state.fields_cache
{'address': <Address: Your Address>}

Согласно этому комментарию к тикету, указанному в комментарии @jaap3 выше, рекомендуемый способ сделать это для Django 3+ (возможно, 2+?) - использовать недокументированный is_cachedв поле модели, которое происходит из этого внутреннего миксина :

      >>> person1 = Person.objects.first()
>>> Person.address.is_cached(person1)
False

>>> person2 = Person.objects.select_related('address').last()
>>> Person.address.is_cached(person2)
True
Другие вопросы по тегам