¿Cómo heredaría y anularía las clases del modelo de django para crear un listOfStringsField?

Quiero crear un nuevo tipo de campo para los modelos de django que es básicamente un ListOfStrings. Entonces en tu código de modelo tendrías lo siguiente:

modelos.py:

from django.db import models class ListOfStringsField(???): ??? class myDjangoModelClass(): myName = models.CharField(max_length=64) myFriends = ListOfStringsField() # 

otro.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"] 

¿Cómo haría para escribir este tipo de campo, con las siguientes estipulaciones?

  • No queremos crear un campo que simplemente agrupe todas las cadenas y las separa con un token en un campo como este . Es una buena solución en algunos casos, pero queremos mantener los datos de la cadena normalizados para que otras herramientas además de django puedan consultar los datos.
  • El campo debe crear automáticamente las tablas secundarias necesarias para almacenar los datos de la cadena.
  • La tabla secundaria debería tener idealmente solo una copia de cada cadena única. Esto es opcional, pero sería bueno tenerlo.

Buscando en el código de Django, parece que me gustaría hacer algo similar a lo que está haciendo ForeignKey, pero la documentación es escasa.

Esto lleva a las siguientes preguntas:

  • Se puede hacer esto?
  • ¿Se ha hecho (y si es así, dónde)?
  • ¿Existe alguna documentación en Django sobre cómo extender y anular sus clases modelo, específicamente sus clases de relación? No he visto mucha documentación sobre ese aspecto de su código, pero hay esto .

Esto viene de esta pregunta .

Lo que me has descrito me suena muy parecido a las tags.
Entonces, ¿por qué no usar el etiquetado django ?
Funciona como un encanto, puede instalarlo independientemente de su aplicación y su API es bastante fácil de usar.

Hay una muy buena documentación sobre la creación de campos personalizados aquí .

Sin embargo, creo que estás pensando demasiado en esto. Parece que en realidad solo quieres una clave externa estándar, pero con la capacidad adicional de recuperar todos los elementos como una sola lista. Así que lo más fácil sería usar una ForeignKey y definir un método get_myfield_as_list en el modelo:

 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)) 

Ahora llamar a get_my_friends_as_list() en una instancia de MyModel le devolverá una lista de cadenas, según sea necesario.

También creo que estás haciendo esto por el camino equivocado. Tratar de hacer que un campo Django cree una tabla de base de datos auxiliar es casi seguramente el enfoque equivocado. Sería muy difícil de hacer, y probablemente confundiría a los desarrolladores de terceros si está tratando de hacer que su solución sea generalmente útil.

Si está intentando almacenar un blob de datos desnormalizado en una sola columna, tomaría un enfoque similar al que vinculó, serializando la estructura de datos de Python y almacenándola en un TextField. Si desea que otras herramientas además de Django puedan operar con los datos, puede serializar a JSON (o algún otro formato que tenga soporte de lenguaje amplio):

 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) 

Si solo desea un descriptor similar a django Manager que le permita operar en una lista de cadenas asociadas con un modelo, puede crear manualmente una tabla de unión y usar un descriptor para administrar la relación. No es exactamente lo que necesitas, pero este código debería ayudarte a comenzar .

Gracias por todos los que respondieron. Incluso si no usé su respuesta directamente, los ejemplos y los enlaces me llevaron en la dirección correcta.

No estoy seguro de si esto está listo para la producción, pero hasta ahora parece estar funcionando en todas mis pruebas.

 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) 

En última instancia, creo que esto sería aún mejor si se introdujera más profundamente en el código django y funcionara más como ManyToManyField o ForeignKey.

Creo que lo que quieres es un campo de modelo personalizado .