CRM系统项目总结

一、准备:

  1、组件部分(数据增删改查实现)模仿django admin开发一个组件

    效果:

    (1)、启动服务(先在settings中添加app)

from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules


class XadminConfig(AppConfig):
    name = 'xadmin'

    def ready(self):
        autodiscover_modules('xadmin')
AppConfig

    (2)、注册表

from django.db import models

# Create your models here.
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    age=models.IntegerField()

    # 与AuthorDetail建立一对一的关系
    authorDetail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE)
    def __str__(self):
        return self.name

class AuthorDetail(models.Model):

    nid = models.AutoField(primary_key=True)
    birthday=models.DateTimeField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)

    def __str__(self):
        return self.addr

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField( max_length=32)
    city=models.CharField( max_length=32)
    email=models.EmailField()



    def __str__(self):
        return self.name


class Book(models.Model):

    nid = models.AutoField(primary_key=True, verbose_name=" 编号")
    title = models.CharField( max_length=32, verbose_name="书籍名称")
    publishDate=models.DateTimeField(null=True, verbose_name='日期')
    price=models.DecimalField(max_digits=5, decimal_places=2)

    # 与Publish建立一对多的关系,外键字段建立在多的一方
    publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
    # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
    authors=models.ManyToManyField(to='Author',)


    def __str__(self):
        return self.title
models
from xadmin.service.start__xadim import site, ModelAdmin
from app01 import models
from django import forms
from django.utils.safestring import mark_safe
from django.urls import reverse


class BookConfig(ModelAdmin):

    # def add_tag_change(self, obj=None, is_header=False):
    #     if is_header:  # 如果是表头
    #         return '修改操作'
    #     return mark_safe('<a href="%s">修改</a>' % reverse('change_data', args=(obj.pk,)))  # 如果是表单数据
    #
    # def add_tag_delete(self, obj=None, is_header=False):
    #     if is_header:
    #         return '删除操作'
    #     return mark_safe('<a href="%s/delete">删除</a>' % reverse('delete_data', args=(obj.pk,)))
    #
    # def add_tag_checked(self, obj=None, is_header=False):
    #     if is_header:
    #         return mark_safe('<input type="checkbox" name="checked_data" class="all">')
    #     return mark_safe('<input type="checkbox" name="checked_data" value="%s" class="list_all">' % obj.pk)
    class ModelForms(forms.ModelForm):
        class Meta:
            model = models.Book
            fields = '__all__'
            widgets = {'publishDate': forms.widgets.TextInput(attrs={'type': 'date', }), }
            labels = {
                'title': '书名',
                'price': '价格',
            }

    # def action_test(self, queryset, request):
    #     print(self,type(self))
    #     for i in queryset:
    #         i.price = 100
    #         i.save()

    # action_test.short_description = "批量初始化"  # action函数功能实现简介

    list_display = ['nid', 'title', 'price', 'publish', 'publishDate', 'authors']
    list_display_links = ['title']
    model_forms_chinese_field = ModelForms
    list_search_fields = ['title', 'price']
    # list_action_func = [action_test]
    list_filter = ['title', 'publish', 'authors']

class PublishConfig(ModelAdmin):
    list_display = ['nid', 'title', 'price']


site.register(models.Book, BookConfig)
site.register(models.Publish)
site.register(models.Author)
site.register(models.AuthorDetail)
在组件之外的app中添加表和注册表

    (3)、服务(url分发,数据增删改查,展示等等)

       注意问题:

        url二级分发为什么在配置类实现?

        url地址反向解析

        自定义显示哪些字段内容,多对多字段该如何处理,修改和删除标签、哪个字段可点击进入到修改页面,该如何实现

        自定义哪些字段可以搜索,获取搜索值后用Q方法处理条件

        自定义批量处理函数,页面标签value值应该放什么(函数名,选中数据的pk值)

        自定义哪些字段可以筛选(关系字段如何获取(all方法),普通字段如何获取)

field_obj = self.model_admin_self.model._meta.get_field(filter_field)
            if isinstance(field_obj, ManyToManyField) or isinstance(field_obj, ForeignKey):  # 过滤标签是不是多对多或者多对一关系
                queryset_list = field_obj.remote_field.model.objects.all()  # 是的话取到所有关联表对象
通过字符串形式的字段取得对应关系表的所有对象

        修改页面时,关系字段应该可以直接点击添加按钮添加关系字段对象(浏览器父类窗口,子类窗口的使用)

window.opener.pop_response('{{ ret.pk }}',"{{ ret.text }}",'{{ ret.pop_res_id }}')  //执行父页面的函数pop_response,向里面传递参数,用于数据更新后的DOM操作
    function pop(url) {
        window.open(url,"","width=600,height=400,top=100,left=100")  // 为a标签“+”的点击事件函数,打开一个添加数据的子窗口
        
    }


    function pop_response(pk, text, pop_res_id) {
        console.log(pk, text, pop_res_id)
        var $option = $('<option>')  //创建一个<option>标签
        console.log($option)
        $option.val(pk)  //标签value值为这条数据的主键值
        $option.text(text) // 文本为__str__
        $option.attr("selected","selected")  //option标签为选中状态
        $('#'+pop_res_id).append($option)  // 将这条option标签插入到select标签,

    }
</script>

        主页面展示的数据应该放在一个类下,比如分页数据、表头数据、表单数据、函数数据、过滤字段数据等。只需要在视图函数中创建这个类对象,把所有数据对象、request、当前models的配置类self对象作为参数传入,把创建的这个类对象传入模板语言,模板语言直接通过这个对象可以调用类下的所有方法,用于展示分页、表头、表单等数据。

        修改和增加页面使用modelform模块快速构建一个html页面,modelform在视图的使用,关系字段在增加和修改页面如何实时添加数据

    def add(self, request):
        # 获取一个modelform类,并创建一个modelform类对象,用于添加或者修改页面的渲染
        modelform = self.get_model_forms()
        forms_obj = modelform()
        # print(forms_obj)  # modelform对象
        from django.forms.boundfield import BoundField
        # from django.forms.models import ModelMultipleChoiceField
        from django.forms.models import ModelChoiceField   # ModelMultipleChoiceField继承ModelChoiceField
        for forms_field_obj in forms_obj:
            # print('-------', forms_field_obj, type(forms_field_obj))  # django.forms.boundfield.BoundField
            # print('+++++++', forms_field_obj.field, type(forms_field_obj.field))  # <class 'django.forms.models.ModelMultipleChoiceField'>  # modelform字段对象
            # print('*******', forms_field_obj.name, type(forms_field_obj.name))  # authors <class 'str'>  字段名字(字符串)
            if isinstance(forms_field_obj.field, ModelChoiceField):  # 如果这个字段对象是多对多或者一对多字段
                forms_field_obj.is_pop = True  # 用于模板语言判断是否为关系字段
                # print(forms_field_obj.field.queryset, type(forms_field_obj.field.queryset))  # 返回这个关系字段下所有的对象
                # print(forms_field_obj.field.queryset.model, type(forms_field_obj.field.queryset.model))  # 返回这个关系字段的关联模型表

                app_label = forms_field_obj.field.queryset.model._meta.app_label  # 获取关联字段的关系模型表所在的app
                model_name = forms_field_obj.field.queryset.model._meta.model_name  # 获取关联字段的关系模型表的表名

                url = reverse('{}_{}_add_data'.format(app_label, model_name))
                forms_field_obj.url = url+'?pop_res_id=id_{}'.format(forms_field_obj.name)  # 用于添加页面关系字段‘+’的链接拼接 ,参数用于判断是返回当前添加页还是主添加页,以及为哪个select标签进行DOM操作

        if request.method == 'POST':
            forms_obj = modelform(request.POST)
            if forms_obj.is_valid():
                add_obj = forms_obj.save()
                pop_res_id = request.GET.get('pop_res_id')
                if pop_res_id:
                    ret = {"pk": add_obj.pk, "text": str(add_obj), 'pop_res_id': pop_res_id}
                    return render(request, 'pop.html', {'ret': ret})

                return redirect('%s' % (self.addtag.get_show_reverse_url(self.model)))

        return render(request, 'add_data.html', {'forms_obj': forms_obj})
视图
<script>
    window.opener.pop_response('{{ ret.pk }}',"{{ ret.text }}",'{{ ret.pop_res_id }}')  //执行父页面的函数pop_response,向里面传递参数,用于数据更新后的DOM操作
    {#console.log('{{ ret.pk }}',"{{ ret.text }}",'{{ ret.pop_res_id }}')#}
    window.close()
</script>
pop.html 用于DOM操作所需的数据传输
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3" style="padding-top: 100px">

            <hr>
            <form action="" method="post" novalidate>

                {% csrf_token %}
                {% for field in forms_obj %}
                    <p style="position: relative">{{ field.label }}
                        {{ field }} <span>{{ field.errors.0 }}</span>
                        {% if field.is_pop %}
                        <a onclick="pop('{{ field.url }}')" style="position: absolute;right: -30px; top: 25px; font-size: 20px; height: 50%">+</a>
                        {% endif %}

                    </p>
                {% endfor %}


                <div class="submit-btn">
                    <button class="btn btn-info ">提交</button>
                </div>

            </form>
        </div>
    </div>
</div>

<script>
    function pop(url) {
        window.open(url,"","width=600,height=400,top=100,left=100")  // 为a标签“+”的点击事件函数,打开一个添加数据的子窗口
        
    }


    function pop_response(pk, text, pop_res_id) {
        console.log(pk, text, pop_res_id)
        var $option = $('<option>')  //创建一个<option>标签
        console.log($option)
        $option.val(pk)  //标签value值为这条数据的主键值
        $option.text(text) // 文本为__str__
        $option.attr("selected","selected")  //option标签为选中状态
        $('#'+pop_res_id).append($option)  // 将这条option标签插入到select标签,

    }
</script>
add.html

      url路径保存搜索条件:先深copy之前的url条件,然后再往条件添加新的条件,添加后换成url编码,最后把它拼到a标签        

params = copy.deepcopy(self.request.GET)  #{‘name’: aike}
params[id] = 1  #{'name': aike, 'id': 1}
url = params.urlencode()  # # 把字典转为URL编码(?name=aike&id=1)
link_tag = mark_safe("<a href='?%s' class='filter_field active'>%s</a>" % (url, text))

    (4)、url

from django.contrib import admin
from xadmin.service.start__xadim import site
from django.urls import path, re_path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('xadmin/', site.urls),]
urls

    

  2、权限管理部分:RBAC权限介绍,用django制作一个简单的权限组件

    (1)、表设计:用户表、角色表、权限表、权限分组表

        用户拥有角色,角色拥有权限(一条url代表一条权限),权限拥有分组。一张表的增删改查分为一组,例如学生表,查看学生,编辑学生,删除学生,添加学生可归为学生管理。

from django.db import models

# Create your models here.
from django.db import models

# Create your models here.


class User(models.Model):
    nid = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32, )
    password = models.CharField(max_length=32,)
    roles = models.ManyToManyField(to='Roles')

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = '用户表'


class Roles(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=12, verbose_name='角色名')
    permission = models.ManyToManyField(to='Permission', verbose_name='权限')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = '角色表'


class Permission(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=12)
    urls = models.CharField(max_length=120)

    # action和group用于第二种实现权限校验方式需要添加的字段,不添加这些也能实现
    # 主要作用是在HTML页面当中用模板语言判断是否有权限时,url地址不用写很死
    action = models.CharField(max_length=32, null=True)  # 功能 增删改查
    group = models.ForeignKey(to='PermissionGroup', on_delete=models.CASCADE, null=True)  # 权限分组

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = '权限表'


# 权限分组, 用户分组、角色分组等。用于第二种权限校验方式
class PermissionGroup(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)

    def __str__(self):
        return self.title
models.py

    (2)、登录成功注册session:session中应该存放哪些数据,用户主键、用户名、用户权限url列表、权限分组等。

def reg_session(request, user):
    """
    登录后将权限和用户id存入session,存入后才能进行权限校验
    :param request:
    :param user: 登录对象
    :return: 没有返回值
    """
    request.session['user_id'] = user.pk
    request.session['username'] = user.username
    # 将用户的所有权限url以列表形式添加到session
    permission_urls_obj_list = user.roles.values('permission__urls').distinct()
    permission_urls_list = [permission_urls['permission__urls'] for permission_urls in permission_urls_obj_list]
    request.session['permission_urls_list'] = permission_urls_list

    # 将用户权限分组nid为键(1为用户管理,2为角色管理),action和url为值,以字典形式存入到session,
    #    {1: {
    #         'urls': ['/users/', '/users/add/', '/users/delete/(\d+)', 'users/edit/(\d+)'],
    #         'actions': ['list', 'add', 'delete', 'edit']},

    # 第二种校验方式,可以在HTML页面不写死url判断是否有删改的操作,即该不该显示删除、修改或者增加的按钮
    permission_dict = {}
    urls_action_nid = user.roles.values('permission__urls', 'permission__action', 'permission__group__nid').distinct()

    for i in urls_action_nid:
        nid = i['permission__group__nid']
        if nid not in permission_dict:
            permission_dict[nid] = {
                    'urls': [i['permission__urls']],
                    'actions': [i['permission__action']]
                    }
        else:
            permission_dict[nid]['urls'].append(i['permission__urls'])
            permission_dict[nid]['actions'].append(i['permission__action'])

    request.session['permission_dict'] = permission_dict

    # 菜单栏显示哪些权限,角色管理或者用户管理
    menu_permission = user.roles.filter(permission__action='show').values('permission__urls', 'permission__group__title')
    menu_permission_list = []
    for item in menu_permission:
        menu_permission_list.append(item)

    request.session['menu_permission_list'] = menu_permission_list
注册session

    (3)、中间件权限校验:权限url以正则方式存储在数据库,校验时应该以正则方式校验。白名单处理:登录、admin等。判断是否登录。最后进行权限校验

from django.utils.deprecation import MiddlewareMixin
import re
from django.shortcuts import render, redirect, HttpResponse

def re_checkout(permission_urls_list, path):
    flag = False
    for permission_urls in permission_urls_list:
        permission_urls = '^%s$' % permission_urls
        ret = re.search(permission_urls, path)
        if ret:
            flag = True
            break
    return flag


class PermissionVerify(MiddlewareMixin):

    def process_request(self, request):

        # 校验白名单
        path = request.path
        filter_urls = ['/login/', '/reg/', '/admin/.*', '/logout/']

        for urls in filter_urls:
            ret = re.search(urls, path)
            # print('******', ret)
            if ret:
                return None

        # 校验是否登录
        user_id = request.session.get('user_id', None)
        if not user_id:
            return redirect('/login/')

        # 网址存在再进行权限校验
        # path_list = ['/user/edit/(d+)/', '/login/', '/user/', '/user/add/', '/roles/']
        # flag = re_checkout(path_list, path)
        # if not flag:
        #     return HttpResponse('404')

        # 校验权限
        # permission_urls_list = request.session['permission_urls_list']
        #
        # flag = re_checkout(permission_urls_list, path)
        # if not flag:
        #     return HttpResponse('没有权限')
        #
        # return None

        # 第二种校验方式,可以在HTML页面不写死url判断是否有删改的操作,即该不该显示删除、修改或者增加的按钮
        permission_dict = request.session.get('permission_dict')
        for urls_actions in permission_dict.values():
            permission_urls_list = urls_actions['urls']
            for permission_urls in permission_urls_list:
                new_urls = '^{}$'.format(permission_urls)
                ret = re.match(new_urls, path)
                print(ret)
                # 将用户权限分组:nid为键(1为用户管理,2为角色管理),action和url为值,以字典形式存入到session,
                #    permission_urls_list={
                #               1: {
                #                   'urls': ['/users/', '/users/add/', '/users/delete/(\d+)', 'users/edit/(\d+)'],
                #                   'actions': ['show', 'add', 'delete', 'edit']},
                #               2: {
                #                      'urls': ['/roles/'],
                #                      'actions': ['show']}
                #                  }

                # 可以实现访问哪个权限分组(用户管理,角色管理),就能得到这个分组所有的权限url和对action,
                # 当用户访问的是用户管理时,对应键值为1,那么通过遍历,就能取到这个分组拥有的权限
                if ret:
                    request.permission_actions_list = urls_actions['actions']
                    print(request.permission_actions_list)
                    return None
        return HttpResponse('没有权限')
中间件实现

二、导入组件:

   将权限和admin组件的所有静态文件、templates等文件一并拷贝至项目即可。然后注册app,加入中间件,项目用户表与权限组件用户表一对一关系。自己写的admin组件注册表,选择实现自定义配置类

三、功能扩展:

   需求将全部在项目app中实现,例如登录注册,自定义配置类。其中自定义配置类是在为表注册组件时实现,所有需要扩展功能时,是在自身配置类中实现。

项目中遇到的小问题:

    self与类名调用类方法的区别,self调用会实例化方法,而类调用不会。所有传参数时,如果是实例化的方法,不需要传self,而类调用方法不会被实例化,使用时需要传self参数。

    使用ajax时,视图返回的值最好使用JsonResponse返回一个字典,非字典时需要设置safe=False

def func(request):
    ret = {'name':aike}
    return JsonResponse(ret)


def func1(request):
    ret = [1,2,3],
    return JsonResponse(ret,safe=False)

    删除多对多字段中的值用remove方法,删除queryset或者obj对象用delete方法。

    关系字段,通过字段对象取得关联表所有对象

field_obj = self.model_admin_self.model._meta.get_field(filter_field)
            if isinstance(field_obj, ManyToManyField) or isinstance(field_obj, ForeignKey):  # 过滤标签是不是多对多或者多对一关系
                queryset_list = field_obj.remote_field.model.objects.all()  # 是的话取到所有关联表对象
            else:
                queryset_list = self.model_admin_self.model.objects.values('pk', filter_field)   # 不是的话取到对应字段值即可

    Q方法的进阶使用

search_connection = Q()  # 创建一个Q对象
search_connection.connector = 'or'  # Q对象查询条件为或关系
search_connection.children.append(条件)# 将查询条件添加到children列表,children接收一个元祖,一个为字段,一个为字段值

search_connection 为一个查询条件

    不清空筛选条件

params = copy.deepcopy(self.request.GET)  #{‘name’: aike}
params[id] = 1  #{'name': aike, 'id': 1}
url = params.urlencode()  # # 把字典转为URL编码(?name=aike&id=1)
link_tag = mark_safe("<a href='?%s' class='filter_field active'>%s</a>" % (url, text))
原文地址:https://www.cnblogs.com/aizhinong/p/12459652.html