美文网首页Python Web开发学习
【CRM客户关系管理】19.对象删除功能,显示删除的关联对象和确

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

作者: 吾星喵 | 来源:发表于2018-12-23 15:46 被阅读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())
    

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/hojnkqtx.html