对象删除功能
基础配置
创建删除对象模板
在djadmin应用下templates/djadmin中新建table_delete.html
{% extends 'djadmin/base.html' %}
{% load djadmin_tags %}
{% block title %}
数据表删除 - 后台管理
{% endblock %}
{% block content %}
<h2 class="page-header">{{ model_name }}</h2>
<div>
删除<a href="{% url 'djadmin:table_change' app_name model_name obj.id %}">{{ obj }}</a>
</div>
{% endblock %}
创建删除对象视图
在djadmin应用的views.py视图文件中,增加删除数据视图
# 数据删除
@login_required
def table_delete(request, app_name, model_name, obj_id):
admin_class = site.enable_admins[app_name][model_name]
obj = admin_class.model.objects.get(id=obj_id)
return render(request, 'djadmin/table_delete.html', locals())
创建删除对象url
修改djadmin中的urls.py,增加删除路由
from django.urls import path
from djadmin.views import index, user_login, user_logout, table_detail, table_change, table_add, table_delete
app_name = 'djadmin'
urlpatterns = [
path('login/', user_login, name='user_login'), # djAdmin登录
path('logout/', user_logout, name='user_logout'), # djAdmin登出
path('', index, name='index'), # djAdmin主页
path('<str:app_name>/<str:model_name>/', table_detail, name='table_detail'), # 数据表详情
path('<str:app_name>/<str:model_name>/<int:obj_id>/change/', table_change, name='table_change'), # 数据表修改
path('<str:app_name>/<str:model_name>/add/', table_add, name='table_add'), # 数据增加
path('<str:app_name>/<str:model_name>/<int:obj_id>/delete/', table_delete, name='table_delete'), # 数据表删除
]
配置列表页删除跳转模板
在table_detal.html中增加删除的链接<td><a href="{% url 'djadmin:table_delete' app_name model_name obj.id %}">删除</a></td>
<table class="table table-striped">
<thead>
<tr>
{% if admin_class.list_display %}
{% for display_field in admin_class.list_display %}
<th>
<a href="?_order={% get_sorted_data display_field current_order_field forloop.counter0 %}{% render_filter_args admin_class %}">
{{ display_field }} {% get_sorted_arrow display_field current_order_field forloop.counter0 %}
</a>
</th>
{% endfor %}
{% else %}
{% build_table_head_name admin_class %}
{% endif %}
<th>操作</th>
<!--
{% build_table_head_name admin_class %}
-->
</tr>
</thead>
<tbody>
{% for obj in queryset %}
<tr>
{% build_table_body obj admin_class %}
<td><a href="{% url 'djadmin:table_delete' app_name model_name obj.id %}">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
image.png
当点击这个删除会跳转到 http://127.0.0.1:8000/djadmin/crm/customerinfo/1/delete/
image.png配置编辑页删除跳转模板
修改table_edit.html模板,增加删除按钮。但要求当为数据增加时,不能显示删除按钮
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button type="submit" class="btn btn-primary">提交</button>
</div>
{% url 'djadmin:table_add' app_name model_name as table_add %}
{% if request.path != table_add %}
<div class="col-sm-offset-2 col-sm-4">
<a class="btn btn-danger" href="{% url 'djadmin:table_delete' app_name model_name obj_id %}">删除</a>
</div>
{% endif %}
</div>
image.png
显示要被删除的对象
获取反向关联方法
from crm.models import CustomerInfo
obj = CustomerInfo.objects.first()
obj._meta
# <Options for CustomerInfo>
obj._meta.related_objects
# (<ManyToOneRel: crm.customerinfo>, <ManyToOneRel: crm.customerfollowup>, <OneToOneRel: crm.student>)
obj._meta.related_objects[1]
# <ManyToOneRel: crm.customerfollowup>
# 反向获取表明
obj._meta.related_objects[1].name
# 'customerfollowup'
obj._meta.related_objects[0].name
# 'customerinfo'
obj._meta.related_objects[2].name
# 'student'
# 获取类型
obj._meta.related_objects[2].get_internal_type()
# 'OneToOneField'
obj._meta.related_objects[1].get_internal_type()
# 'ForeignKey'
obj._meta.related_objects[0].get_internal_type()
# 'ForeignKey'
Student.objects.first()
<Student: 测试>
# fs = Student.objects.first()
fs
# <Student: 测试(None)>
fs.customer
# <CustomerInfo: 测试>
fsc = fs.customer
obj
# <CustomerInfo: 测试>
obj == fsc
# True
创建模板标签显示关联对象
在djadmin应用的templatetags包djadmin_tags.py增加一个模板标签,用于显示要被删除的对象所有关联对象
测试
AttributeError: 'CustomerInfo' object has no attribute 'student'
# 增加关联名
customer = models.OneToOneField(CustomerInfo, verbose_name='客户', on_delete=models.CASCADE, related_name='student')
'CustomerInfo' object has no attribute 'customerinfo'
# 增加关联名
referral_from = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='转介绍客户', related_name='customerinfo')
'CustomerInfo' object has no attribute 'customerfollowup'
# 增加关联名
customer = models.ForeignKey(CustomerInfo, on_delete=models.CASCADE, verbose_name='客户', related_name='customerfollowup')
然后把所有外键都添加related_name属性为该模型名称小写
修改后的模型所有如下:
from django.db import models
from django.contrib.auth.models import User
class Menu(models.Model):
"""动态菜单"""
Url_Type_Choices = (
(0, '绝对URL'),
(1, '动态URL')
)
name = models.CharField(max_length=100, verbose_name='菜单名称')
url_type = models.SmallIntegerField(choices=Url_Type_Choices, default=0, verbose_name='菜单类型')
url = models.CharField(max_length=200, verbose_name='URL地址')
class Meta:
unique_together = ('name', 'url')
verbose_name_plural = verbose_name = '动态菜单'
def __str__(self):
return self.name
class Role(models.Model):
"""角色表"""
name = models.CharField(max_length=50, unique=True, verbose_name='角色名称')
menus = models.ManyToManyField(Menu, blank=True, verbose_name='动态菜单', related_name='role') # 一个角色可以访问多个菜单,一个菜单可以被多个角色访问
class Meta:
verbose_name_plural = verbose_name = '角色'
def __str__(self):
return self.name
class UserProfile(models.Model):
"""用户信息表"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='userprofile', verbose_name='关联系统User') # 扩展user模型
name = models.CharField(max_length=50, verbose_name='姓名')
role = models.ManyToManyField(Role, related_name='userprofile', verbose_name='角色列表')
class Meta:
verbose_name_plural = verbose_name = '用户'
def __str__(self):
return self.name
class Branch(models.Model):
"""校区分支"""
name = models.CharField(max_length=50, unique=True, verbose_name='校区名')
address = models.CharField(max_length=200, blank=True, null=True, verbose_name='地址')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = verbose_name = '校区'
class Course(models.Model):
"""课程表"""
name = models.CharField(max_length=50, unique=True, verbose_name='课程名称')
price = models.PositiveSmallIntegerField(verbose_name='价格') # 整数
period = models.PositiveSmallIntegerField(verbose_name='课程周期(月)', default=5)
outline = models.TextField(verbose_name='大纲')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = verbose_name = '课程'
class ClassInfo(models.Model):
"""班级信息"""
Class_Type_Choices = (
(1, '工作日'),
(2, '周末'),
(3, '网络班')
)
branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='所属校区', related_name='classinfo')
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='课程', related_name='classinfo')
class_type = models.SmallIntegerField(choices=Class_Type_Choices, verbose_name='班级类型')
semester = models.SmallIntegerField(verbose_name='学期')
teachers = models.ManyToManyField(UserProfile, verbose_name='讲师', related_name='classinfo')
start_date = models.DateField(verbose_name='开班日期')
graduate_date = models.DateField(blank=True, null=True, verbose_name='毕业日期') # 结束日期不固定,可为空
class Meta:
verbose_name_plural = verbose_name = '班级信息'
unique_together = ('branch', 'course', 'class_type', 'semester') # 联合唯一,班级不能重复
def __str__(self):
return '{}({})期'.format(self.course.name, self.semester)
class CourseRecord(models.Model):
"""上课记录"""
class_grade = models.ForeignKey(ClassInfo, on_delete=models.CASCADE, verbose_name='班级', related_name='courserecord')
day_num = models.PositiveSmallIntegerField(verbose_name='课程节次')
teacher = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name='讲师', related_name='courserecord')
title = models.CharField(max_length=200, verbose_name='本节主题')
content = models.TextField(verbose_name='本节内容')
has_homework = models.BooleanField(default=False, verbose_name='本节是否有作业')
homework = models.TextField(blank=True, null=True, verbose_name='作业内容')
created_time = models.DateField(auto_now_add=True, verbose_name='创建时间')
def __str__(self):
return '{}第({})节'.format(self.class_grade, self.day_num)
class Meta:
verbose_name_plural = verbose_name = '上课记录'
unique_together = ('class_grade', 'day_num')
class CustomerInfo(models.Model):
"""客户信息表"""
Contact_Type_Choices = (
(1, 'qq'),
(2, '微信'),
(3, '手机'),
(4, '其他')
)
Source_Choice = (
(1, 'qq群'),
(2, '微信'),
(3, '转介绍'),
(4, '其它'),
)
Status_Choice = (
(1, '未报名'),
(2, '已报名'),
(3, '结业')
)
name = models.CharField(max_length=50, verbose_name='客户姓名')
contact_type = models.SmallIntegerField(choices=Contact_Type_Choices, default=1, verbose_name='联系媒介')
contact = models.CharField(max_length=50, unique=True, verbose_name='联系方式')
source = models.SmallIntegerField(choices=Source_Choice, verbose_name='客户来源')
# 如果是转介绍,介绍人是学员,介绍别人来学习,需要关联到学员本人,如果不是,可为空
referral_from = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='转介绍客户', related_name='customerinfo')
consult_courses = models.ManyToManyField(Course, verbose_name='咨询课程', related_name='customerinfo') # 多对多关联课程
consult_content = models.TextField(verbose_name='咨询内容')
status = models.SmallIntegerField(choices=Status_Choice, verbose_name='客户状态')
consultant = models.ForeignKey(UserProfile, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='课程顾问', related_name='customerinfo')
created_time = models.DateField(auto_now_add=True, verbose_name='创建时间')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = verbose_name = '客户信息'
class CustomerFollowUp(models.Model):
"""客户跟进记录"""
Status_Choices = (
(0, '近期无报名计划'),
(1, '一个月内报名'),
(2, '半个月报名'),
(3, '已报名')
)
customer = models.ForeignKey(CustomerInfo, on_delete=models.CASCADE, verbose_name='客户', related_name='customerfollowup')
content = models.TextField(verbose_name='跟进内容')
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name='跟进人', related_name='customerfollowup')
status = models.SmallIntegerField(choices=Status_Choices, verbose_name='客户状态')
created_time = models.DateField(auto_now_add=True, verbose_name='创建时间')
def __str__(self):
return "{}跟进{}状态:{}".format(self.user.name, self.customer.name, self.get_status_display())
class Meta:
verbose_name_plural = verbose_name = '跟进记录'
class Student(models.Model):
customer = models.OneToOneField(CustomerInfo, verbose_name='客户', on_delete=models.CASCADE, related_name='student')
class_grades = models.ManyToManyField(ClassInfo, verbose_name='班级', related_name='student')
class Meta:
verbose_name_plural = verbose_name = '学员'
def __str__(self):
return '{}'.format(self.customer)
class StudyRecord(models.Model):
"""学习记录"""
Score_Choices = (
(100, 'A+'),
(90, 'A'),
(85, 'B+'),
(80, 'B'),
(75, 'B-'),
(70, 'C+'),
(60, 'C'),
(40, 'C-'),
(-50, 'D'),
(0, 'N/A'), # not avaliable
(-100, 'COPY'), # 抄作业
)
Show_Choices = (
(0, '缺勤'),
(1, '已签到'),
(2, '迟到'),
(3, '早退'),
)
course_record = models.ForeignKey(CourseRecord, on_delete=models.CASCADE, verbose_name='课程', related_name='studyrecord')
student = models.ForeignKey(Student, verbose_name='学生', on_delete=models.CASCADE, related_name='studyrecord')
score = models.SmallIntegerField(choices=Score_Choices, default=0, verbose_name='得分')
show_status = models.SmallIntegerField(choices=Show_Choices, default=1, verbose_name='出勤')
note = models.TextField(blank=True, null=True, verbose_name='成绩备注')
created_time = models.DateField(auto_now_add=True, verbose_name='创建时间')
class Meta:
verbose_name_plural = verbose_name = '学习记录'
def __str__(self):
return '{} {} {}'.format(self.course_record, self.student, self.score)
然后再djadmin_tags.py中添加获取关联对象的模板标签
@register.simple_tag
def display_all_related_objs(obj):
"""获取要被删除的对象的所有关联对象"""
ele = "<ul><b style='color:red'>%s</b>" % obj
# 获取所有反向关联的对象
for reversed_fk_obj in obj._meta.related_objects:
# 获取关联对象的表名
related_table_name = reversed_fk_obj.name
print('\n\n', related_table_name)
ele += "<li>{}<ul>".format(related_table_name)
# 通过表明反向查所有关联的数据
related_lookup_key = '{}'.format(related_table_name) # 原文用的{}_set,也许Django版本不同,在我这就用不了,直接用的related_name表名
from django.urls import reverse
if reversed_fk_obj.get_internal_type() == 'OneToOneField':
try:
related_objs = getattr(obj, related_lookup_key)
print('一对一', related_objs)
ele += "<li><a href='{}'>{}</a> 记录里与[{}]相关的的数据将被删除</li>".format(
reverse('djadmin:table_change', kwargs={'app_name': related_objs._meta.app_label, 'model_name': related_objs._meta.model_name, 'obj_id': related_objs.id}),
related_objs,
obj
)
except:
# 这儿做一个异常捕获:crm.models.CustomerInfo.student.RelatedObjectDoesNotExist: CustomerInfo has no student.
# 当客户还未变成学员时,他并没有student的属性
pass
elif reversed_fk_obj.get_internal_type() == "ManyToManyField": # 不需要深入查找
related_objs = getattr(obj, related_lookup_key).all()
for item in related_objs:
ele += "<li><a href='{}'>{}</a> 记录里与[{}]相关的的数据将被删除</li>".format(
reverse('djadmin:table_change', kwargs={'app_name': item._meta.app_label, 'model_name': item._meta.model_name, 'obj_id': item.id}),
item,
obj
)
elif reversed_fk_obj.get_internal_type() == 'ForeignKey': # 如果不是m2m,就递归查找所有关联的数据
related_objs = getattr(obj, related_lookup_key).all()
print('一对多', related_objs)
for item in related_objs:
ele += "<li><a href='{}'>{}</a></li>".format(
reverse('djadmin:table_change', kwargs={'app_name': item._meta.app_label, 'model_name': item._meta.model_name, 'obj_id': item.id}),
item,
)
# 递归查找
ele += display_all_related_objs(item)
ele += "</ul></li>"
ele += '</ul>'
return ele
修改删除模板页面显示table_delete.html
处理从后端返回的html代码需要加上|safe
。
{% extends 'djadmin/base.html' %}
{% load djadmin_tags %}
{% block title %}
数据表删除 - 后台管理
{% endblock %}
{% block content %}
<h2 class="page-header">{{ model_name }}</h2>
<h3 class="page-header alert alert-danger">你确定要删除吗?</h3>
<div>
删除<a href="{% url 'djadmin:table_change' app_name model_name obj.id %}">【{{ obj }}】</a>
</div>
{% display_all_related_objs obj as all_related_obj_eles %}
{{ all_related_obj_eles|safe }} {# 需要加上|safe,否则会直接显示源代码 #}
{% endblock %}
现在需要向数据库中添加一些测试数据,另外在crm应用的djadmin.py文件中注册一些app,例如
from djadmin.sites import site
from crm import models
from djadmin.djadmin_base import BaseDjAdmin
print('crm models...')
# 注册model
class CustomerInfoAdmin(BaseDjAdmin): # 不使用object,直接继承BaseDjAdmin
list_display = ['name', 'contact_type', 'contact', 'consultant', 'consult_content', 'status', 'created_time']
list_filter = ['source', 'consultant', 'status', 'created_time']
search_fields = ['contact', 'consultant__name', 'consult_content']
readonly_fields = ['contact', 'status']
filter_horizontal = ['consult_courses']
site.register(models.CustomerInfo, CustomerInfoAdmin)
site.register(models.Role)
site.register(models.Menu)
site.register(models.UserProfile)
site.register(models.Course)
site.register(models.Student)
site.register(models.ClassInfo)
site.register(models.Branch)
site.register(models.CourseRecord)
site.register(models.CustomerFollowUp)
site.register(models.StudyRecord)
现在选择删除一个课程,例如 http://127.0.0.1:8000/djadmin/crm/course/2/delete/
image.png就会显示这个页面, 删除的时候会提示所有关联对象
删除确认按钮及逻辑
删除模板中增加删除确认按钮
修改table_delete.htm模板
{% extends 'djadmin/base.html' %}
{% load djadmin_tags %}
{% block title %}
数据表删除 - 后台管理
{% endblock %}
{% block content %}
<h2 class="page-header">{{ model_name }}</h2>
<h3 class="page-header alert alert-danger">你确定要删除吗?</h3>
<div>
删除<a href="{% url 'djadmin:table_change' app_name model_name obj.id %}">【{{ obj }}】</a>
</div>
{% display_all_related_objs obj as all_related_obj_eles %}
{{ all_related_obj_eles|safe }} {# 需要加上|safe,否则会直接显示源代码 #}
<form method="post">
{% csrf_token %}
<input class="hidden" value="yes" name="delete_confirm">
<input type="submit" class="btn btn-danger" value="确认删除">
<a class="btn btn-info" href="{% url 'djadmin:table_change' app_name model_name obj.id %}">返回编辑</a>
</form>
{% endblock %}
image.png
处理删除post逻辑
修改djadmin应用下的views.py中的删除数据视图
# 数据删除
@login_required
def table_delete(request, app_name, model_name, obj_id):
admin_class = site.enable_admins[app_name][model_name]
obj = admin_class.model.objects.get(id=obj_id)
if request.method == 'POST':
if request.POST.get('delete_confirm') == 'yes':
admin_class.model.objects.filter(id=obj_id).delete()
return redirect(reverse('djadmin:table_detail', args=(app_name, model_name)))
return render(request, 'djadmin/table_delete.html', locals())
网友评论