2014-01-31 40 views
0

我試圖在Django中使用django-mptt創建一個菜單應用程序來創建嵌套的菜單項。在構建樹時,菜單項應按menu_order排序。保存MPTTModel時出現AttributeError

的問題是,每當我添加嵌套的菜單項,對它們重新排序並保存菜單,引發此錯誤:

'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,lftrght。這是造成get_next_sibling功能來查詢,其中tree_id__gt=4節點時的最後兩個節點都具有4

一個tree_id的MenuItem對象與每個菜單對象上內嵌管理員管理。可以使用Grappelli的可排序內聯對它們進行重新排序。新創建的子節點獲得比以下根節點更高的menu_order值似乎存在問題。

我使用Python 2.7.4,Django 1.5.5和django-mptt 0.6.0。

這是django-mptt中的錯誤還是我做錯了什麼?

回答

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() 
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 
相關問題