Python Web开发学习

【CRM客户关系管理】19.对象删除功能,显示删除的关联对象和确

2018-12-23  本文已影响0人  吾星喵

对象删除功能

基础配置

创建删除对象模板

在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())
上一篇 下一篇

猜你喜欢

热点阅读