我写了以下自定义字段:
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models import signals
from sitetree.models import Tree, TreeItem
from south.modelsinspector import introspector
class AutoTreeItemField(models.ForeignKey):
def __init__(self, *args, **kwargs):
super(AutoTreeItemField, self).__init__(TreeItem, null=True)
self.date_field = kwargs['date_field']
self.__should_appear = kwargs['should_appear']
self.year_menu_item_url = kwargs['year_menu_item_url']
self.month_menu_item_url = kwargs['month_menu_item_url']
self.item_menu_item_url = kwargs['item_menu_item_url']
self.year_format = kwargs.get('year_format', '%Y')
self.month_format = kwargs.get('month_format', '%B')
self.inmenu = kwargs.get('inmenu', True)
self.inbreadcrumbs = kwargs.get('inbreadcrumbs', True)
self.insitetree = kwargs.get('insitetree', True)
self.item_title = kwargs.get('title', 'title')
def contribute_to_class(self, cls, name):
super(AutoTreeItemField, self).contribute_to_class(cls, name)
# Make this object the descriptor for field access.
setattr(cls, self.name, self)
self.tree = self.__get_or_create_tree(cls._meta.verbose_name_plural.lower())
# Delete menu item after the instance is deleted
signals.post_delete.connect(self.__delete, cls, True)
def pre_save(self, model_instance, add):
super(AutoTreeItemField, self).pre_save(model_instance, add)
if self.should_appear(model_instance):
year_menu_item = self.__get_or_create_year_tree_item(model_instance)
month_menu_item = self.__get_or_create_month_tree_item(model_instance, year_menu_item)
menu_item = self.__save_menu_item(model_instance, month_menu_item)
setattr(model_instance, self.get_attname(), menu_item.id)
return menu_item.id
else:
self.__delete_orphans(model_instance)
return None
def __delete(self, **kwargs):
self.__delete_orphans(kwargs['instace'])
def __get_or_create_tree(self, alias):
try:
return Tree.objects.get(alias=alias)
except Tree.DoesNotExist:
return Tree.objects.create(alias=alias)
def should_appear(self, instance):
if isinstance(self.__should_appear, str):
return getattr(instance, self.__should_appear)
elif callable(self.__should_appear):
return self.__should_appear()
def south_field_triple(self):
"""Returns a suitable description of this field for South."""
args, kwargs = introspector(self)
kwargs.update({'date_field': 'None'})
return ('website.blog.fields.AutoTreeItemField', args, kwargs)
def __get_or_create_year_tree_item(self, model_instance):
year = self.__get_year(model_instance)
try:
return TreeItem.objects.get(title=year, tree=self.tree)
except TreeItem.DoesNotExist:
return TreeItem.objects.create(title=year,
url=reverse(self.year_menu_item_url, args = [year]),
tree=self.tree,
inmenu=self.inmenu,
inbreadcrumbs=self.inbreadcrumbs,
insitetree=self.insitetree,
parent=None)
def __get_year(self, model_instance):
return getattr(model_instance, self.date_field).strftime(self.year_format)
def __get_or_create_month_tree_item(self, model_instance, year_menu_item):
month = self.__get_month(model_instance)
try:
return TreeItem.objects.get(title=month, tree=self.tree, parent=year_menu_item)
except TreeItem.DoesNotExist:
return TreeItem.objects.create(title=month,
url=reverse(self.month_menu_item_url, args = [getattr(model_instance, self.date_field).year, getattr(model_instance, self.date_field).month]),
tree=self.tree,
inmenu=self.inmenu,
inbreadcrumbs=self.inbreadcrumbs,
insitetree=self.insitetree,
parent=year_menu_item)
def __get_month(self, model_instance):
return getattr(model_instance, self.date_field).strftime(self.month_format)
def __save_menu_item(self, model_instance, month_tree_item):
try:
item = self.__get_menu_item(model_instance)
item.title = getattr(model_instance, self.item_title)
item.url = model_instance.get_absolute_url()
item.parent = month_tree_item
return item
except TreeItem.DoesNotExist:
return TreeItem.objects.create(title=getattr(model_instance, self.item_title),
url=model_instance.get_absolute_url(),
tree=self.tree,
inmenu=self.inmenu,
inbreadcrumbs=self.inbreadcrumbs,
insitetree=self.insitetree,
parent=month_tree_item)
def __get_year_tree_item(self, model_instance):
year = self.__get_year(model_instance)
return TreeItem.objects.filter(title=year, tree=self.tree)
def __delete_orphans(self, model_instance):
menu_item = self.__get_menu_item(model_instance)
try:
if menu_item is not None:
month_menu_item = menu_item.parent
menu_item.delete()
if TreeItem.objects.filter(parent=month_menu_item, parent__parent=self.__get_year_tree_item(model_instance), tree=self.tree).count():
year_menu_item = month_menu_item.parent
month_menu_item.delete()
if TreeItem.objects.filter(parent=year_menu_item, tree=self.tree).count() == 0:
year_menu_item.delete()
except TreeItem.DoesNotExist:
pass
def __get_menu_item(self, model_instance):
menu_item_id = getattr(model_instance, self.get_attname())
return TreeItem.objects.get(id=menu_item_id)
但是当我试图保存它时,我得到了:
'AutoTreeItemField' object has no attribute '_meta'
这是完整的堆栈跟踪:
Environment:
Request Method: POST
Request URL: http://127.0.0.1:8000/admin/blog/draftpost/add/
Django Version: 1.3
Python Version: 2.7.1
Installed Applications:
['django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django.contrib.sites',
'django.contrib.flatpages',
'tagging',
'reversion',
'south',
'sitetree',
'dojango',
'disqus',
'website.blog',
'website.cms']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'dojango.middleware.DojoCollector',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware')
Traceback:
File "C:\Python27\lib\site-packages\django\core\handlers\base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "C:\Python27\lib\site-packages\django\contrib\admin\options.py" in wrapper
307. return self.admin_site.admin_view(view)(*args, **kwargs)
File "C:\Python27\lib\site-packages\django\utils\decorators.py" in _wrapped_view
93. response = view_func(request, *args, **kwargs)
File "C:\Python27\lib\site-packages\django\views\decorators\cache.py" in _wrapped_view_func
79. response = view_func(request, *args, **kwargs)
File "C:\Python27\lib\site-packages\django\contrib\admin\sites.py" in inner
197. return view(request, *args, **kwargs)
File "C:\Python27\lib\site-packages\django\db\transaction.py" in inner
217. res = func(*args, **kwargs)
File "C:\Python27\lib\site-packages\reversion\revisions.py" in _create_on_success
352. self.end()
File "C:\Python27\lib\site-packages\reversion\revisions.py" in end
274. revision_set = self.follow_relationships(models)
File "C:\Python27\lib\site-packages\reversion\revisions.py" in follow_relationships
244. map(_follow_relationships, object_dict)
File "C:\Python27\lib\site-packages\reversion\revisions.py" in _follow_relationships
243. _follow_relationships(parent_obj)
File "C:\Python27\lib\site-packages\reversion\revisions.py" in _follow_relationships
213. result_dict[obj] = self.get_version_data(obj, VERSION_CHANGE)
File "C:\Python27\lib\site-packages\reversion\revisions.py" in get_version_data
254. serialized_data = serializers.serialize(registration_info.format, [obj], fields=registration_info.fields)
File "C:\Python27\lib\site-packages\django\core\serializers\__init__.py" in serialize
91. s.serialize(queryset, **options)
File "C:\Python27\lib\site-packages\django\core\serializers\base.py" in serialize
48. self.handle_fk_field(obj, field)
File "C:\Python27\lib\site-packages\django\core\serializers\python.py" in handle_fk_field
53. if field.rel.field_name == related._meta.pk.name:
Exception Type: AttributeError at /admin/blog/draftpost/add/
Exception Value: 'AutoTreeItemField' object has no attribute '_meta'
任何人都可以向我展示这是怎么回事?可以采取哪些措施来解决这个问题呢? 奇怪的是,它发生在事务结束后,它将我需要的所有内容保存到数据库中。
编辑:
该错误是由尝试将我的实例序列化为json的reversion引起的。但是,这仍然是我的问题,因为_meta属性应该在那里,而不是。怎么解决这个问题?
答案 0 :(得分:1)
我想到的第一件事是在以下评论中:
# Make this object the descriptor for field access.
setattr(cls, self.name, self)
您声明您的字段实例应该充当描述符,但它不实现描述符协议所需的__get__
和__set__
。请注意,ForeignKey
本身也不会实现这些,而是使用ReverseSingleRelatedObjectDescriptor
。
现在,在您的情况下发生的情况是序列化程序看到该字段是ForeignKey,因此它期望模型实例位于模型实例上的名称中。它调用getattr(obj, field.name)
,它通常会调用ReverseSingleRelatedObjectDescriptor
的{{1}}方法并返回相关模型的实例。在您的情况下,特定__get__
调用会返回您的字段实例(因为它缺少getattr
)而不是__get__
实例,这就是出错的时候。
所以,为了帮助解决你的问题,我会选择一种完全不同的方法。从您的代码中可以看出,您需要的只是一些方便的方法,这些方法可以从包含TreeItem
的实例访问,这些实例会自动使用AutoTreeItemField
指向的相关TreeItem
。
我宁愿将字段保持为常规ForeignKey
,而是将便捷方法附加到模型类。如果你需要在几个模型中使用它们,你总是可以创建一个mixin,如果你想要它非常整洁,你可以创建一个自定义的ForeignKey
子类,它只会覆盖它的ForeignKey
以自动添加mixin到contribute_to_class
。
如果您需要cls.__bases__
位于不同的字段名称中,您甚至可以通过在字段名称前加上它们来动态创建方法的名称,并将它们调整为提供字段名称一个参数。可能性几乎是无穷无尽的。
编辑:这可以通过创建一个mixin类来完成,该类包含名称前缀为ForeignKey
的所有方便的额外方法。您还必须让他们接受所有额外参数,例如_TREEITEM
,year_format
,month_format
等作为关键字参数。将这些保存在inmenu
子类'ForeignKey
方法中,就像当前一样,然后在其__init__
中执行以下操作:
contribute_to_class
此外,在from django.utils.functional import curry
...
def contribute_to_class(self, cls, name):
super(...).contribute_to_class(cls, name)
cls.__bases__ += (TreeItemMixin,)
# Now curry all your handy methods to pass your extra parameters to them.
extra_kwargs = {
'year_format': self.year_format,
# ...
}
setattr(cls, '%s_get_year' % (self.name,), curry(cls._TREEITEM_get_year, **extra_kwargs))
# Repeat the previous line for all your methods.
子类中,您可以保留ForeignKey
和信号处理程序,只需记住在模型实例上调用相应的方法即可。当您想要在视图或业务逻辑中调用它们时,您可以在模型实例上使用方便的curried自动生成别名,例如pre_save
。
另一种可能性是在obj.myfield_get_year()
中实现描述符协议以返回有效的AutoTreeItemField
实例,但在这种情况下,您将无法访问其额外的方法,因为描述符将返回模型实例而不是自己。
答案 1 :(得分:0)
我相信错误在这里:
def __init__(self, *args, **kwargs):
super(AutoTreeItemField, self).__init__(TreeItem, null=True)
你应该通过其他(未命名的)args:
def __init__(self, *args, **kwargs):
if not args:
args = [TreeItem]
options = {'null': True}
options.update(kwargs)
super(AutoTreeItemField, self).__init__(*args, **options)
修改强>
我可能找到了一些东西。在正常的FK中,假设它被声明为
other = models.ForeignKey(OtherClass)
instance.other的类型是OtherClass,而不是ForeignKey。 OtherClass实例具有_meta
属性,因为它们是模型。
在你的情况下,似乎(来自django.core.serializers.python
traceback)它正在尝试序列化AutoTreeItemField实例(当然不是可序列化的)而不是相关的TreeItem。
来自django.core.serializers.python
模块,l。 48:
def handle_fk_field(self, obj, field):
related = getattr(obj, field.name)
你有使用contrib_to_class方法:
setattr(cls, self.name, self)
在ForeignKey的contrib_to_class方法中:
setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
鉴于ForeignKey实例不是RelatedObjectDescriptor实例,所有django序列化程序都希望找到后者,即实际模型的实例(或在被称为__get__
方法时返回实例)。
最后,您可能会从方法中删除setattr(cls ...)行。