python - tutorial - tipos de campos django
¿Cómo heredarías e invalidarías las clases del modelo django para crear un listOfStringsField? (5)
Quiero crear un nuevo tipo de campo para los modelos django que es básicamente un ListOfStrings. Entonces, en su código modelo tendría lo siguiente:
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"]
¿Cómo haría para escribir este tipo de campo, con las siguientes estipulaciones?
- No queremos crear un campo que agrupe todas las cadenas y las separe con un token en un campo como este . En algunos casos, es una buena solución, pero queremos mantener los datos de cadena normalizados para que las herramientas que no sean django puedan consultar los datos.
- El campo debe crear automáticamente las tablas secundarias necesarias para almacenar los datos de cadena.
- La tabla secundaria idealmente debería tener solo una copia de cada cadena única. Esto es opcional, pero sería bueno tenerlo.
Al mirar el código de Django, parece que quisiera hacer algo similar a lo que hace ForeignKey, pero la documentación es escasa.
Esto lleva a las siguientes preguntas:
- Se puede hacer esto?
- ¿Ha sido hecho (y si es así en dónde)?
- ¿Hay alguna documentación sobre 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 .
Creo que lo que quieres es un campo de modelo personalizado .
Hay muy buena documentación sobre cómo crear campos personalizados aquí .
Sin embargo, creo que estás pensando demasiado en esto. Parece que en realidad solo quiere una clave externa estándar, pero con la capacidad adicional de recuperar todos los elementos como una lista única. Entonces, 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 llamando 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 de la manera incorrecta. Tratar de hacer que un campo de 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 su solución generalmente útil.
Si está intentando almacenar una masa de datos desnormalizada en una sola columna, tomaría un enfoque similar al que usó para enlazar, serializando la estructura de datos de Python y almacenándola en un TextField. Si desea que otras herramientas que no sean Django puedan operar con los datos, puede serializar a JSON (u otro formato que tenga amplio soporte de idiomas):
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, entonces puede crear manualmente una tabla de unión y usar un descriptor para administrar la relación. No es exactamente lo que necesita, pero este código debería ayudarlo a comenzar .
Gracias a todos los que respondieron. Incluso si no usé su respuesta directamente, los ejemplos y enlaces me ayudaron a avanzar en la dirección correcta.
No estoy seguro si esto está listo para la producción, pero parece estar funcionando en todas mis pruebas hasta el momento.
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 profundizara en el código django y funcionara más como ManyToManyField o ForeignKey.
Lo que has descrito me suena muy similar a las etiquetas.
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.