基于Django實(shí)現(xiàn)的CRM系統(tǒng)

前言:Django是一個(gè)python大而全的前端框架,Django自帶的admin也是一個(gè)不錯(cuò)的信息管理系統(tǒng)加缘,功能多沿侈,可拓展性強(qiáng)。那么质礼,我們仿照Django-admin能不能自己寫代碼實(shí)現(xiàn)admin的主要功能呢旺聚?答案是當(dāng)然可以!?艚丁砰粹!通過這個(gè)小項(xiàng)目的練習(xí),可以更加深刻地理解其中的編程原理造挽,升華自己的思想碱璃。

!7谷搿嵌器!學(xué)習(xí)之前需要先大概了解Django-admin的源碼:http://www.reibang.com/p/006ec45bcf1a

廢話少說,接下來開始進(jìn)入正題P扯W旖铡!

一庇谆、新建Django項(xiàng)目

Django的項(xiàng)目新建這里就不說了岳掐,此處省略100字.......

注意:需要注冊(cè)兩個(gè)app(stark,app01)stark是admin的翻版饭耳,名字無所謂串述。

二、修改stark中apps的代碼

通過看django的源碼可以得知:Django啟動(dòng)時(shí)寞肖,自動(dòng)加載settings配置文件中的installed_apps纲酗,然后執(zhí)行源碼中的autodiscover()方法來順序加載apps對(duì)應(yīng)的admin.py文件衰腌。所以我們可以通過修改apps中的代碼讓django來執(zhí)行我們寫的stark.py文件而不執(zhí)行admin.py文件.

  • stark中的apps.py文件:
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules

class StarkConfig(AppConfig):
    name = 'stark'

    # 這個(gè)ready方法是固定不變的
    def ready(self):
        autodiscover_modules('stark')

三、重寫StarkSite類的register方法來實(shí)現(xiàn)model的注冊(cè)

先在app名為stark的文件夾下新建一個(gè)servers包觅赊,在servers下新建一個(gè)site.py文件右蕊,在site.py文件中寫如下代碼:

class StarkSite(object):
    def __init__(self):
        #定義一個(gè)字典用于存儲(chǔ)接下來需要注冊(cè)的model和對(duì)應(yīng)congfig配置類
        self._registry = {}

    def register(self, model, admin_class=None):
        # 設(shè)置配置類,有自定義的就用自定義的吮螺,沒有就用默認(rèn)的ModelStark
        if not admin_class:
            admin_class = ModelStark
        #以model為鍵饶囚,配置類實(shí)例化對(duì)象為值進(jìn)行注冊(cè)
        self._registry[model] = admin_class(model)

site = StarkSite()

四、在每個(gè)app下新建stark.py文件鸠补,通過調(diào)用StarkSite類來注冊(cè)model表

執(zhí)行每個(gè)app下的stark.py文件來注冊(cè)所有app下用戶提交的需要注冊(cè)的model表和對(duì)應(yīng)的config配置類

  • app01下的stark.py:
//導(dǎo)入默認(rèn)配置類ModelStark萝风,(這個(gè)類稍后再創(chuàng)建)
from stark.servers.site import ModelStark
//site是StarkSite類的實(shí)例化對(duì)象,通過模塊導(dǎo)入實(shí)現(xiàn)的單利模式
from stark.servers.site import site
//導(dǎo)入app01下的models文件
from app01 import models


//注冊(cè)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建立一對(duì)一的關(guān)系
    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="價(jià)格")
    # 與Publish建立一對(duì)多的關(guān)系,外鍵字段建立在多的一方
    publish = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.CASCADE,verbose_name='出版社')
    publishDate = models.DateField(verbose_name="出版日期",auto_created=True)
    # 與Author表建立多對(duì)多的關(guān)系,ManyToManyField可以建在兩個(gè)模型中的任意一個(gè)紫岩,自動(dòng)創(chuàng)建第三張表
    authors = models.ManyToManyField(to='Author',verbose_name='作者' )

    def __str__(self):
        return self.title

五规惰、設(shè)計(jì)Url

url的設(shè)計(jì)中,我們采用和django-admin相同的一下這種方式:
url('正則表達(dá)式'泉蝌,([ url列表 ]歇万,None,None)) 規(guī)則

  • 項(xiàng)目文件夾下的url路由分發(fā)
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):
        #定義一個(gè)字典用于存儲(chǔ)接下來需要注冊(cè)的model和對(duì)應(yīng)congfig配置類
        self._registry = {}

    def register(self, model, stark_class=None):
        # 設(shè)置配置類勋陪,有自定義的就用自定義的堕花,沒有就用默認(rèn)的ModelStark
        if not stark_class:
            stark_class = ModelStark
        #以model為鍵,配置類實(shí)例化對(duì)象為值進(jìn)行注冊(cè)
        self._registry[model] = stark_class(model)

    def get_urls(self):
        temp = []
        # self._registry是以model為鍵粥鞋,config_obj配置類實(shí)例化對(duì)象為值進(jìn)行注冊(cè)后的字典容器
        for model, config_obj in self._registry.items():
            # 通過模型表model的._meta方法得到動(dòng)態(tài)的、注冊(cè)的瞄崇、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為通過配置類對(duì)象調(diào)用配置類下的urls方法來得到相應(yīng)model表的增刪改查url
            #--->用到 了路由的二級(jí)分發(fā)
            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默認(rèn)配置類中添加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設(shè)置不同的name呻粹,后續(xù)需要通過反向解析找到對(duì)應(yīng)的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

代碼執(zhí)行到這里就為注冊(cè)的每張表生成了增、刪苏研、改等浊、查四個(gè)url
以上代碼段的執(zhí)行順序圖示:


0.png

六、接下來開始寫每個(gè)url對(duì)應(yīng)的視圖函數(shù)之查看頁面listview

listview中大概包含數(shù)據(jù)展示摹蘑、分頁筹燕、search查詢、action批量操作衅鹿、filter過濾撒踪、pop關(guān)聯(lián)等主要功能,接下來就將每一項(xiàng)內(nèi)容的設(shè)計(jì)思路和需要用到的知識(shí)點(diǎn)做一下分享大渤,具體的代碼太多了制妄,就不一一列舉:

接下來的所有邏輯將基于site.py下的ModelStark配置類完成:

注意:由于在StarkSite類中代碼的第一步就是判斷用戶有沒有自己stark_class配置類,所以通過這種方式來擴(kuò)展出來的用戶的自定義功能泵三,自己的配置類中有相應(yīng)的方法或者屬性就用自己的耕捞,沒有就用默認(rèn)的配置類中的衔掸。這都是基于用戶在自定義配置類時(shí)主動(dòng)繼承默認(rèn)配置類ModelStark。這個(gè)項(xiàng)目充分利用了python的面向?qū)ο箢惖睦^承俺抽、調(diào)用敞映、封裝等知識(shí)點(diǎn)。

在ModelStark類中有兩個(gè)貫穿全局的參數(shù):謹(jǐn)記謹(jǐn)記
self:當(dāng)前model模型表的配置類對(duì)象
self.model:當(dāng)前model模型表

- 展示數(shù)據(jù)的表頭部分

通過配置類對(duì)象中的new_list_display列表得到表頭的展示字段或者默認(rèn)顯示的操作名稱磷斧,將操作的具體邏輯封裝成函數(shù)來調(diào)用振愿,這其中用到了一下知識(shí)點(diǎn):

  • django.utils.safestring.mark_safe方法,在后端對(duì)html代碼段做safe處理瞳抓,不讓前端轉(zhuǎn)義html代碼
  • field_obj = model._meta.get_field('字段字符串') 通過字段字符串得到對(duì)應(yīng)的字段對(duì)象
  • model_name = model._meta.model_name 得到模型表對(duì)應(yīng)的表名
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

- 展示數(shù)據(jù)體部分

這部分的邏輯和表頭的設(shè)計(jì)很像埃疫,不同的有以下幾點(diǎn):

  • 數(shù)據(jù)的結(jié)構(gòu),數(shù)據(jù)體的數(shù)據(jù)對(duì)應(yīng)著model來看分為每條記錄和記錄中包含的字段信息孩哑,所有可以構(gòu)建出如下的數(shù)據(jù)結(jié)構(gòu):
    舉例:content_list=[['title','prince','publish'.....],['title','prince','publish'.....]......]栓霜,前端就可以通過兩次循環(huán)拿到數(shù)據(jù)
  • 一對(duì)多,多對(duì)多字段横蜒,這種字段需要單拿出來作數(shù)據(jù)的處理才能展示
  • 對(duì)于自定制的操作來說胳蛮,通過配置類對(duì)象中的list_display_links列表得到相應(yīng)的目標(biāo)字段,通過反向解析得到對(duì)應(yīng)的url丛晌,通過字符串處理后返回仅炊,

用到一下知識(shí)點(diǎn):

  • isinstance():判斷字段的所屬類型
  • getattr():通過反射得到字段的對(duì)應(yīng)內(nèi)容
  • 反向解析來獲取對(duì)應(yīng)操作的url路徑
def create_body(self):
    # 表內(nèi)容content_list=[[數(shù)據(jù)1],[數(shù)據(jù)2],[數(shù)據(jù)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

- 分頁功能

  • 分頁鏈接:http://www.reibang.com/p/e4ff742e6c60
  • 分頁主要的問題是必須要有url參數(shù)的保留功能,因?yàn)楹竺娴膄ilter和search需要通過聯(lián)合條件做查詢

用到的知識(shí)點(diǎn):

  • params= copy.deepcopy(request.GET)澎蛛,request.GET中的數(shù)據(jù)是字典類型的抚垄,但是不能修改,因?yàn)樵创a中對(duì)這個(gè)字典加了鎖谋逻,但是可以對(duì)它做深copy呆馁,copy之后數(shù)據(jù)就可以修改了
  • params.urlencode()方法可以直接將字典轉(zhuǎn)成url參數(shù)的固定格式(key1=value1&key2=value2)

- search查詢

通過配置類對(duì)象中的search_fields列表得到相應(yīng)的查詢字段

  • search查詢就是通過前端得到的數(shù)據(jù)在后端做數(shù)據(jù)庫的filter過濾

用到的知識(shí)點(diǎn):

  • ORM的內(nèi)置函數(shù)Q()的經(jīng)典用法
from django.db.models import Q
//實(shí)例化創(chuàng)建Q對(duì)象
search_condition = Q()
//對(duì)象.connector可以修改Q的查詢條件,默認(rèn)的是and條件
search_condition.connector = "or"
//通過.children.append向Q()對(duì)象中添加元組類型('field'毁兆,condition)的篩選條件
search_condition.children.append((f"{field}__icontains", condition))
//過濾數(shù)據(jù)
ret = queryset.filter(search_conditions)
  • field__icontains:ORM操作中的filte()模糊查詢

- action批量操作

  • 通過配置類對(duì)象中的actions_list列表得到對(duì)應(yīng)的批量操作處理函數(shù)

用到的知識(shí)點(diǎn):

  • action.short_description浙滤,python一切皆對(duì)象,都可以添加屬性气堕,
  • action.__ name__:得到函數(shù)名
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  //處理得到前端所需要的數(shù)據(jù)
//視圖處理函數(shù)纺腊,處理用戶發(fā)過來的請(qǐng)求,執(zhí)行相應(yīng)的方法操作
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表中的一對(duì)多和多對(duì)多的字段茎芭,通過filter來篩選出數(shù)據(jù)的交集揖膜,filter的設(shè)計(jì)主要分為三個(gè)方面:

  • 通過判斷model各字段的類型找出一對(duì)多和多對(duì)多的字段,布局前端頁面樣式
  • 設(shè)計(jì)url
  • 通過前端發(fā)過來的數(shù)據(jù)做filter篩選梅桩,返回篩選之后的數(shù)據(jù)

用到的知識(shí)點(diǎn):

  • rel_model = field_obj.rel.to:通過多對(duì)多或者一對(duì)多的字段得到對(duì)應(yīng)的模型表
//url設(shè)計(jì)代碼段
def filter_field_links(self):
    filter_links = {}

    for field in self.config_obj.list_filter:
        # 從get請(qǐng)求中得到需要的filter參數(shù)
        params = copy.deepcopy(self.request.GET)
        choice_field_pk = self.request.GET.get(field, 0)
        # 通過多對(duì)多或者一對(duì)多的字段得到對(duì)應(yīng)的模型表
        field_obj = self.config_obj.model._meta.get_field(field)
        rel_model = field_obj.rel.to

        # 得到模型表中的所有數(shù)據(jù)
        ret_model_queryset = rel_model.objects.all()
        tem = []
        for obj in ret_model_queryset:
            # 將對(duì)應(yīng)的對(duì)象pk值添加到字典中次氨,以當(dāng)前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關(guān)聯(lián)操作

在model字段中經(jīng)常存在著表與表的關(guān)聯(lián)摘投,所以在創(chuàng)建新數(shù)據(jù)或者修改數(shù)據(jù)時(shí)需要同時(shí)創(chuàng)建相關(guān)聯(lián)的數(shù)據(jù)煮寡,
業(yè)務(wù)邏輯:

  • 通過對(duì)form對(duì)象內(nèi)字段的類型判斷找到一對(duì)多和多對(duì)多的字段虹蓄,在字段中添加標(biāo)志位,用于前端的選擇性生成對(duì)應(yīng)的a標(biāo)簽鏈接
  • 通過反向解析得到一對(duì)多幸撕、多對(duì)多字段對(duì)應(yīng)表的add的url
  • 由于form表單生成的前端標(biāo)簽的id值是id_'字段名',所以后端需要將對(duì)應(yīng)的字段名字符串拼接一個(gè)pop_back_id薇组,用于前端DOM操作。
  • 前端通過事件綁定觸發(fā)window.open()打開一個(gè)新窗口用于添加關(guān)聯(lián)字段的數(shù)據(jù)
  • 數(shù)據(jù)提交成功后返回一個(gè)頁面坐儿,在頁面中通過js代碼將新添加的數(shù)據(jù)傳遞給父頁面律胀,再通過window.cloce()方法關(guān)閉子頁面
  • 父頁面通過DOM操作將新添加的數(shù)據(jù)通過DOM操作渲染到頁面的相應(yīng)位置。

執(zhí)行思路:


222.png

就先到這吧C部蟆L烤!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逛漫,一起剝皮案震驚了整個(gè)濱河市黑低,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酌毡,老刑警劉巖克握,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異枷踏,居然都是意外死亡菩暗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門旭蠕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來停团,“玉大人,你說我怎么就攤上這事掏熬∮映恚” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵孽江,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我番电,道長(zhǎng)岗屏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任漱办,我火速辦了婚禮这刷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娩井。我一直安慰自己暇屋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布洞辣。 她就那樣靜靜地躺著咐刨,像睡著了一般昙衅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上定鸟,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天而涉,我揣著相機(jī)與錄音,去河邊找鬼联予。 笑死啼县,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沸久。 我是一名探鬼主播季眷,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼卷胯!你這毒婦竟也來了子刮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤诵竭,失蹤者是張志新(化名)和其女友劉穎话告,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卵慰,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沙郭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裳朋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片病线。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鲤嫡,靈堂內(nèi)的尸體忽然破棺而出送挑,到底是詐尸還是另有隱情,我是刑警寧澤暖眼,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布惕耕,位于F島的核電站,受9級(jí)特大地震影響诫肠,放射性物質(zhì)發(fā)生泄漏司澎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一栋豫、第九天 我趴在偏房一處隱蔽的房頂上張望挤安。 院中可真熱鬧,春花似錦丧鸯、人聲如沸蛤铜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽围肥。三九已至剿干,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虐先,已是汗流浹背怨愤。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛹批,地道東北人撰洗。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像腐芍,于是被迫代替她去往敵國(guó)和親差导。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容