Как бы вы унаследовали и переопределили классы модели django для создания listOfStringsField? - PullRequest
8 голосов
/ 14 июля 2009

Я хочу создать новый тип поля для моделей django, который в основном представляет собой ListOfStrings. Таким образом, в коде вашей модели вы должны иметь следующее:

models.py:

from django.db import models

class ListOfStringsField(???):
    ???

class myDjangoModelClass():
    myName = models.CharField(max_length=64)
    myFriends = ListOfStringsField() # 

other.py:

myclass = myDjangoModelClass()
myclass.myName = "bob"
myclass.myFriends = ["me", "myself", "and I"]

myclass.save()

id = myclass.id

loadedmyclass = myDjangoModelClass.objects.filter(id__exact=id)

myFriendsList = loadedclass.myFriends
# myFriendsList is a list and should equal ["me", "myself", "and I"]

Как вы будете писать этот тип поля со следующими условиями?

  • Мы не хотим создавать поле, которое просто объединяет все строки вместе и разделяет их токеном в одном поле, например this . В некоторых случаях это хорошее решение, но мы хотим, чтобы строковые данные были нормализованы, чтобы инструменты, отличные от django, могли запрашивать данные.
  • Поле должно автоматически создавать любые дополнительные таблицы, необходимые для хранения строковых данных.
  • Вторичная таблица в идеале должна иметь только одну копию каждой уникальной строки. Это необязательно, но было бы неплохо иметь.

Глядя в код Django, похоже, что я хотел бы сделать что-то похожее на то, что делает ForeignKey, но документация скудная.

Это приводит к следующим вопросам:

  • Можно ли это сделать?
  • Было ли это сделано (и если да, то где)?
  • Есть ли какая-либо документация по Django о том, как расширить и переопределить их классы моделей, в частности классы отношений? Я не видел много документации по этому аспекту их кода, но есть это .

Это из этого вопроса .

Ответы [ 5 ]

6 голосов
/ 14 июля 2009

Здесь есть очень хорошая документация по созданию пользовательских полей здесь .

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

class Friends(model.Model):
    name = models.CharField(max_length=100)
    my_items = models.ForeignKey(MyModel)

class MyModel(models.Model):
    ...

    def get_my_friends_as_list(self):
        return ', '.join(self.friends_set.values_list('name', flat=True))

Теперь вызов get_my_friends_as_list() для экземпляра MyModel вернет вам список необходимых строк.

5 голосов
/ 20 июля 2009

То, что вы описали, звучит для меня очень похоже на теги.
Итак, почему бы не использовать django tagging ?
Он работает как шарм, вы можете установить его независимо от вашего приложения, а его API довольно прост в использовании.

5 голосов
/ 15 июля 2009

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

Если вы пытаетесь сохранить денормализованный блоб данных в одном столбце, я бы выбрал подход, аналогичный тому, с которым вы связались, - сериализацию структуры данных Python и сохранение ее в TextField. Если вы хотите, чтобы инструменты, отличные от Django, могли работать с данными, то вы можете сериализовать в JSON (или другой формат, который имеет широкую языковую поддержку):

from django.db import models
from django.utils import simplejson

class JSONDataField(models.TextField):
    __metaclass__ = models.SubfieldBase

    def to_python(self, value):
        if value is None: 
            return None
        if not isinstance(value, basestring): 
            return value
        return simplejson.loads(value)

    def get_db_prep_save(self, value):
        if value is None: 
            return None
        return simplejson.dumps(value)

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

1 голос
/ 15 июля 2009

Спасибо всем, кто ответил. Даже если я не использовал ваш ответ напрямую, примеры и ссылки заставили меня двигаться в правильном направлении.

Я не уверен, готов ли он к производству, но, похоже, он работает во всех моих тестах.

class ListValueDescriptor(object):

   def __init__(self, lvd_parent, lvd_model_name, lvd_value_type, lvd_unique, **kwargs):
      """
         This descriptor object acts like a django field, but it will accept
         a list of values, instead a single value.
         For example:
            # define our model
            class Person(models.Model):
               name = models.CharField(max_length=120)
               friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)

            # Later in the code we can do this
            p = Person("John")
            p.save() # we have to have an id
            p.friends = ["Jerry", "Jimmy", "Jamail"]
            ...
            p = Person.objects.get(name="John")
            friends = p.friends
            # and now friends is a list.
         lvd_parent - The name of our parent class
         lvd_model_name - The name of our new model
         lvd_value_type - The value type of the value in our new model
                        This has to be the name of one of the valid django
                        model field types such as 'CharField', 'FloatField',
                        or a valid custom field name.
         lvd_unique - Set this to true if you want the values in the list to
                     be unique in the table they are stored in. For
                     example if you are storing a list of strings and
                     the strings are always "foo", "bar", and "baz", your
                     data table would only have those three strings listed in
                     it in the database.
         kwargs - These are passed to the value field.
      """
      self.related_set_name = lvd_model_name.lower() + "_set"
      self.model_name = lvd_model_name
      self.parent = lvd_parent
      self.unique = lvd_unique

      # only set this to true if they have not already set it.
      # this helps speed up the searchs when unique is true.
      kwargs['db_index'] = kwargs.get('db_index', True)

      filter = ["lvd_parent", "lvd_model_name", "lvd_value_type", "lvd_unique"]

      evalStr = """class %s (models.Model):\n""" % (self.model_name)
      evalStr += """    value = models.%s(""" % (lvd_value_type)
      evalStr += self._params_from_kwargs(filter, **kwargs) 
      evalStr += ")\n"
      if self.unique:
         evalStr += """    parent = models.ManyToManyField('%s')\n""" % (self.parent)
      else:
         evalStr += """    parent = models.ForeignKey('%s')\n""" % (self.parent)
      evalStr += "\n"
      evalStr += """self.innerClass = %s\n""" % (self.model_name)

      print evalStr

      exec (evalStr) # build the inner class

   def __get__(self, instance, owner):
      value_set = instance.__getattribute__(self.related_set_name)
      l = []
      for x in value_set.all():
         l.append(x.value)

      return l

   def __set__(self, instance, values):
      value_set = instance.__getattribute__(self.related_set_name)
      for x in values:
         value_set.add(self._get_or_create_value(x))

   def __delete__(self, instance):
      pass # I should probably try and do something here.


   def _get_or_create_value(self, x):
      if self.unique:
         # Try and find an existing value
         try:
            return self.innerClass.objects.get(value=x)
         except django.core.exceptions.ObjectDoesNotExist:
            pass

      v = self.innerClass(value=x)
      v.save() # we have to save to create the id.
      return v

   def _params_from_kwargs(self, filter, **kwargs):
      """Given a dictionary of arguments, build a string which 
      represents it as a parameter list, and filter out any
      keywords in filter."""
      params = ""
      for key in kwargs:
         if key not in filter:
            value = kwargs[key]
            params += "%s=%s, " % (key, value.__repr__())

      return params[:-2] # chop off the last ', '

class Person(models.Model):
   name = models.CharField(max_length=120)
   friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)

В конечном счете, я думаю, что было бы еще лучше, если бы он углублялся в код django и работал больше как ManyToManyField или ForeignKey.

0 голосов
/ 14 июля 2009
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...