Объекты модели Django стали недоступны для хэширования после обновления до django 2.2 - PullRequest
1 голос
/ 14 апреля 2020

Я тестирую обновление приложения с Django 2.1.7 до 2.2.12. Я получил ошибку при запуске моих модульных тестов, которая сводится к тому, что объект модели не может быть хешируемым:

    Station.objects.all().delete()
py37\lib\site-packages\django\db\models\query.py:710: in delete
    collector.collect(del_query)
py37\lib\site-packages\django\db\models\deletion.py:192: in collect
    reverse_dependency=reverse_dependency)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <django.db.models.deletion.Collector object at 0x000001EC78243E80>
objs = <QuerySet [<Station(nom='DUNKERQUE')>, <Station(nom='STATION1')>, <Station(nom='STATION2')>]>, source = None, nullable = False
reverse_dependency = False

    def add(self, objs, source=None, nullable=False, reverse_dependency=False):
        """
        Add 'objs' to the collection of objects to be deleted.  If the call is
        the result of a cascade, 'source' should be the model that caused it,
        and 'nullable' should be set to True if the relation can be null.

        Return a list of all objects that were not already collected.
        """
        if not objs:
            return []
        new_objs = []
        model = objs[0].__class__
        instances = self.data.setdefault(model, set())
        for obj in objs:
>           if obj not in instances:
E           TypeError: unhashable type: 'Station'

Экземпляры объектов модели могут быть хэшируемыми в Django, как только они сохранено в базу данных и получено первичный ключ.

Я не понимаю, откуда возникла ошибка и почему я получаю ее при запуске этого базового c кода:

In [7]: s = Station.objects.create(nom='SOME PLACE')

In [8]: hash(s)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-9333020f3184> in <module>
----> 1 hash(s)

TypeError: unhashable type: 'Station'

In [9]: s.pk
Out[9]: 2035

Все это код работает нормально, когда я снова переключаюсь на Django 2.1.7. То же самое происходит с другими объектами модели в приложении. Я использую python версию 3.7.2 на Windows, с бэкэндом SQlite (на рабочей станции разработки).

Редактировать: вот определение модели, упомянутой выше:

class Station(models.Model):
    nom = models.CharField(max_length=200, unique=True)

    def __str__(self):
        return self.nom

    def __repr__(self):
        return "<Station(nom='{}')>".format(self.nom)

    def __eq__(self, other):
        return isinstance(other, Station) and self.nom == other.nom

1 Ответ

1 голос
/ 15 апреля 2020

Как отмечает @Alasdair, проблема заключалась в изменении поведения, внесенном в Django 2.2 для соответствия поведению модельного класса, когда __eq__() переопределяется, но не __hash__(). В соответствии с python документами для __hash__():

Классу, который переопределяет __eq__() и не определяет __hash__(), будет __hash__() иметь неявно установленный для Нет.

Более подробную информацию о реализации этого поведения в Django можно найти в этот билет .

Исправление может быть предложенным. в билете, т. е. переназначение метода __hash__() модели на метод суперкласса: __hash__ = models.Model.__hash__

Или более объектно-ориентированный способ может быть:

    def __hash__(self):
        return super().__hash__()

Это кажется немного странным, потому что в этом нет необходимости: по умолчанию вызов __hash__() должен использовать метод из суперкласса, в котором он реализован. Это говорит о том, что Django как-то нарушает инкапсуляцию. Но, может быть, я не все понимаю. В любом случае, это sidenote.

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

    def __hash__(self):
        if self.pk is None:
            return hash(self.nom)
        return super().__hash__()
...