Я пытаюсь создать приложение меню в Django, используя django-mptt, чтобы создать вложенные элементы меню. Элементы меню должны быть упорядочены по menu_order
, когда дерево построено.AttributeError при сохранении MPTTModel
Проблема заключается в том, что всякий раз, когда я добавить вложенные пункты меню, изменить их порядок и сохранить в меню, возникает эта ошибка:
'NoneType' object has no attribute 'tree_id'
Чтобы иметь возможность сохранить в меню я должен либо вручную восстановить дерево из Оболочка Django, которая не всегда помогает, или удалить родительское отношение из дочерних элементов.
При удалении order_insertion_by = ['menu_order']
из модели MenuItem все (кроме заказа) работает по назначению.
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
возвращает None, даже если есть следующий родственный.
При переупорядочивании двух последних пунктов меню иногда я получаю два корневых узла с теми же tree_id
, lft
и rght
значениями. Это вызывает функцию get_next_sibling
для запроса узла, где tree_id__gt=4
когда последние два узла оба имеют tree_id
из 4.
Объекты MenuItem управляются с инлайн администратора на каждом объекте меню. Их можно переупорядочить, используя сортируемые ряды Grappelli. Кажется, что проблема с вновь созданными дочерними узлами получает более высокое значение menu_order, чем следующие корневые узлы.
Я использую Python 2.7.4, Django 1.5.5 и django-mptt 0.6.0.
Является ли это ошибкой в django-mptt или я что-то не так?