2014-01-31 3 views
0

Я пытаюсь создать приложение меню в 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 или я что-то не так?

ответ

0

Я решил это, добавив еще один атрибут сортировки и восстановив меню, когда сохраняется набор форм. Это, вероятно, не самое оптимальное решение, каждое спасение теперь занимает несколько секунд.

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() 
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 
Смежные вопросы