24 ноября 2010 г.

Заставляем кастомные поля для моделей работать под Django 1.2+

Некоторое время тому назад я разработал несколько кастомных полей для моделей, таких как PickleField и JSONField и все было в них хорошо. Но время ведь не стоит на месте и мне захотелось окончательно перейти в своих проектах на прекрасную Django 1.2+ и использовать ее бенефиты вместе с использованием моих старых кастомных полей.

Для начала я просто попробовал запустить свой проект под Django 1.2 и сразу же получил вылетающие тесты при попытках считать любые данные из базы данных для моделей, содержащих кастомное поле. Ошибкой был обыкновенный ObjectDoesNotExist, который сразу натолкнул меня на мысль что проблема где-то в get_db_prep_value методе.

Открыв документацию, я понял, что был прав. Проблема была в том, что при переходе на поддержку многих БД одновременно был совершен рефакторинг get_db_prep_value и подобных ему методов. Для них были добавлены аргументы connection, prepared=False и пользоваться ими рекоммендовалось только в случае БД-зависимого преобразования. А БД-независимые преобразования советовали делать в get_prep_value и подомных ему методов.

Так как мои поля не требовали БД-зависимого преобразования, я решил просто переименовать свои get_db_prep_value методы в get_prep_value. Результат не заставил себя долго ждать и при следующем перезапуске, тесты моделей, содержащие кастомные поля, прошли без ошибок. Все бы хорошо, но не выбрасывать же поддержку кастомных полей для версий Django ниже 1.2.

Посему я принялся думать как бы это реализовать. Первым в голову, почему-то пришел дурной вариант о дублировании функционала get_prep_value в get_db_prep_value, следующим образом:

class PickleField(models.Field):
    ...
    def get_db_prep_value(self, value, **kwargs):
        return self.get_prep_value(value)

    def get_prep_value(self, value):
        return pickle.dumps(value)
    ...

Да, это решило проблему с прохождением тестов для Django < 1.2, но теперь тесты стали валиться на Django 1.2+. Видно что-то было не так. Вооружившись дуростью, я начал копать в сторону неправильного генерирования SQL-запроса. И так бы продолжалось долго, если бы я не остановился и не подумал еще раз посмотреть на отрефакторенный код get_db_prep_value метода. После этого проблема стала очевидной, мое предыдущее решение просто дублировало преобразование значения и потому Django не могло найти записи в базе данных и возвращало ObjectDoesNotExist.

Что ж после определении проблемы надо было немного подумать над ее решением, но я не стал себя заморачивать и просто для всех кастомных полей и версии Django меньше 1.2, я переименовал методы get_prep_value в get_db_prep_value при помощи следующего кода:

from django import VERSION


if VERSION[0] == 1 and VERSION[1] < 2:
    fields = (JSONField, PickleField)
    for field in fields:
        if not hasattr(field, 'get_prep_value'):
            continue
        field.get_db_prep_value = field.get_prep_value
        del field.get_prep_value

Последующий перезапуск тестов на 1.0.4 и 1.1.2 завершился успехом, впрочем как и для 1.2.3 и версии из транка.

Конечно, мне не особо нравится эта магия, но у разработчиков Django похоже не было другого выбора для реализации рефакторинга, посему имеем то, что имеем :)

blog comments powered by Disqus