1. 程式人生 > >10.18正式開發stark元件*(三)

10.18正式開發stark元件*(三)

2018-10-18 19:15:54

等這個stark元件做完了再上傳到github上面,然後再整理部落格!這就到週末啦!

因為models匯入的時候出現bug,所以只有原始碼沒有測試資料!

原始碼都有註釋,已經很詳細啦! 一步一步能看懂!

裡面重要的思想就是 用類封裝,元件用模組封裝! 然後解耦!把重複的東西封裝成類!面向物件程式設計!

看原始碼也許是一種享受!

越努力,與幸運!永遠不要高估自己!

新增了 搜尋框!和批量操作!

批量操作沒有預設的刪除可以迭代自行完成!批量操作需要使用者自己定製函式

先放上用到的新的知識點!

# Q 查詢的兩種方式
# Book.objects.filter(Q(title="yuan")|Q(price=123))
# # 第二種方式可以傳入字串 # q=Q() # q.connection="or" # q.children.append(("title","yuan")) # q.children.append(("price",123)) # 模糊查詢 # ret=self.model.objects.filter(title__startswith="py") # ret=self.model.objects.filter(price__in=[12,34,56,78,222]) # ret=self.model.objects.filter(price__range=[10,100])
# ret=self.model.objects.filter(title__contains="o") # ret=self.model.objects.filter(title__icontains="o") # print(ret)


分頁的元件(只要傳好引數就可以直接用)

utils/page.py

import copy


# 自定義分頁元件   注意引數
class Pagination(object):
    def __init__(self, current_page, all_count, base_url, params, per_page_num=8, pager_count=11, ):
        
""" 封裝分頁相關資料 :param params: 接收url裡面的引數也就是鍵值對?xx=2&xx=3&xx=4 類似於這樣的 :param current_page: 當前頁 :param all_count: 資料庫中的資料總條數 :param per_page_num: 每頁顯示的資料條數 :param base_url: 分頁中顯示的URL字首 :param pager_count: 最多顯示的頁碼個數 """ try: current_page = int(current_page) except Exception as e: current_page = 1 if current_page < 1: current_page = 1 self.current_page = current_page self.all_count = all_count self.per_page_num = per_page_num self.base_url = base_url # 總頁碼 all_pager, tmp = divmod(all_count, per_page_num) if tmp: all_pager += 1 self.all_pager = all_pager self.pager_count = pager_count # 最多顯示頁碼數 self.pager_count_half = int((pager_count - 1) / 2) params = copy.deepcopy(params) params._mutable = True self.params = params # self.params : {"page":77,"title":"python","nid":1} @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num def page_html(self): # 如果總頁碼 < 11個: if self.all_pager <= self.pager_count: pager_start = 1 pager_end = self.all_pager + 1 # 總頁碼 > 11 else: # 當前頁如果<=頁面上最多顯示(11-1)/2個頁碼 if self.current_page <= self.pager_count_half: pager_start = 1 pager_end = self.pager_count + 1 # 當前頁大於5 else: # 頁碼翻到最後 if (self.current_page + self.pager_count_half) > self.all_pager: pager_start = self.all_pager - self.pager_count + 1 pager_end = self.all_pager + 1 else: pager_start = self.current_page - self.pager_count_half pager_end = self.current_page + self.pager_count_half + 1 page_html_list = [] self.params["page"] = 1 first_page = '<li><a href="%s?%s">首頁</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(first_page) if self.current_page <= 1: prev_page = '<li class="disabled"><a href="#">上一頁</a></li>' else: self.params["page"] = self.current_page - 1 prev_page = '<li><a href="%s?%s">上一頁</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(prev_page) for i in range(pager_start, pager_end): # self.params : {"page":77,"title":"python","nid":1} # 既保留每次迴圈的page 有保留了後面引數的值 # urlencode()是內建方法 把鍵值拼成url ?page=1&xx=22 類似於這樣的 # 這樣做法僅僅變頁碼page 然後後面引數也就是條件不變 self.params["page"] = i # {"page":72,"title":"python","nid":1} if i == self.current_page: temp = '<li class="active"><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,) else: temp = '<li><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,) page_html_list.append(temp) if self.current_page >= self.all_pager: next_page = '<li class="disabled"><a href="#">下一頁</a></li>' else: self.params["page"] = self.current_page + 1 next_page = '<li><a href="%s?%s">下一頁</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(next_page) self.params["page"] = self.all_pager last_page = '<li><a href="%s?%s">尾頁</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(last_page) return ''.join(page_html_list) # class Pagination(object): # # def __init__(self, data_num, current_page, url_prefix,params, per_page=10, max_show=4): # """ # 進行初始化. # :param data_num: 資料總數 # :param current_page: 當前頁 # :param url_prefix: 生成的頁碼的連結字首 # :param per_page: 每頁顯示多少條資料 # :param max_show: 頁面最多顯示多少個頁碼 # """ # self.data_num = data_num # self.per_page = per_page # self.max_show = max_show # self.url_prefix = url_prefix # # # 把頁碼數算出來 # self.page_num, more = divmod(data_num, per_page) # if more: # self.page_num += 1 # # try: # self.current_page = int(current_page) # except Exception as e: # self.current_page = 1 # # 如果URL傳過來的頁碼數是負數 # if self.current_page <= 0: # self.current_page = 1 # # 如果URL傳過來的頁碼數超過了最大頁碼數 # elif self.current_page > self.page_num: # self.current_page = self.page_num # 預設展示最後一頁 # # # 頁碼數的一半 算出來 # self.half_show = max_show // 2 # # # 頁碼最左邊顯示多少 # if self.current_page - self.half_show <= 1: # self.page_start = 1 # self.page_end = self.max_show # elif self.current_page + self.half_show >= self.page_num: # 如果右邊越界 # self.page_end = self.page_num # self.page_start = self.page_num - self.max_show # else: # self.page_start = self.current_page - self.half_show # # 頁碼最右邊顯示 # self.page_end = self.current_page + self.half_show # # # import copy # self.params=copy.deepcopy(params) # {"page":"12","title_startwith":"py","id__gt":"5"} # # # # @property # def start(self): # # 資料從哪兒開始切 # return (self.current_page - 1) * self.per_page # # @property # def end(self): # # 資料切片切到哪兒 # return self.current_page * self.per_page # # def page_html(self): # # 生成頁碼 # l = [] # # 加一個首頁 # l.append('<li><a href="{}?page=1">首頁</a></li>'.format(self.url_prefix)) # # 加一個上一頁 # if self.current_page == 1: # l.append('<li class="disabled" ><a href="#">«</a></li>'.format(self.current_page)) # else: # l.append('<li><a href="{}?page={}">«</a></li>'.format(self.url_prefix, self.current_page - 1)) # # # # # {"page":"12","title_startwith":"py","id__gt":"5"} # "page=12&title_startwith=py&id__gt=5" # # # print(self.params.urlencode()) # for i in range(self.page_start, self.page_end + 1): # self.params["page"]=i # # {"page":"7","title_startwith":"py","id__gt":"5"} # "page=7&title_startwith=py&id__gt=5" # if i == self.current_page: # tmp = '<li class="active"><a href="{0}?page={1}">{1}</a></li>'.format(self.url_prefix, i) # else: # tmp = '<li><a href="{0}?{1}">{2}</a></li>'.format(self.url_prefix, self.params.urlencode(),i) # l.append(tmp) # # # # # # # # # 加一個下一頁 # if self.current_page == self.page_num: # l.append('<li class="disabled"><a href="#">»</a></li>'.format(self.current_page)) # else: # l.append('<li><a href="{}?page={}">»</a></li>'.format(self.url_prefix, self.current_page + 1)) # # 加一個尾頁 # l.append('<li><a href="{}?page={}">尾頁</a></li>'.format(self.url_prefix, self.page_num)) # return "".join(l)

app01/models.py

from django.db import models

# Create your models here.


from django.db import models

# Create your models here.
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.DateField()
    telephone=models.BigIntegerField()
    addr=models.CharField( max_length=64)

    def __str__(self):
        return self.telephone



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)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    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

server/server.py

from django.conf.urls import url
from django.shortcuts import render, redirect
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.forms import ModelForm
from stark.utils.page import Pagination
from django.db.models import Q


class ShowList(object):
    # 這是一個配置類的物件初始化
    def __init__(self, config, data_list, request):
        self.config = config
        self.data_list = data_list
        self.request = request
        # 分頁
        data_count = self.data_list.count()
        current_page = int(self.request.GET.get("page", 1))
        base_path = self.request.path
        self.pagination = Pagination(current_page, data_count, base_path, self.request.GET, per_page_num=3, pager_count=11,)
        self.page_data = self.data_list[self.pagination.start:self.pagination.end]
        # actions   獲取actions這個配置類的列表
        self.actions = self.config.actions  # [patch_init,]

    # 獲取下拉框 使用者配置的action_list
    def get_action_list(self):
        temp = []
        for action in self.actions:
            #  [{"name":""patch_init,"desc":"批量初始化"}]
           temp.append({
               "name": action.__name__,
               "desc": action.short_description
           })
        return temp

    # 構建表頭
    def get_header(self):
        header_list = []
        print("header", self.config.new_list_play())
        # [checkbox,"pk","name","age",edit ,deletes]     【checkbox ,"__str__", edit ,deletes】
        for field in self.config.new_list_play():

            if callable(field):
                # header_list.append(field.__name__)
                val = field(self.config, header=True)
                header_list.append(val)

            else:
                if field == "__str__":
                    header_list.append(self.config.model._meta.model_name.upper())
                else:
                    # header_list.append(field)
                    val = self.config.model._meta.get_field(field).verbose_name
                    header_list.append(val)
        return header_list

    # 構建表單資料
    def get_body(self):
        new_data_list = []
        for obj in self.page_data:
            temp = []
            for filed in self.config.new_list_play():  # ["__str__",]      ["pk","name","age",edit]
                if callable(filed):
                    val = filed(self.config, obj)
                else:
                    val = getattr(obj, filed)
                    if filed in self.config.list_display_links:
                        # "app01/userinfo/(\d+)/change"
                        _url = self.config.get_change_url(obj)

                        val = mark_safe("<a href='%s'>%s</a>" % (_url, val))
                temp.append(val)
            new_data_list.append(temp)
        return new_data_list


class ModelStark(object):
    # 預設的list_play[]
    list_display = ["__str__", ]
    list_display_links = []
    modelform_class = None
    search_fields = []
    actions = []

    def __init__(self, model, site):
        self.model = model
        self.site = site

    # 配置表頭: 刪除 編輯,複選框
    def edit(self, obj=None, header=False):
        """編輯"""
        if header:
            return "操作"
        # return mark_safe("<a href='%s/change'>編輯</a>"%obj.pk)
        _url = self.get_change_url(obj)
        return mark_safe("<a href='%s'>編輯</a>" % _url)

    def deletes(self, obj=None, header=False):
        """刪除"""
        if header:
            return "操作"
        # return mark_safe("<a href='%s/change'>編輯</a>"%obj.pk)
        _url = self.get_delete_url(obj)
        return mark_safe("<a href='%s'>刪除</a>" % _url)

    def checkbox(self, obj=None, header=False):
        """複選框"""
        if header:
            return mark_safe('<input id="choice" type="checkbox">')
        # value的值不能寫死,
        return mark_safe('<input class="choice_item" type="checkbox" name="selected_pk" value="%s">' % obj.pk)

    # 獲取配置類的表頭資訊
    def get_modelform_class(self):
        """獲取表的配置類"""
        if not self.modelform_class:
            # 如果表的配置類為空
            class ModelFormDemo(ModelForm):
                class Meta:
                    model = self.model
                    fields = "__all__"
                    labels = {
                        ""
                    }
            return ModelFormDemo
        else:
            return self.modelform_class

    # 新增的檢視函式
    def add_view(self, request):
        ModelFormDemo = self.get_modelform_class()
        if request.method == "POST":
            form = ModelFormDemo(request.POST)
            if form.is_valid():
                form.save()
                return redirect(self.get_list_url())

            return render(request, "add_view.html", locals())

        form = ModelFormDemo()

        return render(request, "add_view.html", locals())

    # 刪除的檢視函式
    def delete_view(self, request, id):
        url = self.get_list_url()
        if request.method == "POST":
            self.model.objects.filter(pk=id).delete()
            return redirect(url)
        return render(request, "delete_view.html", locals())

    # 編輯的檢視函式
    def change_view(self, request, id):
        ModelFormDemo = self.get_modelform_class()
        edit_obj = self.model.objects.filter(pk=id).first()
        if request.method == "POST":
            form = ModelFormDemo(request.POST, instance=edit_obj)
            if form.is_valid():
                form.save()
                return redirect(self.get_list_url())
            return render(request, "add_view.html", locals())
        form = ModelFormDemo(instance=edit_obj)
        return render(request, "change_view.html", locals())

    # 搜尋的檢視函式
    def get_serach_conditon(self, request):
        key_word = request.GET.get("q", "")
        self.key_word = key_word
        search_connection = Q()
        if key_word:
            # self.search_fields # ["title","price"]
            search_connection.connector = "or"
            # 用Q的這種新增方法可以新增字串
            for search_field in self.search_fields:
                # search_field+"__contains"  ---->  title__contains="o"   就是title欄位裡面包含字母o的
                search_connection.children.append((search_field + "__contains", key_word))
        return search_connection

    # 檢視的檢視函式
    def list_view(self, request):
        if request.method == "POST":  # action
            print("POST:", request.POST)
            action = request.POST.get("action")
            selected_pk = request.POST.getlist("selected_pk")
            # 通過getattr()傳入函式名的字串和呼叫該函式的物件 拿到該函式的變數
            action_func = getattr(self, action)
            queryset = self.model.objects.filter(pk__in=selected_pk)
            # 把request和queryset物件傳給函式,然後執行,最後return給使用者配置的那個函式
            ret = action_func(request, queryset)
            return ret
        # 獲取search的Q物件
        search_connection = self.get_serach_conditon(request)
        # 篩選獲取當前表所有資料
        data_list = self.model.objects.all().filter(search_connection)     # 【obj1,obj2,....】
        # 按這ShowList展示頁面
        showlist=ShowList(self, data_list, request)
        # 構建一個檢視URL
        add_url = self.get_add_url()
        return render(request, "list_view.html", locals())

    #  獲取使用者配置類裡面的list_play[]
    def new_list_play(self):
        temp = []
        temp.append(ModelStark.checkbox)
        temp.extend(self.list_display)
        if not self.list_display_links:
            temp.append(ModelStark.edit)
        temp.append(ModelStark.deletes)
        return temp

    """把url進行反向解析,解耦到各自的函式中,函式中直接返回了對應的url"""
    # 獲取修改頁面的url
    def get_change_url(self, obj):
        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        _url = reverse("%s_%s_change" % (app_label, model_name), args=(obj.pk,))

        return _url

    # 獲刪除改頁面的url
    def get_delete_url(self, obj):
        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        _url = reverse("%s_%s_delete" % (app_label, model_name), args=(obj.pk,))

        return _url

    # 獲取新增頁面的url
    def get_add_url(self):

        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        _url = reverse("%s_%s_add" % (app_label, model_name))

        return _url

    # 獲取檢視頁面的url
    def get_list_url(self):

        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        _url = reverse("%s_%s_list" % (app_label, model_name))

        return _url

    # 二級url分發函式
    def get_urls_2(self):

        temp = []

        model_name = self.model._meta.model_name
        app_label = self.model._meta.app_label

        temp.append(url(r"^add/", self.add_view, name="%s_%s_add" % (app_label, model_name)))
        temp.append(url(r"^(\d+)/delete/", self.delete_view, name="%s_%s_delete" % (app_label, model_name)))
        temp.append(url(r"^(\d+)/change/", self.change_view, name="%s_%s_change" % (app_label, model_name)))
        temp.append(url(r"^$", self.list_view, name="%s_%s_list" % (app_label, model_name)))

        return temp

    @property
    def urls_2(self):
        print(self.model)
        return self.get_urls_2(), None, None


class StarkSite(object):
    def __init__(self):
        self._registry = {}

    def register(self, model, stark_class=None):
        if not stark_class:
            stark_class = ModelStark

        self._registry[model] = stark_class(model, self)

    # 一級分發url函式
    def get_urls(self):
        temp = []
        for model, stark_class_obj in self._registry.items():
            model_name = model._meta.model_name
            app_label = model._meta.app_label
            # 分發增刪改查
            temp.append(url(r"^%s/%s/" % (app_label, model_name), stark_class_obj.urls_2))

            '''
            url(r"^app01/userinfo/",UserConfig(Userinfo).urls_2),
            url(r"^app01/book/",ModelStark(Book).urls_2), 


            '''
        return temp

    @property
    def urls(self):

        return self.get_urls(), None, None


# 建立stark的一個單例物件
site = StarkSite()

list.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css">
    <script src="/static/js/jquery-1.12.4.min.js"></script>
</head>
<body>

<h4>資料列表</h4>


<div class="container">
    <div class="row">
        <div class="col-md-9">
            <a href="{{ add_url }}" class="btn btn-primary">新增資料</a>

            {% if showlist.config.search_fields %}
                     <form action="" class="pull-right">
                        <input type="text" name="q" value="{{ showlist.config.key_word }}"><button>submit</button>
                    </form>
            {% endif %}

            <form action="" method="post">
                    {% csrf_token %}
                    <select name="action" id="" style="width: 200px;padding: 5px 8px;display: inline-block">
                        {% for item in showlist.get_action_list %}
                            <option value="">---------------</option>
                            <option value="{{ item.name }}">{{ item.desc }}</option>
                        {% endfor %}

                    </select><button type="submit" class="btn btn-info">Go</button>
                    <table class="table table-bordered table-striped">
                        <thead>
                             <tr>
                                 {% for item in showlist.get_header %}
                                   <th>{{ item }}</th>
                                 {% endfor %}


                             </tr>

                        </thead>
                        <tbody>
                             {% for data in showlist.get_body %}

                              <tr>
                                  {% for item in data %}
                                   <td>{{ item }}</td>
                                  {% endfor %}

                              </tr>
                             {% endfor %}

                        </tbody>
                    </table>
                        <nav class="pull-right">
                            <ul class="pagination">
                               {{ showlist.pagination.page_html|safe }}
                            </ul>
                        </nav>

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



<script>

   $("#choice").click(function () {

     if($(this).prop("checked")){
           $(".choice_item").prop("checked",true)
     }else {
           $(".choice_item").prop("checked",false)
     }

   })

</script>
</body>
</html>

app01/server.py

from stark.service.stark import site, ModelStark
from django.shortcuts import HttpResponse
from .models import *
from django.forms import ModelForm


class BookModelForm(ModelForm):
    class Meta:
        model = Book
        fields = "__all__"
        labels = {
            "title": "書籍名稱",
            "price": "價格"
        }


# Book表的配置類
class BookConfig(ModelStark):
    # 自定義顯示欄位
    list_display = ["title", "price", "publishDate"]
    modelform_class = BookModelForm
    # 自定義搜尋欄位
    search_fields = ["title", "price"]

    # 自定義action函式,在下拉框中顯示
    def patch_init(self, request, queryset):
        print(queryset, request)
        queryset.update(price=123)
        return HttpResponse("批量初始化OK")

    patch_init.short_description = "批量初始化"
    actions = [patch_init]


site.register(Book,BookConfig)
site.register(Publish)
site.register(Author)
site.register(AuthorDetail)

這個stark完完全仿照的admin !並且仿照的很成功!