Ищем пользовательское поле Django, которое эмулирует ManyToManyField, сохраняя список идентификаторов в столбце - PullRequest
0 голосов
/ 28 июля 2011

Я бы хотел связать MyModel с AnotherModel, используя отношение «многие ко многим» без фактического использования отношения SQL: вместо этого я хочу сохранить список пакетов AnotherModel в столбце MyModel и иметь настраиваемое поле для обработки преобразования к QuerySet (или списку экземпляров) и обратному отношению из AnotherModel к MyModel.

Знаете ли вы кого-нибудь, кто уже сделал это, или у вас есть какой-либо совет, чтобы сделать это простым способом? Я начал реализовывать это сам, но начинаю понимать, насколько сложно будет полностью реализовать поведение ManyToManyField: я все еще довольно новичок в Django, и для его правильного выполнения требуется некоторое знакомство с внутренней работой рамки.

Пока у меня есть это:

class InlineManyToManyField(models.CharField):
    __metaclass__ = models.SubfieldBase

    def __init__(self, other_model, *args, **kwargs):
        try:
            assert not other_model._meta.abstract, "{0} cannot define a relation with abstract class {0}".format(
                self.__class__.__name__, to._meta.object_name)
        except AttributeError:
            assert isinstance(other_model, basestring), "{0}({1}) is invalid. First parameter to InlineManyToManyField must be either a model, a model name, or the string self".format(
                self.__class__.__name__,unicode(other_model))
        kwargs['max_length'] = kwargs.get('max_length', 255)
        kwargs['blank'] = kwargs.get('blank', True)
        self.other_model = other_model
        self.token = kwargs.pop('token', ' ')
        super(InlineManyToManyField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value: return
        if isinstance(value, basestring):
            pk_list = value.split(self.token)
            pk_list.pop(0)
            pk_list.pop()
            value = self.other_model._default_manager.filter(pk__in=pk_list)
        return value

    def get_db_prep_value(self, value, connection, prepared=False):
        if not value: return
        pk_list = [item.pk for item in value]
        pk_list.sort()
        return self.token + self.token.join(unicode(pk) for pk in pk_list) + self.token

    def contribute_to_class(self, cls, name):
        super(InlineManyToManyField, self).contribute_to_class(cls, name)
        if isinstance(self.other_model, basestring):
            def resolve_through_model(field, model, cls):
                field.other_model = model
            add_lazy_relation(cls, self, self.other_model, resolve_through_model)

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
        if lookup_type in ('contains', 'icontains'):
            if isinstance(value, self.other_model):
                value = value.pk
            return ["%{0}{1}{0}%".format(self.token, connection.ops.prep_for_like_query(value))]
        return super(InlineManyToManyField, self).get_db_prep_lookup(
            lookup_type, value, connection=connection, prepared=prepared)

А вот как я это использую:

class MyModel(models.Model):
    anotherlist = InlineManyToManyField(AnotherModel, token=':')

Если таблица mymodel содержит строку с pk = 1 и anotherlist = ": 1: 2: 3:", я могу сделать это:

>>> m = MyModel.objects.get(pk=1)
>>> m.anotherlist
[<AnotherModel: 1>, <AnotherModel: 2>, <AnotherModel: 3>]
>>> MyModel.objects.filter(anotherlist__contains=2)
[<MyModel: 2>]

Что я хотел бы добавить, это обратное отношение: я бы хотел иметь mymodel_set в экземплярах AnotherModel, используя, например, приведенный выше код «содержащий», но мне сложно понять как все работает в django / db / models / fields / related.py:)

Итак, прежде чем я потратил несколько дней на работу над этим, вы наткнулись на что-нибудь подобное где-нибудь или вы уже написали что-то подобное?

Ответы [ 2 ]

1 голос
/ 28 июля 2011

Умм ... почему?и .. нет!Все популярные базы данных sql чертовски хорошо масштабируются.Вы не должны бояться внутренних соединений! .. Они в порядке!

То, что вы пытаетесь сделать, - это немного боли ... Оставляя это в стороне, если вы удалите экземпляр AnotherModel, что будетты сделаешь?Сканирование всей таблицы MyModel для идентификатора строки?Что происходит, когда выходит Django 5.6.6.1 и что-то не совместимо?Переписать?Также подумайте о модулях администратора и их последствиях / изменениях!

Не переусердствуйте с минимальной производительностью с самого начала!Исправьте, если не работает, проверьте, если нет!

Инвестируйте больше времени в архитектуру приложения, и все будет в порядке!

Чтобы ответить на ваш вопрос: Нет, я тоже думаю о чем-то подобном, найдите его, но ничего полезного не нашли!

0 голосов
/ 19 октября 2011

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

class AnotherModel(models.Model):
    // set up model here

    def mymodels(self):
        return MyModel.objects.filter(anotherlist__contains=self.pk)

Это должно работать.

...