模型
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>