Хранение пользовательских типов данных в Django - PullRequest
0 голосов
/ 06 марта 2019

У меня есть собственный класс (и, возможно, более скоро), который я хотел бы аккуратно хранить на некоторых своих моделях в Django.

Класс выглядит примерно так:

class BBOX(object):

    def __init__(self, min_x, min_y, max_x, max_y, srs_code, **kwargs):
        self.min_x = min_x
        self.max_x = max_x
        self.min_y = min_y
        self.max_y = max_y

        self._srs_code = get_srs_code(srs_code)

        self._srs_identifier = 'EPSG:{}'.format(self.srs_code)
        self._srs = SRS(self.srs_code)

У класса очень много свойств и вспомогательных функций, которые мы используем для безопасной обработки объектов ограничивающего прямоугольника в нашем приложении на основе геопространственных данных. В коде мы безопасно передаем их, зная, что они будут просты в использовании и прекрасно взаимодействуют с остальным нашим кодом. Как правило, мы никогда не хотим работать с ограничивающим прямоугольником в нашем коде, который не представлен экземпляром этого класса, за исключением случаев, когда мы обязательно должны передать список или что-то подобное (например, когда мы сериализуем значения в JSON, YAML или что-то подобное).

Некоторые из наших моделей Django имеют значения, связанные с BBOX. В настоящее время все эти разрозненные модели должны вручную определить поля, которые им необходимы для представления BBOX. Для удобства мы добавили некоторые вспомогательные свойства к некоторым из них для преобразования в наш объект. Например:

class Layer(PolymorphicModel):

    name = models.CharField(max_length=255, blank=True, null=True)
    title = models.CharField(max_length=255, blank=True, null=True)
    parent_layer = models.ForeignKey('self', blank=True, null=True)

    min_x = models.FloatField()
    min_y = models.FloatField()
    max_x = models.FloatField()
    max_y = models.FloatField()
    srs_code = models.IntegerField()

    @property
    def bbox(self):
        return BBOX(self.min_x, self.min_y, self.max_x, self.max_y, self.srs_code)

Очевидно, что это может привести к значительному дублированию кода, если нам потребуется добавить набор полей в каждую модель, которая может иметь BBOX. Это может затруднить поддержание и изменение.

Какой лучший способ справиться с чем-то подобным? Я подумал о создании новой таблицы для BBOX:

class DjangoBbox(Model):

    min_x = models.FloatField()
    min_y = models.FloatField()
    max_x = models.FloatField()
    max_y = models.FloatField()
    srs_code = models.IntegerField()

Тогда я мог бы добавить ссылки на внешние ключи, где это необходимо, но что-то в этой идее пахнет мной. Что-то не так с этой идеей? Первое, о чем я могу подумать, это то, что теоретически несколько ключей могут указывать на одну и ту же запись DjangoBbox, и изменения одного из них будут отражаться в обоих, даже если они непреднамеренные.

1 Ответ

1 голос
/ 06 марта 2019

Я вижу три варианта решения этой проблемы:

  1. Использование класса абстрактной модели : вы можете создать абстрактный базовый класс BBox, который вы наследуете каждый раз, когда высоздать модель.Просто добавьте дополнительные поля к своим подклассам.Поэтому предположим, что абстрактный BBOXModel класс:

    class Layer(BBOXModel):
        name = models.CharField(max_length=255, blank=True, null=True)
        title = models.CharField(max_length=255, blank=True, null=True)
        parent_layer = models.ForeignKey('self', blank=True, null=True)
    
    layer = Layer.objects.first()
    layer.min_x  # returns the min_x field
    layer.bbox  # returns the bbox property from `BBOXModel`
    layer.bbox = myBbox  # if you also create a setter for this property
    
  2. Наследование нескольких таблиц также может работать, так что ваши модели наследуются от конкретного BBox Model: Thisавтоматически создает отношения один-к-одному.Он также создает одну отдельную таблицу со всеми вашими ограничивающими рамками (так что вы можете запросить ее отдельно), но каждая из них привязана только к одному экземпляру подмодели, который может быть разных типов.Также здесь все свойства и методы родительского класса работают так же, как вы ожидаете от подклассов.Пример кода выше аналогичен, за исключением того, что теперь у вас есть конкретный BBOXModel, вы также можете сделать:

    bbox = BBOXModel.objects.first()
    bbox.layer  # works if this bbox "is part of a" Layer, otherwise it will throw an AttributeError
    
  3. Наконец, вы можете создать полностью пользовательскийтип поля : в ваших моделях оно может содержать BBOX: само поле будет BBOX, которое у вас уже есть, поэтому, говоря layer.bbox, это будет полный объект Python BBOX, как вы ужепривыкли к.Требуется решить, как кодировать BBOX в базе данных.Пример, приведенный в ссылке (раздача карт), может помочь.Лично, если бы использовать PostgreSQL, я бы использовал JSONField в качестве базового поля базы данных (наследовать от JSONField).Вам, вероятно, нужно будет только определить to_python и get_prep_value, преобразовать в JSON и из него и вызвать super(), чтобы позволить JSONField обрабатывать то, как оно представлено в БД.Таким образом, вы сможете определить свой класс следующим образом:

    class Layer(PolymorphicModel):
        name = ...
        ...
        bbox = BBOXField()
    

Со всеми тремя опциями вы можете полностью повторно использовать класс BBOX, который у вас уже есть, путем инкапсуляции обработкиBBOX объектов внутри классов (или поля).В конечном итоге вы сможете просто присвоить BBOX экземпляру вашей модели, а при извлечении экземпляра модели из базы данных получить его свойство bbox, которое будет возвращать BBOX.

...