django做自关联时遇到的各种问题

模型


from .BaseModel import BaseModel
from django.db import models
from django.urls import reverse

class Category(BaseModel):
    STATUS = [
        [0, '正常'],
        [9, '禁用'],
    ]
    MODULE = [
        ['single', '单页面'],
        ['article', '文章'],
        ['product', '产品'],
    ]
    name = models.CharField(max_length=255, verbose_name = "栏目名称")
    module = models.CharField(max_length=255, choices = MODULE, verbose_name = "栏目类型")
    parent = models.ForeignKey('self', null=True, blank=True, on_delete = models.SET_NULL, related_name='children', verbose_name = "父级")
    seo_title = models.CharField(max_length=255, null = True, verbose_name = "seo标题")
    seo_keywords = models.CharField(max_length=255, null = True, verbose_name = "seo关键字")
    seo_description = models.CharField(max_length=255, null = True, verbose_name = "seo描述")
    sort = models.IntegerField(default = 0, verbose_name = "排序")
    status = models.SmallIntegerField(default = 0, choices = STATUS, db_index = True, verbose_name = "状态")

    def get_absolute_url(self):
        return reverse('backend:category-index')
    
    # 调用时返回自身的属性,不然都是显示xx object
    def __str__(self):
        return self.name

    class Meta:
        db_table =  'category'

遇到问题1:

ForeignKey设置default无用,php写习惯了,总想给ForeignKey初始化为0,这里行不通了,同时要设置null=True, blank=True,因为顶级分类时没有关联的。

遇到问题2:

既然有parent,那么我在展示select的时候,就想展示成树形结构,所以我自定义了choice选择列表

'''
    组装options
    格式: choices = [[1, 'SH'], [2, 'BJ']]
    '''
    def ListOptions(self, data, pid = None, level = 1):
        options = []
        for obj in data:
            if obj['parent_id'] == pid:
                nextLevel = level + 1
                if level == 1:
                    nameStr = '┖'*level
                else:
                    nameStr = '╌'*level
                tree = []
                tree.append(obj['id'])
                tree.append(nameStr + obj['name'])
                options.append(tree)

                children    = self.ListOptions(data, obj['id'], nextLevel)
                if children:
                    options.extend(children)
                
        return options
def treeview():
    categories = Category.objects.all()
    treeView = TreeView()
    tree = treeView.ListOptions(categories.values())
    tree.insert(0, ['', '顶级栏目'])
    return tree
parent = ChoiceField(choices = treeview(), label = '父级')

这样保存的时候,前端验证就通不过,提示“请在列表选择一项”,我又改了下把

tree.insert(0, ['', '顶级栏目'])

改为

tree.insert(0, [0, '顶级栏目'])

保存又提示

Cannot assign "'0'": "Category.parent" must be a "Category" instance.

这个时因为验证时找不到主键为0的记录。

那我加个验证

def clean_parent(self):
        parent = int(self.cleaned_data['parent'])
        if parent > 0:
            return Category.objects.get(id = parent)

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return None

这样好像是可以了,可以添加数据了,但我在去添加数据时,发现下拉列表没数据,又改吧,又添加了

def __init__(self,*args,**kwargs):
        super(CategoryForm,self).__init__(*args,**kwargs)
        self.fields['parent'].choices = treeview()

这样可以实时加载了,但保存又出问题了

int() argument must be a string, a bytes-like object or a number, not 'NoneType'

这是因为choice列表choice在初始化就改变了,验证方式也改变了,所以下面的自定义验证也没有用了。

去掉自定义验证,并且把select顶级改为空。

tree.insert(0, ['', '顶级栏目'])

最终为

model:


from .BaseModel import BaseModel
from django.db import models
from django.urls import reverse

class Category(BaseModel):
    STATUS = [
        [0, '正常'],
        [9, '禁用'],
    ]
    MODULE = [
        ['single', '单页面'],
        ['article', '文章'],
        ['product', '产品'],
    ]
    name = models.CharField(max_length=255, verbose_name = "栏目名称")
    module = models.CharField(max_length=255, choices = MODULE, verbose_name = "栏目类型")
    parent = models.ForeignKey('self', null=True, blank=True, on_delete = models.SET_NULL, related_name='children', verbose_name = "父级")
    seo_title = models.CharField(max_length=255, null = True, verbose_name = "seo标题")
    seo_keywords = models.CharField(max_length=255, null = True, verbose_name = "seo关键字")
    seo_description = models.CharField(max_length=255, null = True, verbose_name = "seo描述")
    sort = models.IntegerField(default = 0, verbose_name = "排序")
    status = models.SmallIntegerField(default = 0, choices = STATUS, db_index = True, verbose_name = "状态")

    def get_absolute_url(self):
        return reverse('backend:category-index')
    
    # 调用时返回自身的属性,不然都是显示xx object
    def __str__(self):
        return self.name

    class Meta:
        db_table =  'category'

 

form:

from .BaseForm import BootstrapModelForm
from django.forms import widgets as widget
from django.forms.fields import ChoiceField
from backend.widgets.TyWidgets import TyRadioSelect
from common.models import Category
from backend.helps.TreeView import TreeView


def treeview():
    categories = Category.objects.all()
    treeView = TreeView()
    tree = treeView.ListOptions(categories.values())
    tree.insert(0, ['', '顶级栏目'])
    return tree


class CategoryForm(BootstrapModelForm):
    #parent = ChoiceField(choices = treeview(), label = '父级')
    #parent = ChoiceField(choices = treeview(), label = '父级')
    #print(parent)
    
    def __init__(self,*args,**kwargs):
        super(CategoryForm,self).__init__(*args,**kwargs)
        self.fields['parent'].choices = treeview()
    

    '''
    def clean_parent(self):
        parent = int(self.cleaned_data['parent'])
        if parent > 0:
            return Category.objects.get(id = parent)

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return None
    '''
    
    

    class Meta:
        model = Category
        fields = ['name', 'module', 'parent', 'seo_title', 'seo_keywords', 'seo_description', 'sort', 'status']
        widgets = {
            "status": TyRadioSelect(attrs={'class':'customer-form-radio'}),
            "seo_description": widget.Textarea(attrs={'class':'form-control', 'rows': 5}),
        }  

tree:

class TreeView:

    '''
    组装列表
    格式: list = [[id, name, ... ], [id, name, ...]]
    '''
    def ListLayerTree(self, data, pid = None, level = 1):
        trees = []
        for obj in data:
           if obj['parent_id'] == pid:
                nextLevel = level + 1
                tree = {}
                tree['id'] = obj['id']
                tree['parent_id'] = pid
                tree['name'] = obj['name']
                tree['level'] = level
                tree['sort'] = obj['sort']
                tree['status'] = obj['status']
                tree['module'] = obj['module']
                tree['children'] = []
                
                children    = self.ListLayerTree(data, obj['id'], nextLevel)
                if children:
                    tree['children'] = children
                trees.append(tree)
        return trees

    '''
    组装列表
    格式: list = [[id, name, ... ], [id, name, ...]]
    '''
    def ListTree(self, data, pid = None, level = 1):
        trees = []
        for obj in data:
           if obj['parent_id'] == pid:
                nextLevel = level + 1
                tree = {}
                tree['id'] = obj['id']
                tree['parent_id'] = pid
                tree['name'] = obj['name']
                tree['level'] = level
                tree['sort'] = obj['sort']
                tree['status'] = obj['status']
                tree['module'] = obj['module']
                trees.append(tree)
                children    = self.ListTree(data, obj['id'], nextLevel)
                if children:
                    trees.extend(children)
        return trees

    '''
    组装options
    格式: choices = [[1, 'SH'], [2, 'BJ']]
    '''
    def ListOptions(self, data, pid = None, level = 1):
        options = []
        for obj in data:
            if obj['parent_id'] == pid:
                nextLevel = level + 1
                if level == 1:
                    nameStr = '┖'*level
                else:
                    nameStr = '╌'*level
                tree = []
                tree.append(obj['id'])
                tree.append(nameStr + obj['name'])
                options.append(tree)

                children    = self.ListOptions(data, obj['id'], nextLevel)
                if children:
                    options.extend(children)
                
        return options

view:

from django.shortcuts import render, redirect
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from common.models.Category import Category
from backend.forms.CategoryForm import CategoryForm
from backend.helps.TreeView import TreeView

class CategoryIndexView(ListView):
    model = Category # 指定模型
    context_object_name = 'grid' # 默认object_list
    paginate_by = False # 每页显示数量 默认Paginator实例 page_obj
    #ordering = ['-id'] # 默认排序
    template_name = 'category/index.html'

    def get(self, request, *args, **kwargs):
        categories = Category.objects.order_by('sort').all()
        treeView = TreeView()
        queryset = treeView.ListTree(categories.values())
        return render(request, self.template_name, {'grid': queryset})

class CategoryCreateView(CreateView):
    model = Category # 指定模型
    template_name = 'category/create.html'
    form_class = CategoryForm


class CategoryUpdateView(UpdateView):
    model = Category # 指定模型
    template_name = 'category/update.html'
    form_class = CategoryForm

    #def get(self, request, *args, **kwargs):
    #    adv_positin = Category.objects.get(id = self.kwargs['pk'])
    #    #form = self.form_class(instance=adv_positin)
    #    form = CategoryForm(instance=adv_positin)
    #    return render(request, self.template_name, {'form': form})
    

class CategoryDeleteView(DeleteView):
    #model = Category
    #success_url = '/backend/category/index'

    def post(self, request, *args, **kwargs):
        category = Category.objects.get(id = self.kwargs['pk'])
        category.status = 0 if category.status == 8 else 8
        print(category.status)
        category.save()
        return redirect('/backend/category/index')
   

        

template:

<div class="card-header ty-main-title">
  <h3 class="card-title">
    栏目
  </h3>
  <a class="btn btn-default btn-btn1" href="/backend/category/index">列表</a>
</div>
<form class="form-horizontal" action="{{ request.get_full_path }}" method="post">
    {% csrf_token %}
   
  <div class="card-body">
    {{ form.as_div }}
  </div>
  <div class="card-footer">
    <input type="submit" class="btn btn-info" value="submit" />
  </div>
</form>

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值