美文网首页python全栈学习
基于Django实现的CRM系统

基于Django实现的CRM系统

作者: SlashBoyMr_wang | 来源:发表于2018-09-03 22:46 被阅读49次

    前言:Django是一个python大而全的前端框架,Django自带的admin也是一个不错的信息管理系统,功能多,可拓展性强。那么,我们仿照Django-admin能不能自己写代码实现admin的主要功能呢?答案是当然可以!!!通过这个小项目的练习,可以更加深刻地理解其中的编程原理,升华自己的思想。

    !!!学习之前需要先大概了解Django-admin的源码:https://www.jianshu.com/p/006ec45bcf1a

    废话少说,接下来开始进入正题!!!

    一、新建Django项目

    Django的项目新建这里就不说了,此处省略100字.......

    注意:需要注册两个app(stark,app01)stark是admin的翻版,名字无所谓。

    二、修改stark中apps的代码

    通过看django的源码可以得知:Django启动时,自动加载settings配置文件中的installed_apps,然后执行源码中的autodiscover()方法来顺序加载apps对应的admin.py文件。所以我们可以通过修改apps中的代码让django来执行我们写的stark.py文件而不执行admin.py文件.

    • stark中的apps.py文件:
    from django.apps import AppConfig
    from django.utils.module_loading import autodiscover_modules
    
    class StarkConfig(AppConfig):
        name = 'stark'
    
        # 这个ready方法是固定不变的
        def ready(self):
            autodiscover_modules('stark')
    

    三、重写StarkSite类的register方法来实现model的注册

    先在app名为stark的文件夹下新建一个servers包,在servers下新建一个site.py文件,在site.py文件中写如下代码:

    class StarkSite(object):
        def __init__(self):
            #定义一个字典用于存储接下来需要注册的model和对应congfig配置类
            self._registry = {}
    
        def register(self, model, admin_class=None):
            # 设置配置类,有自定义的就用自定义的,没有就用默认的ModelStark
            if not admin_class:
                admin_class = ModelStark
            #以model为键,配置类实例化对象为值进行注册
            self._registry[model] = admin_class(model)
    
    site = StarkSite()
    

    四、在每个app下新建stark.py文件,通过调用StarkSite类来注册model表

    执行每个app下的stark.py文件来注册所有app下用户提交的需要注册的model表和对应的config配置类

    • app01下的stark.py:
    //导入默认配置类ModelStark,(这个类稍后再创建)
    from stark.servers.site import ModelStark
    //site是StarkSite类的实例化对象,通过模块导入实现的单利模式
    from stark.servers.site import site
    //导入app01下的models文件
    from app01 import models
    
    
    //注册models表
    site.register(models.Book)
    site.register(models.Publish)
    site.register(models.Author)
    site.register(models.AuthorDetail)
    
    • 另附app01下的models.py:
    from django.db import models
    
    
    class Author(models.Model):
        nid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32,verbose_name='作者')
        age = models.IntegerField(verbose_name="年龄")
    
        # 与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.DateField()
        telephone = models.BigIntegerField()
        addr = models.CharField(max_length=64)
    
    
    class Publish(models.Model):
        nid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32,verbose_name='出版社')
        city = models.CharField(max_length=32,verbose_name="城市")
        email = models.EmailField(verbose_name='邮箱')
    
        def __str__(self):
            return self.name
    
    
    class Book(models.Model):
        nid = models.AutoField(primary_key=True)
        title = models.CharField(max_length=32,verbose_name='书名')
        price = models.DecimalField(max_digits=5, decimal_places=2,verbose_name="价格")
        # 与Publish建立一对多的关系,外键字段建立在多的一方
        publish = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.CASCADE,verbose_name='出版社')
        publishDate = models.DateField(verbose_name="出版日期",auto_created=True)
        # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
        authors = models.ManyToManyField(to='Author',verbose_name='作者' )
    
        def __str__(self):
            return self.title
    

    五、设计Url

    url的设计中,我们采用和django-admin相同的一下这种方式:
    url('正则表达式',([ url列表 ],None,None)) 规则

    • 项目文件夹下的url路由分发
    from django.conf.urls import url
    from stark.servers.site import site
    
    urlpatterns = [
        url(r"^stark/",site.urls)
    ]
    
    
    • StarkSite类中添加上urls和get_urls方法
    class StarkSite(object):
        def __init__(self):
            #定义一个字典用于存储接下来需要注册的model和对应congfig配置类
            self._registry = {}
    
        def register(self, model, stark_class=None):
            # 设置配置类,有自定义的就用自定义的,没有就用默认的ModelStark
            if not stark_class:
                stark_class = ModelStark
            #以model为键,配置类实例化对象为值进行注册
            self._registry[model] = stark_class(model)
    
        def get_urls(self):
            temp = []
            # self._registry是以model为键,config_obj配置类实例化对象为值进行注册后的字典容器
            for model, config_obj in self._registry.items():
                # 通过模型表model的._meta方法得到动态的、注册的、model的名字和model所属的app名
                model_name = model._meta.model_name
                app_label = model._meta.app_label
                # "%s/%s/" % (app_label, model_name)字符串拼接先得到所需路径的前边部分'app/model/'
                # config_obj.urls为通过配置类对象调用配置类下的urls方法来得到相应model表的增删改查url
                #--->用到 了路由的二级分发
                temp.append(url(r"%s/%s/" % (app_label, model_name), config_obj.urls))
    
            return temp
    
        @property
        def urls(self):
            return self.get_urls(), None, None
    
    • 在ModelStark默认配置类中添加urls和get_urls方法
    class ModelStark(object):
        def get_urls(self):
            model_name = self.model._meta.model_name
            app_label = self.model._meta.app_label
            temp = [
                #给每条增删改查的url设置不同的name,后续需要通过反向解析找到对应的url
                url(r"^$", self.listview, name=f'{app_label}_{model_name}_check'),
                url(r"add/$", self.addview, name=f'{app_label}_{model_name}_add'),
                url(r"(\d+)/change/$", self.changeview, name=f'{app_label}_{model_name}_change'),
                url(r"(\d+)/delete/$", self.delview, name=f'{app_label}_{model_name}_delete'),
            ]
            return temp
    
        @property
        def urls(self):
            return self.get_urls(), None, None
    

    代码执行到这里就为注册的每张表生成了增、删、改、查四个url
    以上代码段的执行顺序图示:


    0.png

    六、接下来开始写每个url对应的视图函数之查看页面listview

    listview中大概包含数据展示、分页、search查询、action批量操作、filter过滤、pop关联等主要功能,接下来就将每一项内容的设计思路和需要用到的知识点做一下分享,具体的代码太多了,就不一一列举:

    接下来的所有逻辑将基于site.py下的ModelStark配置类完成:

    注意:由于在StarkSite类中代码的第一步就是判断用户有没有自己stark_class配置类,所以通过这种方式来扩展出来的用户的自定义功能,自己的配置类中有相应的方法或者属性就用自己的,没有就用默认的配置类中的。这都是基于用户在自定义配置类时主动继承默认配置类ModelStark。这个项目充分利用了python的面向对象类的继承、调用、封装等知识点。

    在ModelStark类中有两个贯穿全局的参数:谨记谨记
    self:当前model模型表的配置类对象
    self.model:当前model模型表

    - 展示数据的表头部分

    通过配置类对象中的new_list_display列表得到表头的展示字段或者默认显示的操作名称,将操作的具体逻辑封装成函数来调用,这其中用到了一下知识点:

    • django.utils.safestring.mark_safe方法,在后端对html代码段做safe处理,不让前端转义html代码
    • field_obj = model._meta.get_field('字段字符串') 通过字段字符串得到对应的字段对象
    • model_name = model._meta.model_name 得到模型表对应的表名
    def create_head(self):
        # 表头的建立
        heads_list = []
        for field_or_func in self.config_obj.new_list_display():
            if callable(field_or_func):
                head = field_or_func(self.config_obj, is_head=True)
            else:
                if field_or_func != "__str__":
                    field_obj = self.config_obj.model._meta.get_field(field_or_func)
                    head = field_obj.verbose_name
                else:
                    head = self.config_obj.model._meta.model_name
            heads_list.append(head)
        return heads_list
    

    - 展示数据体部分

    这部分的逻辑和表头的设计很像,不同的有以下几点:

    • 数据的结构,数据体的数据对应着model来看分为每条记录和记录中包含的字段信息,所有可以构建出如下的数据结构:
      举例:content_list=[['title','prince','publish'.....],['title','prince','publish'.....]......],前端就可以通过两次循环拿到数据
    • 一对多,多对多字段,这种字段需要单拿出来作数据的处理才能展示
    • 对于自定制的操作来说,通过配置类对象中的list_display_links列表得到相应的目标字段,通过反向解析得到对应的url,通过字符串处理后返回,

    用到一下知识点:

    • isinstance():判断字段的所属类型
    • getattr():通过反射得到字段的对应内容
    • 反向解析来获取对应操作的url路径
    def create_body(self):
        # 表内容content_list=[[数据1],[数据2],[数据3]....]
        obj_list = []
        for data_obj in self.data_list:
            content_list = []
            for field_or_func in self.config_obj.new_list_display():
                if callable(field_or_func):
                    content = field_or_func(self.config_obj, data_obj)
                else:
                    try:
                        from django.db.models.fields.related import ManyToManyField
                        field_obj = data_obj._meta.get_field(field_or_func)
                        if isinstance(field_obj, ManyToManyField):
                            contents_list = getattr(data_obj, field_or_func).all()
                            content = '||'.join([str(item) for item in contents_list])
                        else:
                            content = getattr(data_obj, field_or_func)
                            if field_or_func in self.config_obj.list_display_links:
                                url = self.config_obj.get_change_url(data_obj)
                                content = mark_safe(f'<a href="{url}">{content}</a>')
                    except:
                        content = getattr(data_obj, field_or_func)
                content_list.append(content)
            obj_list.append(content_list)
        return obj_list
    

    - 分页功能

    用到的知识点:

    • params= copy.deepcopy(request.GET),request.GET中的数据是字典类型的,但是不能修改,因为源码中对这个字典加了锁,但是可以对它做深copy,copy之后数据就可以修改了
    • params.urlencode()方法可以直接将字典转成url参数的固定格式(key1=value1&key2=value2)

    - search查询

    通过配置类对象中的search_fields列表得到相应的查询字段

    • search查询就是通过前端得到的数据在后端做数据库的filter过滤

    用到的知识点:

    • ORM的内置函数Q()的经典用法
    from django.db.models import Q
    //实例化创建Q对象
    search_condition = Q()
    //对象.connector可以修改Q的查询条件,默认的是and条件
    search_condition.connector = "or"
    //通过.children.append向Q()对象中添加元组类型('field',condition)的筛选条件
    search_condition.children.append((f"{field}__icontains", condition))
    //过滤数据
    ret = queryset.filter(search_conditions)
    
    • field__icontains:ORM操作中的filte()模糊查询

    - action批量操作

    • 通过配置类对象中的actions_list列表得到对应的批量操作处理函数

    用到的知识点:

    • action.short_description,python一切皆对象,都可以添加属性,
    • action.__ name__:得到函数名
    def actions_list(self):
        actions_list = []
        action_info = []
        if self.config_obj.actions:
            actions_list.extend(self.config_obj.actions)
        actions_list.append(self.config_obj.batch_delete)
        for action in actions_list:
            action_info.append({
                "desc": action.short_description,
                "action": action.__name__
            })
        return action_info  //处理得到前端所需要的数据
    
    //视图处理函数,处理用户发过来的请求,执行相应的方法操作
    if request.method == "POST":
        pk_list = request.POST.getlist('checkbox_pk')
        queryset = self.model.objects.filter(pk__in=pk_list)
        action_name = request.POST.get("action")
        try:
            action = getattr(self, action_name)
            action(queryset)
        except:
            code = 1
    

    - filter过滤

    filter过滤主要处理的是model表中的一对多和多对多的字段,通过filter来筛选出数据的交集,filter的设计主要分为三个方面:

    • 通过判断model各字段的类型找出一对多和多对多的字段,布局前端页面样式
    • 设计url
    • 通过前端发过来的数据做filter筛选,返回筛选之后的数据

    用到的知识点:

    • rel_model = field_obj.rel.to:通过多对多或者一对多的字段得到对应的模型表
    //url设计代码段
    def filter_field_links(self):
        filter_links = {}
    
        for field in self.config_obj.list_filter:
            # 从get请求中得到需要的filter参数
            params = copy.deepcopy(self.request.GET)
            choice_field_pk = self.request.GET.get(field, 0)
            # 通过多对多或者一对多的字段得到对应的模型表
            field_obj = self.config_obj.model._meta.get_field(field)
            rel_model = field_obj.rel.to
    
            # 得到模型表中的所有数据
            ret_model_queryset = rel_model.objects.all()
            tem = []
            for obj in ret_model_queryset:
                # 将对应的对象pk值添加到字典中,以当前model表的字段为键
                params[field] = obj.pk
                if obj.pk == int(choice_field_pk):
                    link = f"<a style='color:red' href='?{params.urlencode()}'>{obj}</a>"
                else:
                    link = f"<a href='?{params.urlencode()}'>{obj}</a>"
                tem.append(link)
            filter_links[field] = tem
        return filter_links
    

    七、addview和changeview的pop关联操作

    在model字段中经常存在着表与表的关联,所以在创建新数据或者修改数据时需要同时创建相关联的数据,
    业务逻辑:

    • 通过对form对象内字段的类型判断找到一对多和多对多的字段,在字段中添加标志位,用于前端的选择性生成对应的a标签链接
    • 通过反向解析得到一对多、多对多字段对应表的add的url
    • 由于form表单生成的前端标签的id值是id_'字段名',所以后端需要将对应的字段名字符串拼接一个pop_back_id,用于前端DOM操作。
    • 前端通过事件绑定触发window.open()打开一个新窗口用于添加关联字段的数据
    • 数据提交成功后返回一个页面,在页面中通过js代码将新添加的数据传递给父页面,再通过window.cloce()方法关闭子页面
    • 父页面通过DOM操作将新添加的数据通过DOM操作渲染到页面的相应位置。

    执行思路:


    222.png

    就先到这吧!!!

    相关文章

      网友评论

        本文标题:基于Django实现的CRM系统

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