I want to create a new type of field for django models that is basically a ListOfStrings. So in your model code you would have the following:
我想为django模型创建一个新类型的字段,它基本上是一个ListOfStrings。因此,在您的模型代码中,您将拥有以下内容:
models.py:
models.py:
from django.db import models
class ListOfStringsField(???):
???
class myDjangoModelClass():
myName = models.CharField(max_length=64)
myFriends = ListOfStringsField() #
other.py:
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"]
How would you go about writing this field type, with the following stipulations?
您将如何编写此字段类型,并遵循以下规定?
- We don't want to do create a field which just crams all the strings together and separates them with a token in one field like this. It is a good solution in some cases, but we want to keep the string data normalized so tools other than django can query the data.
- 我们不想创建一个只将所有字符串拼接在一起的字段,并在一个字段中用这样的字符分隔它们。在某些情况下,这是一个很好的解决方案,但我们希望保持字符串数据的规范化,以便django以外的工具可以查询数据。
- The field should automatically create any secondary tables needed to store the string data.
- 该字段应自动创建存储字符串数据所需的任何辅助表。
- The secondary table should ideally have only one copy of each unique string. This is optional, but would be nice to have.
- 理想情况下,辅助表应该只包含每个唯一字符串的一个副本。这是可选的,但是很高兴。
Looking in the Django code it looks like I would want to do something similar to what ForeignKey is doing, but the documentation is sparse.
看看Django代码看起来我想要做类似于ForeignKey正在做的事情,但文档很稀疏。
This leads to the following questions:
这导致以下问题:
- Can this be done?
- 可以这样做吗?
- Has it been done (and if so where)?
- 它已经完成(如果是这样的话)?
- Is there any documentation on Django about how to extend and override their model classes, specifically their relationship classes? I have not seen a lot of documentation on that aspect of their code, but there is this.
- 关于如何扩展和覆盖他们的模型类,特别是他们的关系类,是否有关于Django的文档?我没有看到很多关于他们代码方面的文档,但是有这个。
This is comes from this question.
这是来自这个问题。
5 个解决方案
#1
5
What you have described sounds to me really similar to the tags.
So, why not using django tagging?
It works like a charm, you can install it independently from your application and its API is quite easy to use.
你所描述的对我来说听起来与标签非常相似。那么,为什么不使用django标记呢?它就像一个魅力,你可以独立于你的应用程序安装它,它的API非常容易使用。
#2
6
There's some very good documentation on creating custom fields here.
这里有一些关于创建自定义字段的非常好的文档。
However, I think you're overthinking this. It sounds like you actually just want a standard foreign key, but with the additional ability to retrieve all the elements as a single list. So the easiest thing would be to just use a ForeignKey, and define a get_myfield_as_list
method on the model:
但是,我认为你是在思考这个问题。听起来你实际上只是想要一个标准的外键,但具有将所有元素作为单个列表检索的额外功能。所以最简单的方法就是使用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))
Now calling get_my_friends_as_list()
on an instance of MyModel will return you a list of strings, as required.
现在,在MyModel实例上调用get_my_friends_as_list()将根据需要返回字符串列表。
#3
5
I also think you're going about this the wrong way. Trying to make a Django field create an ancillary database table is almost certainly the wrong approach. It would be very difficult to do, and would likely confuse third party developers if you are trying to make your solution generally useful.
我也认为你这是错误的做法。试图使Django字段创建一个辅助数据库表几乎肯定是错误的方法。如果你试图使你的解决方案普遍有用,那将很难做到,并且可能会使第三方开发人员感到困惑。
If you're trying to store a denormalized blob of data in a single column, I'd take an approach similar to the one you linked to, serializing the Python data structure and storing it in a TextField. If you want tools other than Django to be able to operate on the data then you can serialize to JSON (or some other format that has wide language support):
如果你试图在一个列中存储非规范化的blob数据,我会采用类似于你链接的方法,序列化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)
If you just want a django Manager-like descriptor that lets you operate on a list of strings associated with a model then you can manually create a join table and use a descriptor to manage the relationship. It's not exactly what you need, but this code should get you started.
如果您只想要一个类似django Manager的描述符,它允许您操作与模型关联的字符串列表,那么您可以手动创建连接表并使用描述符来管理关系。这不完全是你需要的,但这段代码应该让你开始。
#4
1
Thanks for all those that answered. Even if I didn't use your answer directly the examples and links got me going in the right direction.
感谢所有回答的人。即使我没有直接使用你的答案,例子和链接也让我朝着正确的方向前进。
I am not sure if this is production ready, but it appears to be working in all my tests so far.
我不确定这是否已准备就绪,但到目前为止它似乎在我的所有测试中都有效。
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)
Ultimately I think this would still be better if it were pushed deeper into the django code and worked more like the ManyToManyField or the ForeignKey.
最后,我认为如果它被深入到django代码中并且更像ManyToManyField或ForeignKey,它仍然会更好。
#1
5
What you have described sounds to me really similar to the tags.
So, why not using django tagging?
It works like a charm, you can install it independently from your application and its API is quite easy to use.
你所描述的对我来说听起来与标签非常相似。那么,为什么不使用django标记呢?它就像一个魅力,你可以独立于你的应用程序安装它,它的API非常容易使用。
#2
6
There's some very good documentation on creating custom fields here.
这里有一些关于创建自定义字段的非常好的文档。
However, I think you're overthinking this. It sounds like you actually just want a standard foreign key, but with the additional ability to retrieve all the elements as a single list. So the easiest thing would be to just use a ForeignKey, and define a get_myfield_as_list
method on the model:
但是,我认为你是在思考这个问题。听起来你实际上只是想要一个标准的外键,但具有将所有元素作为单个列表检索的额外功能。所以最简单的方法就是使用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))
Now calling get_my_friends_as_list()
on an instance of MyModel will return you a list of strings, as required.
现在,在MyModel实例上调用get_my_friends_as_list()将根据需要返回字符串列表。
#3
5
I also think you're going about this the wrong way. Trying to make a Django field create an ancillary database table is almost certainly the wrong approach. It would be very difficult to do, and would likely confuse third party developers if you are trying to make your solution generally useful.
我也认为你这是错误的做法。试图使Django字段创建一个辅助数据库表几乎肯定是错误的方法。如果你试图使你的解决方案普遍有用,那将很难做到,并且可能会使第三方开发人员感到困惑。
If you're trying to store a denormalized blob of data in a single column, I'd take an approach similar to the one you linked to, serializing the Python data structure and storing it in a TextField. If you want tools other than Django to be able to operate on the data then you can serialize to JSON (or some other format that has wide language support):
如果你试图在一个列中存储非规范化的blob数据,我会采用类似于你链接的方法,序列化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)
If you just want a django Manager-like descriptor that lets you operate on a list of strings associated with a model then you can manually create a join table and use a descriptor to manage the relationship. It's not exactly what you need, but this code should get you started.
如果您只想要一个类似django Manager的描述符,它允许您操作与模型关联的字符串列表,那么您可以手动创建连接表并使用描述符来管理关系。这不完全是你需要的,但这段代码应该让你开始。
#4
1
Thanks for all those that answered. Even if I didn't use your answer directly the examples and links got me going in the right direction.
感谢所有回答的人。即使我没有直接使用你的答案,例子和链接也让我朝着正确的方向前进。
I am not sure if this is production ready, but it appears to be working in all my tests so far.
我不确定这是否已准备就绪,但到目前为止它似乎在我的所有测试中都有效。
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)
Ultimately I think this would still be better if it were pushed deeper into the django code and worked more like the ManyToManyField or the ForeignKey.
最后,我认为如果它被深入到django代码中并且更像ManyToManyField或ForeignKey,它仍然会更好。