понедельник, 25 марта 2013 г.

Документация South - Перевод. Часть 4: Пользовательские поля

В South 0.7 появилось радикальное отличие от предыдущих версий. До сих пор, если у Вас было пользовательское поле, South пытался использовать очень сильное колдунство для того, чтобы определить, как "замораживать" это поле,так что оно могло быть отклонено в миграции (и это было не очень красивое колдунство - комбинация регулярных выражений и модуля parser).
Хотя это, как ни странно, работало хорошо у большинства пользователей, тем не менее в некоторых случаях что-то шло не так, и Вы даже не знали об этом, пока не проходило несколько недель... В целях большей вменяемости и меньшей магии теперь Вы должны указать South как замораживать ваши пользовательские поля.
Не волнуйтесь, это достаточно просто и Вам надо сделать это лишь раз для каждого поля.

Наше поле

В этом примере мы будем использовать пользовательское поле, которое хранит список тегов в БД. Мы будем просто хранить их в TEXT колонке с некоторым разделителем (по умолчанию мы будем использовать |, но можно использовать и любой другой символ - просто передайте его как именованный аргумент).
Вот класс этого поля. Я разместил его в appname/fields.py (более подробно о создании собственных полей смотрите документацию Django):

 from django.db import models

 class TagField(models.TextField):

     description = "Stores tags in a single database column."

     __metaclass__ = models.SubfieldBase

     def __init__(self, delimiter="|", *args, **kwargs):
         self.delimiter = delimiter
         super(TagField, self).__init__(*args, **kwargs)
    
     def to_python(self, value):
         # If it's already a list, leave it
         if isinstance(value, list):
             return value
        
         # Otherwise, split by delimiter
         return value.split(self.delimiter)
    
     def get_prep_value(self, value):
         return self.delimiter.join(value)

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

Именованные аргументы

South замораживает поля сохраняя их имя класса и модуль (таким образом можно получить сам класс поля) и именованные аргументы, которые Вы используете для конкретного экземпляра класса (например, CharField(max_lengh=50) не то же самое, что CharField(max_lengh=150)).
Так как Python не хранит именованные аргументы, переданные конструктору класса, South должен восстановить их уже из полученного экземпляра класса. Например, мы знаем что значение именованного аргумента max_length класса CharField хранится в атрибуте self.max_lenght, тогда как ForeignKeys хранят свои именованные аргументы to (определяющие модель, на которую они указывают, так же первый позиционный аргумент) как self.rel.to.
South знает все эти правила для стандартных полей Django, но Вам необходимо объяснить все это касательно своих полей. Хорошая новость состоит в том, что South может отследить цепочку наследования, так что о полях, определённых в родительских классах говорить заново в дочерних классах не надо (или надо сообщить только о тех дополнительных именованных аргументах, которых не было в родительском классе).
В нашем примере мы определяем только один дополнительный именованный аргумент - delimiter. Вот код, который мы добавляем для того, чтобы South мог обработать наше новое поле. Чуть позже мы его объясним:


 from south.modelsinspector import add_introspection_rules
 add_introspection_rules([
     (
         [TagField], # Класс(ы) к которым это применимо
         [],         # позиционные аргументы (не используется)
         {           # именованные аргументы
             "delimiter": ["delimiter", {"default": "|"}],
         },
     ),
 ], ["^southtut\.fields\.TagField"])


Как Вы можете видеть, для того, чтобы рассказать South о вашем новом поле Вам нужно вызвать функцию south.modelsinspector.add_introspection_rules. Вы должны разместить этот код рядом с определением вашего поля. Так как последнее, что Вы хотели бы - это импортировать поле, но чтобы этот код не был выполнен.
add_introspection_rules принимает два аргумента: список правил и список регулярных выражений. Список регулярных выражений используется South для того чтобы понять, можно ли исследовать это поле. Просто иметь правило, которое подходит для поля недостаточно, так как наследование правил подразумевает, что любой пользовательский класс поля будет иметь хотя бы одно правило, под которое он подпадает (опять - "(as they will inherit from ``Field``, if not something more specificlike ``CharField``), and some custom fields can get by with only those inherited rules (more on that shortly)").
Первый аргумент - список правил. Каждое правило - это кортеж (или список) с тремя элементами:

  1. Список классов к которым применимы эти правила. Практически наверняка у Вас будет тут только одно поле, например, то, про которое мы сейчас говорим.
  2. Спецификация позиционных аргументов. Практически наверняка будет пустым списком - []
  3. Спецификация именованных аргументов: это словарь, где ключ - имя аргумента, а значение - список или кортеж (имя_аргумента, опции)
Имя аргумента определяет место, где может быть найдено значение именованного аргумента - в нашем случае это delimiter, так как значение именованного аргумента мы храним в self.delimiter. (Если бы это было правило ForeignKey, то тут бы стояло rel.to)
Опции- это словарь. Вы можете безопасно оставить этот аргумент пустым, но можно и использовать его для определения значения по умолчанию и если South обнаружит значение, соответствующее этому, то он не будет указывать это ключевое слово в определении заморозки, что позволит это определение сделать более простым и читаемым.

Простые наследования

Если ваше поле наследуется напрямую из другого поля Django, например, CharField, и не добавляет других именованных аргументов, то Вам не нужно добавлять какие-либо правила в add_introspection_rules. Вы можете просто сказать South, что с этим полем всё в порядке:
 class UpperCaseField(models.TextField):
     "Убеждаемся, что содержимое всегда в верхнем регистре."
     
     def to_python(self, value):
         return value.upper()
     
     def get_prep_value(self, value):
         return value.upper()
     
 from south.modelsinspector import add_introspection_rules
 add_introspection_rules([], ["^southtut\.fields\.UpperCaseField"])

Больше информации

Более подробную документацию на эту тему можно обнаружить в разделе Extending introspection.

Комментариев нет:

Отправить комментарий