我正在尝试使用django-mptt在Django中构建一个菜单应用程序来创建嵌套的菜单项。构建树时,菜单项应按menu_order
排序。
问题在于,无论何时添加嵌套菜单项,重新排序并保存菜单,都会引发此错误:
'NoneType' object has no attribute 'tree_id'
为了能够保存菜单,我必须从Django shell手动重建树,这并不总是有用,或者从子项删除父关系。
从MenuItem模型中删除order_insertion_by = ['menu_order']
时,所有内容(排序除外)均按预期工作。
models.py:
class Menu(models.Model):
POSITIONS = Choices(('header', _('Header')), ('footer', _('Footer')))
title = models.CharField(max_length=255, default='')
position = models.SlugField(choices=POSITIONS, max_length=64, default='')
def save(self, *args, **kwargs):
MenuItem.objects.rebuild()
super(Menu, self).save(*args, **kwargs)
class MenuItem(MPTTModel):
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
linked_object = generic.GenericForeignKey()
menu = models.ForeignKey('Menu', related_name='menu_items')
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
menu_order = models.PositiveSmallIntegerField(default=0)
class MPTTMeta:
order_insertion_by = ['menu_order']
admin.py:
class MenuItemInline(admin.StackedInline):
model = MenuItem
extra = 0
sortable_field_name = 'menu_order'
autocomplete_lookup_fields = {
'generic': [['content_type', 'object_id']]
}
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
field = super(MenuItemInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
if db_field.name == 'parent':
if request._obj_ is not None:
field.queryset = field.queryset.filter(menu=request._obj_)
else:
field.queryset = field.queryset.none()
return field
class MenuAdmin(admin.ModelAdmin):
inlines = (MenuItemInline,)
def get_form(self, request, obj=None, **kwargs):
request._obj_ = obj
return super(MenuAdmin, self).get_form(request, obj, **kwargs)
admin.site.register(Menu, MenuAdmin)
追溯:
Traceback:
File "/.../django/core/handlers/base.py" in get_response
115. response = callback(request, *callback_args, **callback_kwargs)
File "/.../django/contrib/admin/options.py" in wrapper
372. return self.admin_site.admin_view(view)(*args, **kwargs)
File "/.../django/utils/decorators.py" in _wrapped_view
91. response = view_func(request, *args, **kwargs)
File "/.../django/views/decorators/cache.py" in _wrapped_view_func
89. response = view_func(request, *args, **kwargs)
File "/.../django/contrib/admin/sites.py" in inner
202. return view(request, *args, **kwargs)
File "/.../django/utils/decorators.py" in _wrapper
25. return bound_func(*args, **kwargs)
File "/.../django/utils/decorators.py" in _wrapped_view
91. response = view_func(request, *args, **kwargs)
File "/.../django/utils/decorators.py" in bound_func
21. return func(self, *args2, **kwargs2)
File "/.../django/db/transaction.py" in inner
223. return func(*args, **kwargs)
File "/.../django/contrib/admin/options.py" in change_view
1106. self.save_related(request, form, formsets, True)
File "/.../django/contrib/admin/options.py" in save_related
764. self.save_formset(request, form, formset, change=change)
File "/.../django/contrib/admin/options.py" in save_formset
752. formset.save()
File "/.../django/forms/models.py" in save
514. return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "/.../django/forms/models.py" in save_existing_objects
634. saved_instances.append(self.save_existing(form, obj, commit=commit))
File "/.../django/forms/models.py" in save_existing
502. return form.save(commit=commit)
File "/.../django/forms/models.py" in save
370. fail_message, commit, construct=False)
File "/.../django/forms/models.py" in save_instance
87. instance.save()
File "/.../mptt/models.py" in save
794. self._tree_manager._move_node(self, rightmost_sibling, 'right', save=False)
File "/.../mptt/managers.py" in _move_node
414. self._make_sibling_of_root_node(node, target, position)
File "/.../mptt/managers.py" in _make_sibling_of_root_node
769. new_tree_id = getattr(right_sibling, self.tree_id_attr)
Exception Type: AttributeError at /admin/menus/menu/2/
Exception Value: 'NoneType' object has no attribute 'tree_id'
'NoneType'指的是right_sibling
,即无。
原因追溯到上面的三行,其中设置了right_sibling
:
right_sibling = target.get_next_sibling()
get_next_sibling
即使有下一个兄弟,也会返回无。
重新排序最后两个菜单项时,我最终会得到两个具有相同tree_id
,lft
和rght
值的根节点。这导致get_next_sibling
函数在最后两个节点的tree_id__gt=4
均为4时查询tree_id
的节点。
MenuItem对象在每个Menu对象上使用内联管理进行管理。它们可以使用Grappelli的可排序内联重新排序。新创建的子节点获得比以下根节点更高的menu_order值似乎存在问题。
我使用的是Python 2.7.4,Django 1.5.5和django-mptt 0.6.0。
这是django-mptt中的错误还是我做错了什么?
答案 0 :(得分:0)
我通过添加另一个排序属性并在保存formset时重建菜单来解决这个问题。这可能不是最佳解决方案,现在每次保存都需要几秒钟。
models.py:
class MenuItem:
parent_order = models.PositiveSmallIntegerField(default=0)
def save(self, **kwargs):
if self.parent:
self.parent_order = self.parent.menu_order
super(MenuItem, self).save(**kwargs)
class MPTTMeta:
order_insertion_by = ['parent_order', 'menu_order']
admin.py:
class MenuAdmin(MarketRelatedAdmin):
def save_formset(self, request, form, formset, change):
formset.save()
MenuItem.objects.rebuild()
答案 1 :(得分:0)
我通过在TreeManager中添加两行来解决这个问题。
def _make_sibling_of_root_node(self, node, target, position):
...
elif position == 'right':
if target_tree_id > tree_id:
new_tree_id = target_tree_id
lower_bound, upper_bound = tree_id, target_tree_id
shift = -1
else:
right_sibling = target.get_next_sibling()
if node == right_sibling:
return
# Addition
if not right_sibling:
return
# end of addition
new_tree_id = getattr(right_sibling, self.tree_id_attr)
lower_bound, upper_bound = new_tree_id, tree_id
shift = 1