【django之stark组件】

1、需求

仿照django的admin,开发本身的stark组件。实现相似数据库客户端的功能,对数据进行增删改查。javascript

 

2、实现

一、在settings配置中分别注册这三个app

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'app02.apps.App02Config',
    'stark.apps.StarkConfig',
]

注:python manage.py startapp app02     建立新项目php

 

二、在app01和app02的models文件中建立数据类

from django.db import models

# Create your models here.
from django.contrib.auth.models import AbstractUser

class UserInfo(models.Model):
    """
    用户信息
    """
    nid = models.AutoField(primary_key=True)
    nickname = models.CharField(verbose_name='昵称', max_length=32)
    telephone = models.CharField(max_length=11, null=True, unique=True)
    avatar = models.FileField(upload_to = 'avatars/',default="/avatars/default.png")
    create_time = models.DateTimeField(verbose_name='建立时间', auto_now_add=True)
    blog = models.OneToOneField(to='Blog', to_field='nid',null=True)

    def __str__(self):
        return self.nickname



class Blog(models.Model):

    """
    博客信息
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='我的博客标题', max_length=64)
    site = models.CharField(verbose_name='我的博客后缀', max_length=32, unique=True)
    theme = models.CharField(verbose_name='博客主题', max_length=32)
    #
    # def __str__(self):
    #     return self.title
class Category(models.Model):
    """
    博主我的文章分类表
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='分类标题', max_length=32)
    blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid')

    def __str__(self):
        return self.title
class Tag(models.Model):

    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='标签名称', max_length=32)
    blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid')
    def __str__(self):
        return self.title
class Article(models.Model):

    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=50, verbose_name='文章标题')
    desc = models.CharField(max_length=255, verbose_name='文章描述')

    comment_count= models.IntegerField(default=0)
    up_count = models.IntegerField(default=0)
    down_count = models.IntegerField(default=0)

    create_time = models.DateTimeField(verbose_name='建立时间')

    homeCategory = models.ForeignKey(to='Category', to_field='nid', null=True)
    #siteDetaiCategory = models.ForeignKey(to='SiteCategory', to_field='nid', null=True)

    user = models.ForeignKey(verbose_name='做者', to='UserInfo', to_field='nid')
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
    )


    def __str__(self):
        return self.title
class ArticleDetail(models.Model):
    """
    文章详细表
    """
    nid = models.AutoField(primary_key=True)
    content = models.TextField()
    article = models.OneToOneField(to='Article', to_field='nid')

class Article2Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid')
    tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid')

    class Meta:
        unique_together = [
            ('article', 'tag'),
        ]

    def __str__(self):
        v=self.article.title+"----"+self.tag.title
        return v
app01/models.py

 

from django.db import models

# Create your models here.
class Book(models.Model):
    title=models.CharField(max_length=32,verbose_name="标题")
app02/models.py

 

python manage.py makemigrations
python manage.py migrate

 

三、扫描(加载)每个app下的stark.py 

在app01和app02下分别建立一个stark.py文件,在项目启动时扫描每一个app下的stark.py文件并执行css

即在stark的apps.py中配置html

from django.apps import AppConfig

from django.utils.module_loading import autodiscover_modules
class StarkConfig(AppConfig):
    name = 'stark'

    def ready(self):
        autodiscover_modules('stark')   #自动扫描

 

四、注册

仿照admin设置相关类,首先建立下面的文件前端

在执行admin.py文件时咱们发现其实第一步就是导入admin,导入时经过单例模式生成了一个site对象,如今咱们也来写一个类,生成一个单例对象java

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

在app01和app02的stark.py文件中导入python

from stark.service.stark import site

这样咱们也就获得了一个单例对象site,在注册时admin使用的是site对象的register方法,咱们也学着他写一个register方法jquery

class StarkSite(object):

    def __init__(self):
        self._registry={}

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

        self._registry[model]=modle_stark(model)
site = StarkSite()

这个方法的本质其实就是往self._registry这个字典中添加键值对,键就是咱们的数据类(如Book类),值是一个类的对象,这个类就是咱们要建立的第二个类,样式类linux

class ModelStark(object):

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

self.model指的是什么?   就是用户访问的modelgit

经过这个类咱们控制页面展现的内容和样式

作完这几步咱们就能够在app01和app02的stark.py文件中开始注册了

#app01

from
stark.service.stark import site from .models import * site.register(UserInfo,UserInfoConfig) site.register(Blog) site.register(Article) site.register(Category) site.register(Tag)



#app02
from stark.service.stark import site
from .models import *

site.register(Book,BookConfig)


注册完成后,咱们的site._registry字典中就有了咱们注册类对应的键值对,接下来就要配置url了

 

五、url配置

admin中的url配置

from django.conf.urls import url
from django.contrib import admin


urlpatterns = [
    url(r'^admin/', admin.site.urls),

]

能够看到全部的url都是在admin.site.urls这个方法中生成的,我能够看看这个方法的源码

@property
def urls(self):
    return self.get_urls(), 'admin', self.name

其实就是作了一个分发,url是在self.get_urls()这个函数中生成的,接着看这个函数的主要代码

def get_urls(self):
        from django.conf.urls import url, include
        # Since this module gets imported in the application's root package,
        # it cannot import models from other applications at the module level,
        # and django.contrib.contenttypes.views imports ContentType.
        from django.contrib.contenttypes import views as contenttype_views

        def wrap(view, cacheable=False):
            def wrapper(*args, **kwargs):
                return self.admin_view(view, cacheable)(*args, **kwargs)
            wrapper.admin_site = self
            return update_wrapper(wrapper, view)

        # Admin-site-wide views.
        urlpatterns = [
            url(r'^$', wrap(self.index), name='index'),
            url(r'^login/$', self.login, name='login'),
            url(r'^logout/$', wrap(self.logout), name='logout'),
            url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
            url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),
                name='password_change_done'),
            url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
            url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),
                name='view_on_site'),
        ]

        # Add in each model's views, and create a list of valid URLS for the
        # app_index
        valid_app_labels = []
        for model, model_admin in self._registry.items():
            urlpatterns += [
                url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
            ]
            if model._meta.app_label not in valid_app_labels:
                valid_app_labels.append(model._meta.app_label)

        # If there were ModelAdmins registered, we should have a list of app
        # labels for which we need to allow access to the app_index view,
        if valid_app_labels:
            regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
            urlpatterns += [
                url(regex, wrap(self.app_index), name='app_list'),
            ]
        return urlpatterns

 这里咱们须要知道到是咱们生成的url的格式都是admin/app名/表名,因此咱们要想办法取到app名和表名拼接起来

for model, model_admin in self._registry.items():
    urlpatterns += [
        url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
    ]

这里的model就是咱们的数据类(如Book),如何经过他取到咱们想要的呢

model._meta.app_label 取类所在的app名

model._meta.model_name 取类的名字

这样咱们就成功拼接出了咱们要的url,可是每一个url下又有增删改查不一样的url,这时又要再次进行分发,admin中使用了include方法,经过model_admin咱们注册时样式类生成的对象下的url方法获得咱们想要的

def get_urls(self):
    from django.conf.urls import url
    def wrap(view):
        def wrapper(*args, **kwargs):
            return self.admin_site.admin_view(view)(*args, **kwargs)
        wrapper.model_admin = self
        return update_wrapper(wrapper, view)
    
   info
= self.model._meta.app_label, self.model._meta.model_name urlpatterns = [ url(r'^$', wrap(self.changelist_view), name='%s_%s_changelist' % info), url(r'^add/$', wrap(self.add_view), name='%s_%s_add' % info), url(r'^(.+)/history/$', wrap(self.history_view), name='%s_%s_history' % info), url(r'^(.+)/delete/$', wrap(self.delete_view), name='%s_%s_delete' % info), url(r'^(.+)/change/$', wrap(self.change_view), name='%s_%s_change' % info), # For backwards compatibility (was the change url before 1.9) url(r'^(.+)/$', wrap(RedirectView.as_view( pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info) ))), ] return urlpatterns @property def urls(self): return self.get_urls()

其实和以前同样,只是作了又一次分发,而且对应了视图函数,这里咱们先不看视图函数的内容,值得注意的是这一次的分发和视图函数都是写在样式类中的,而不是写在生成site的AdminStie类中

这样有什么好处呢,咱们知道当咱们要注册时,是能够本身定义一些属性的,其实要显示的页面也是能够本身定义的,因此讲这最后一层url分发和对应的函数写在样式类中能够方便咱们进行自定义

看完了admin的作法,咱们能够来写咱们本身的代码了。

 

stark配置

首先在urls文件中配置

from django.conf.urls import url
from django.contrib import admin
from stark.service.stark import site

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^stark/', site.urls),
]

 而后在咱们建立的两个类中添加相关的代码,这里url对应的函数咱们先简写

from django.conf.urls import url
from django.shortcuts import HttpResponse, render


class ModelStark(object):

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

    def change_list(self, request):
        ret = self.model.objects.all()
        return render(request, "stark/change_list.html", locals())

    def add_view(self, request):
        return HttpResponse("add_view")

    def del_view(self, request, id):
        return HttpResponse("del_view")

    def change_view(self, request, id):
        return HttpResponse("change_view")

    def get_url_func(self):
        temp = []
        temp.append(url("^$", self.change_list))
        temp.append(url("^add/$", self.add_view))
        temp.append(url("^(\d+)/delete/$", self.del_view))
        temp.append(url("^(\d+)/change/$", self.change_view))
        return temp

    @property
    def urls(self):
        return self.get_url_func(), None, None


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

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

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

    def get_urls(self):
        temp = []
        for model, model_config in self._registry.items():
            model_name = model._meta.model_name
            app_label = model._meta.app_label
            u = url("^%s/%s/" % (app_label, model_name), model_config.urls)
            temp.append(u)
        return temp

    @property
    def urls(self):
        return self.get_urls(), None, None
    
site = StarkSite()

反向解析,别名的使用

在设置url对应的视图函数时,咱们能够给这个url添加一个别名,在使用时能够经过这个别名来反向生成url,这样即便url有修改,这样别名不变咱们都不须要修改代码

增长别名时要注意,因为每一个数据类咱们都生成了增删改查4条url,因此在写别名时应该有些区别,否则会引发混淆,因此咱们设计别名的格式为app名_表名_*

def get_url_func(self):
    temp = []
    model_name = self.model._meta.model_name
    app_label = self.model._meta.app_label
    app_model = (app_label, model_name)
    temp.append(url("^$", self.change_list, name="%s_%s_list" % app_model))
    temp.append(url("^add/$", self.add_view, name="%s_%s_add" % app_model))
    temp.append(url("^(\d+)/delete/$", self.del_view, name="%s_%s_delete" % app_model))
    temp.append(url("^(\d+)/change/$", self.change_view, name="%s_%s_change" % app_model))
    return temp
@property
def urls(self):
    return self.get_url_func(), None, None

 

六、列表展现页面

url设计完成后,咱们就须要来设计每一个url对应的页面了,咱们注意到,其实无论是访问哪张表,增删改查都只对应相同的四个视图函数,那么应该如何区分咱们访问的表呢

在样式类ModelStark中,咱们定义了self.model,这里的model其实就是咱们访问表的数据类,经过他咱们就能拿到咱们须要的数据显示到页面上,访问不一样的表时这个model是不一样的,这时就作到了访问什么表显示什么表的内容

 

list_display

在使用admin时,默认给咱们展现的是一个个的类对象,当咱们想要看到其它内容时,能够经过list_display属性设置

from django.contrib import admin
from .models import *
# Register your models here.

admin.site.register(UserInfo)


class RoleConfig(admin.ModelAdmin):
    list_display = ["id", "title"]
admin.site.register(Role, RoleConfig)

经过上面的方法,在访问admin页面时点击Role表就能看到id和title两个字段的内容了,如今咱们也来仿照admin写一个list_display属性

首先,这个属性应该是能够自定制的,若是用户没有定制,那么他应该有一个默认值,因此咱们能够在ModelStark样式类中先本身定义一个list_display静态属性

class ModelStark(object):
    list_display = []

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

若是用户须要定制他,能够在app对应的stark.py文件中作以下配置

class BookConfig(ModelStark):
    list_display = ["id", "title", "price"]
site.register(Book, BookConfig)

这里咱们写在list_display中的内容都是表中有的字段,其实里面还能够写咱们本身定义的函数,用来将咱们本身须要的内容显示到页面上

from stark.service.sites import site, ModelStark
from .models import *
from django.utils.safestring import mark_safe


class BookConfig(ModelStark):

    def edit(self, obj=None, is_header=False):
        if is_header:
            return "操做"
        return mark_safe("<a href='/stark/app01/book/%s/change'>编辑</a>" % obj.pk)

    def delete(self, obj=None, is_header=False):
        if is_header:
            return "操做"
        return mark_safe("<a href='/stark/app01/book/%s/delete'>删除</a>" % obj.pk)
    list_display = ["id", "title", "price", edit, delete]
site.register(Book, BookConfig)


class AuthorConfig(ModelStark):
    list_display = ["name", "age"]
site.register(Author)

这里咱们增长了编辑和删除两个函数,能够看到他们的返回值是一个a标签,这样就能够在页面上显示一个能够点击的编辑和删除,这里的mark_safe和前端渲染时用的safe是同样的功能,可使标签正确的显示在页面上

这样咱们就可让页面显示成下面的样子

当咱们处理列表页面对应的函数时就能够拿到list_display的值,再经过self.model取到对应的数据对象,从对象中拿到咱们想要的数据,放到页面上进行显示

class ModelStark(object):
    list_display = []

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

    def change_list(self, request):
        # 生成表标头
        header_list = []
        for field in self.list_display:
            if callable(field):
                # header_list.append(field.__name__)
                val = field(self, is_header=True)
                header_list.append(val)
            else:
                field_obj = self.model._meta.get_field(field)
                header_list.append(field_obj.verbose_name)

        # 生成表数据列表
        data_list = self.model.objects.all()
        new_data_list = []
        for obj in data_list:
            temp = []
            for field in self.list_display:
                if callable(field):
                    val = field(self, obj)
                else:
                    val = getattr(obj, field)
                temp.append(val)
            new_data_list.append(temp)
        return render(request, "stark/change_list.html", locals())

 

表头数据

首先,咱们要生成表头,表头的内容应该根据list_display中写到的内容进行显示,这里要注意,若是咱们在stark.py里本身写了样式类,那么list_display会优先从咱们本身写的样式类中取,若是里面没有才会找到ModelStark中的

取到list_display的值后咱们对他进行循环,若是值为可调用的,说明值为一个函数,那么咱们就执行函数,取到咱们要的结果,这里要注意执行函数时,咱们给函数传了一个is_header=True,说明咱们此次是取表头,在函数中咱们给这个参数定义一个默认值为False

进入函数时,首先对他进行判断,若是为True,那么咱们直接返回一个表头的信息就好了

class BookConfig(ModelStark):

    def edit(self, obj=None, is_header=False):
        if is_header:
            return "操做"
        return mark_safe("<a href='/stark/app01/book/%s/change'>编辑</a>" % obj.pk)

    def delete(self, obj=None, is_header=False):
        if is_header:
            return "操做"
        return mark_safe("<a href='/stark/app01/book/%s/delete'>删除</a>" % obj.pk)
    list_display = ["id", "title", "price", edit, delete]
site.register(Book, BookConfig)

上面的内容能够看到咱们返回的表头内容为操做

若是咱们循环list_display获得的值是一个字符串,那么说明这应该是表中的一个字段,这时咱们能够经过self.model._meta.get_field(字段名)的方法取到这个字段的对象,这个对象有一个verbose_name的属性,这个属性是用来描述一个字段的,在models中能够进行定义

class Book(models.Model):
    title = models.CharField(verbose_name="标题", max_length=32)
    price = models.DecimalField(verbose_name="价格", decimal_places=2, max_digits=5, default=12)

    def __str__(self):
        return self.title

咱们能够经过self.model._meta.get_field(字段名).verbose_name取到这个属性,将他做为表头,若是没有定义这个属性,那么默认值为字段名

 

表内容数据

取表内容数据时,和表头同样要作判断,判断list_display中的每个值,若是是可调用的就执行函数取值,这里执行时,咱们要将对应的数据对象传进去,这样在生成url时才能使用相关的id值

若是这个值是一个字符串,那么咱们能够经过反射,取到数据对象中的值,最后将这些值组成下面形式的数据格式发给前端渲染

'''
[
  [1, "python", 12],
  [2, "linux", 12],
  [3,"php"], 12        

]
'''

 

前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
</head>
<body>
<h3>数据展现</h3>

<div class="container">
    <div class="row">
        <div class="col-md-8">
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        {% for foo in header_list %}
                        <td>{{ foo }}</td>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody>
                    {% for data in new_data_list %}
                        <tr>
                            {% for item in data %}
                            <td>{{ item }}</td>
                            {% endfor %}
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

 

 添加checkbox选择框

在使用admin时能够看到展现页面上每条记录前都有一个选择框,能够选择多条记录进行批量操做,咱们也给咱们的组件增长这一功能,其实实现方法和编辑按钮相似

咱们先本身定义一个checkbox函数,返回一个checkbox类型的input标签,而后将这个函数添加到list_display中便可

class BookConfig(ModelStark):

    def edit(self, obj=None, is_header=False):
        if is_header:
            return "操做"
        return mark_safe("<a href=%s>编辑</a>" % reverse("%s_%s_change" % self.app_model, args=(obj.pk,)))

    def delete(self, obj=None, is_header=False):
        if is_header:
            return "操做"
        return mark_safe("<a href=%s>删除</a>" % reverse("%s_%s_delete" % self.app_model, args=(obj.pk,)))

    def select(self, obj=None, is_header=False):
        if is_header:
            return "选择"
        return mark_safe("<input type='checkbox' value=%s />" % obj.pk)

    list_display = [select, "id", "title", "price", edit, delete]
site.register(Book, BookConfig)

 

 

 

全选

这里checkbox标签的value值能够设置为该记录的主键值,方便之后使用,当咱们点击最上面的复选框时应该还有全选和所有取消的功能,这里只须要添加一段Js代码便可

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
</head>
<body>
<h3>数据展现</h3>

<div class="container">
    <div class="row">
        <div class="col-md-8">
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        {% for foo in header_list %}
                        <td>{{ foo }}</td>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody>
                    {% for data in new_data_list %}
                        <tr>
                            {% for item in data %}
                            <td>{{ item }}</td>
                            {% endfor %}
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>

#新添加的js代码 <script> $("#action-toggle").click(function () { if ($(this).prop("checked")){ $("tbody :checkbox").prop("checked",true) }else{ $("tbody :checkbox").prop("checked",false) } }) </script> </body> </html>

咱们还注意到,在编辑和删除函数中咱们在生成url时采用了反向解析,利用咱们以前使用的别名来反向生成url,这样就不会把url写死了

 

七、list_display的默认状况

上面的内容咱们都是考虑了用户本身定制了list_display的状况,若是用户没用进行自定制呢,那么咱们所使用的list_display就应该是ModelStark中定义好的

咱们仿照admin将默认的list_display设置为__str__,这样在生成表头时咱们须要多作一步判断,当为__str__时,直接将表名的大写添加到header_list中便可

class ModelStark(object):

    list_display = ["__str__",]

    def __init__(self, model, site):
        self.model = model
        self.site = site
        self.app_model = (self.model._meta.app_label,     self.model._meta.model_name)

    # 查看数据视图
    def change_list(self, request):
        # 生成表标头
        header_list = []
        for field in self.list_display:
            if callable(field):
                # header_list.append(field.__name__)
                val = field(self, is_header=True)
                header_list.append(val)
            else:
                if field == "__str__":
                    header_list.append(self.model._meta.model_name.upper())
                else:
                    field_obj = self.model._meta.get_field(field)
                    header_list.append(field_obj.verbose_name)

        # 生成表数据列表
        data_list = self.model.objects.all()
        new_data_list = []
        for obj in data_list:
            temp = []
            for field in self.list_display:
                if callable(field):
                    val = field(self, obj)
                else:
                    val = getattr(obj, field)
                    print(val)
                temp.append(val)
            new_data_list.append(temp)
        return render(request, "stark/change_list.html", locals())

这样咱们就完成了默认状况的设置,可是咱们发如今admin中不论用户如何设置list_display,其实咱们都能看到复选框和编辑删除功能,因此咱们也将编辑、删除和复选框的函数直接放入到ModelStark中做为默认配置,而后设置一个get_list_display函数,对全部的list_play都增长这三个功能

class ModelStark(object):
    # 编辑按钮
    def edit(self, obj=None, is_header=False):
        if is_header:
            return "操做"
        name = "%s_%s_change" % self.app_model
        return mark_safe("<a href=%s>编辑</a>" % reverse(name, args=(obj.pk,)))

    # 删除按钮
    def delete(self, obj=None, is_header=False):
        if is_header:
            return "操做"
        name = "%s_%s_delete" % self.app_model
        return mark_safe("<a href=%s>删除</a>" % reverse(name, args=(obj.pk,)))

    # 复选框
    def checkbox(self, obj=None, is_header=False):
        if is_header:
            return mark_safe("<input type='checkbox' id='action-toggle'>")
        return mark_safe("<input type='checkbox' value=%s>" % obj.pk)

    def get_list_display(self):
        new_list_display = []
        new_list_display.extend(self.list_display)
        new_list_display.append(ModelStark.edit)
        new_list_display.append(ModelStark.delete)
        new_list_display.insert(0, ModelStark.checkbox)
        return new_list_display

    list_display = ["__str__",]

    def __init__(self, model, site):
        self.model = model
        self.site = site
        self.app_model = (self.model._meta.app_label, self.model._meta.model_name)

    # 查看数据视图
    def change_list(self, request):
        # 生成表标头
        header_list = []
        for field in self.get_list_display():
            if callable(field):
                # header_list.append(field.__name__)
                val = field(self, is_header=True)
                header_list.append(val)
            else:
                if field == "__str__":
                    header_list.append(self.model._meta.model_name.upper())
                else:
                    field_obj = self.model._meta.get_field(field)
                    header_list.append(field_obj.verbose_name)

        # 生成表数据列表
        data_list = self.model.objects.all()
        new_data_list = []
        for obj in data_list:
            temp = []
            for field in self.get_list_display():
                if callable(field):
                    val = field(self, obj)
                else:
                    val = getattr(obj, field)
                    print(val)
                temp.append(val)
            new_data_list.append(temp)
        return render(request, "stark/change_list.html", locals())

 咱们还注意到经过自定制get_list_display函数咱们能够实现一些咱们本身的逻辑,好比根据权限判断是否须要加入编辑按钮等

 

八、list_display_links

使用admin时,咱们还能够经过list_display_links设置一些字段,点击这些字段也能进入编辑页面

咱们也来实现一下这个功能,首先在ModelStark中定义一个默认的list_display_links,当用户本身定制了这个属性时,咱们只要在生成表数据时多作一步判断,若是字段在list_display_links中,则在返回时给字段加上一个a标签,使他能够跳转到编辑页便可

因为咱们常常要用到增删改查的url,因此咱们在ModelStark中定义4个方法,分别获取增删改查的url

class ModelStark(object):
    list_display = ["__str__", ]

    list_display_links = []

    def __init__(self, model, site):
        self.model = model
        self.site = site
        self.app_model = (self.model._meta.app_label, self.model._meta.model_name)

    # 获取当前查看表的编辑url
    def get_edit_url(self, obj):
        edit_url = reverse("%s_%s_change" % self.app_model, args=(obj.pk,))
        return edit_url

    # 获取当前查看表的删除url
    def get_delete_url(self, obj):
        del_url = reverse("%s_%s_delete" % self.app_model, args=(obj.pk,))
        return del_url

    # 获取当前查看表的增长url
    def get_add_url(self):
        add_url = reverse("%s_%s_add" % self.app_model)
        return add_url

    # 获取当前查看表的查看url
    def get_list_url(self):
        list_url = reverse("%s_%s_list" % self.app_model)
        return list_url

    # 查看数据视图
    def change_list(self, request):
        add_url = self.get_add_url()
        # 生成表标头
        header_list = []
        for field in self.get_list_display():
            if callable(field):
                # header_list.append(field.__name__)
                val = field(self, is_header=True)
                header_list.append(val)
            else:
                if field == "__str__":
                    header_list.append(self.model._meta.model_name.upper())
                else:
                    field_obj = self.model._meta.get_field(field)
                    header_list.append(field_obj.verbose_name)

        # 生成表数据列表
        data_list = self.model.objects.all()
        new_data_list = []
        for obj in data_list:
            temp = []
            for field in self.get_list_display():
                if callable(field):
                    val = field(self, obj)
                else:
                    val = getattr(obj, field)
                    if field in self.list_display_links:
                        val = mark_safe("<a href=%s>%s</a>" % (self.get_edit_url(obj), val))
                temp.append(val)
            new_data_list.append(temp)
        return render(request, "stark/change_list.html", locals())

这样当用户在stark.py中本身定义了list_display_links属性时,咱们就能看到下面的效果了

from stark.service.sites import site, ModelStark
from .models import *


class BookConfig(ModelStark):
    list_display = ["id", "title", "price"]
    list_display_links = ["id"]
site.register(Book, BookConfig)

 

若是可以点击字段内容进入编辑页面,那么咱们本身定义的编辑按钮就能够不用显示了,因此能够在get_list_display中再作一次判断

def get_list_display(self):
    new_list_display = []
    new_list_display.extend(self.list_display)
    if not self.list_display_links:
        new_list_display.append(ModelStark.edit)
    new_list_display.append(ModelStark.delete)
    new_list_display.insert(0, ModelStark.checkbox)
    return new_list_display

 

九、添加和编辑页面(modelform)

编辑页面和添加页面的功能咱们经过ModelForm实现,可是生成ModelForm时,因为用户访问的表多是不同的,因此里面的详细字段咱们不能写死,因此咱们只能定义一个简单的ModelForm类,而后在ModelStark中设置一个model_form_class,默认为None

用户若是想要对ModelForm的详细字段作设置,能够本身定制一个类,并将该类设置为model_form_class的值

class ModelStark(object):
    list_display = ["__str__", ]

    model_form_class = None

    list_display_links = []

    def get_modelform_class(self):
        class ModelFormClass(ModelForm):
            class Meta:
                model = self.model
                fields = "__all__"
        if not self.model_form_class:
            return ModelFormClass
        else:
            return self.model_form_class

能够看到当用户未设置model_form_class时,咱们用本身的类,当用户设置了,则使用用户本身的类

from stark.service.sites import site, ModelStark
from django.forms import ModelForm
from .models import *
from django.forms import widgets as wid


class BookModelForm(ModelForm):
    class Meta:
        model = Book
        fields = "__all__"
        error_messages = {
            "title": {"required": "不能为空"},
            "price": {"required": "不能为空"}
        }


class BookConfig(ModelStark):
    list_display = ["id", "title", "price"]
    model_form_class = BookModelForm
    list_display_links = ["id"]
site.register(Book, BookConfig)

用户设置时就能够设置明确的字段信息了

添加和编辑的函数

# 添加数据视图
    def add_view(self, request):
        ModelFormClass = self.get_modelform_class()
        if request.method == "GET":
            form = ModelFormClass()
            return render(request, "stark/add_view.html", locals())
        else:
            form = ModelFormClass(data=request.POST)
            if form.is_valid():
                form.save()
                return redirect(self.get_list_url())
            else:
                return render(request, "stark/add_view.html", locals())

    # 编辑数据视图
    def change_view(self, request, id):
        edit_obj = self.model.objects.filter(pk=id).first()
        ModelFormClass = self.get_modelform_class()
        if request.method == "GET":
            form = ModelFormClass(instance=edit_obj)
            return render(request, "stark/change_view.html", locals())
        else:
            form = ModelFormClass(data=request.POST, instance=edit_obj)
            if form.is_valid():
                form.save()
                return redirect(self.get_list_url())
            else:
                return render(request, "stark/change_view.html", locals())

就是经过ModelForm来实现添加和编辑

前端页面,因为前端的页面基本相同,因此咱们能够把相同的部分写到一个页面中,而后应include调用

<div class="container">
    <div class="row">
        <div class="col-md-6">
            <form action="" method="post" novalidate>
    {% csrf_token %}
    {% for field in form %}
    <div class="form-group">
        <label for="">{{ field.label }}</label>
        <div>
            {{ field }}
            <span class="error pull-right">
                {{ field.errors.0 }}
            </span>
        </div>

    </div>
    {% endfor %}

    <p><input type="submit" class="btn btn-default"></p>
</form>
        </div>
    </div>
</div>

添加页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>添加</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <style>
        .form-group input{
            display: block;
            width: 100%;
            height: 34px;
            padding: 6px 12px;
            font-size: 14px;
            line-height: 1.42857143;
            color: #555;
            background-color: #fff;
            background-image: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
            box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
            -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
            -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
            transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
        }

    </style>
</head>
<body>
<h3>添加数据</h3>

{% include 'stark/form.html' %}

</body>
</html>

删除页面

当点击删除时,咱们不直接将数据删除,而是给用户返回一个确认页面,用户点击确认才真的删除,点击取消还跳回列表页面

# 删除数据视图
def del_view(self, request, id):
    del_obj = self.model.objects.filter(pk=id).first()
    if request.method == "GET":
        list_url = self.get_list_url()
        return render(request, "stark/del_view.html", locals())
    else:
        del_obj.delete()
        return redirect(self.get_list_url())

前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>删除</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
</head>
<body>
<div>
    <p>{{ del_obj }}</p>
</div>
<form action="" method="post">
    {% csrf_token %}
    <input type="submit" value="确认删除" class="btn btn-danger">
    <a href="{{ list_url }}" class="btn btn-primary">取消</a>
</form>
</body>
</html>

 

十、分页功能(url数据保留)

当数据较多时,咱们在列表页面须要进行分页,这里分页时咱们直接调用之前写好的分页组件使用便可

在使用分页组件时,咱们在原有组件的基础上添加一个功能,就是点击页码跳转时,保留原来url上的数据

分页组件以下

class Pagination(object):

    def __init__(self,current_page,all_count,base_url,params,per_page_num=2,pager_count=11):
        """
        封装分页相关数据
        :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
        import copy
        params = copy.deepcopy(params)
        params._mutable = True
        self.params = params

        # 总页码
        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)

    @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/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_end = self.all_pager + 1
                    pager_start = self.all_pager - self.pager_count + 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"] = i
            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)

分页组件
分页组件

能够看到咱们在原来的基础上多传了一个参数params,这个参数就是当前页的request.GET,这是一个QueryDict的数据类型(和字典相似),咱们取到他后,发现没法直接进行修改,这是应为QueryDict默认是不让修改的,须要修改mutable参数为True才能修改

修改前咱们为了防止直接修改request.GET而形成后面的影响,因此先用深拷贝拷贝一份数据,再进行修改,修改时,咱们将pape改成当前页的页码,再利用QueryDict的urlencode方法将字典类型的数据转换成a=1&b=2类型的字符串数据,而后在生成页码a标签时在a标签的href属性后面加上生成的字符串,这样咱们点击页面跳转时就能够保留url上的数据了

 

分页组件的使用

from stark.utils.page import Pagination
current_page = request.GET.get("page", 1)
all_count = self.model.objects.all().count()
base_url = request.path_info
params = request.GET
pagination = Pagination(current_page, all_count, base_url, params)
data_list = self.model.objects.all()[pagination.start: pagination.end]

 

编辑页面实现url数据保留

上面咱们在写编辑页面时并无考虑保留页面url上的数据,如今咱们增长上这个功能

首先点击编辑按钮进入编辑页面时咱们须要保留url上的数据,这就须要对编辑按钮这个a标签的href属性进行修改,在后面加上url上要保留的数据,同时,为了让加上的数据不和编辑页面可能有的数据冲突,因此咱们单独定义一个list_filter键来存放这些数据

def get_link_tag(self, obj, val):
    params = self.request.GET
    params = copy.deepcopy(params)
    params._mutable = True
    from django.http import QueryDict
    qd = QueryDict(mutable=True)
    qd["list_filter"] = params.urlencode()
    s = mark_safe("<a href=%s?%s>%s</a>" % (self.get_edit_url(obj), qd.urlencode(), val))
    return s

在列表视图中将原来使用get_edit_url的方法换成上面的方法便可

当编辑页面完成编辑后点击提交后咱们须要跳转回列表页面,这时咱们须要将url上保留的数据还原为原来的形式

# 编辑数据视图
def change_view(self, request, id):
    edit_obj = self.model.objects.filter(pk=id).first()
    ModelFormClass = self.get_modelform_class()
    if request.method == "GET":
        form = ModelFormClass(instance=edit_obj)
        return render(request, "stark/change_view.html", locals())
    else:
        form = ModelFormClass(data=request.POST, instance=edit_obj)
        if form.is_valid():
            form.save()
            params = request.GET.get("list_filter")
            url = "%s?%s" % (self.get_list_url(), params)
            return redirect(url)
        else:
            return render(request, "stark/change_view.html", locals())

这里咱们在原来的url后面加上了咱们从request.GET中取出的数据

 

十一、生成为列表页面服务的类ChangeList

写了这么多内容咱们发现咱们的列表页面的视图函数内容较多,同时列表页面还有不少功能未添加,为了可以减小列表页面的代码,咱们生成一个专门为列表视图函数服务的类,将一些主要的逻辑放到这个类中

# ChangeList服务于change_list视图
class ChangeList(object):
    def __init__(self, config, request, queryset):
        self.config = config
        self.request = request
        self.queryset = queryset
        from stark.utils.page import Pagination
        current_page = self.request.GET.get("page", 1)
        all_count = self.queryset.count()
        base_url = self.request.path_info
        params = self.request.GET
        pagination = Pagination(current_page, all_count, base_url, params)
        data_list = self.queryset[pagination.start: pagination.end]
        self.pagination = pagination
        self.data_list = data_list

    def get_header(self):
        # 生成表标头
        header_list = []
        for field in self.config.get_list_display():
            if callable(field):
                # header_list.append(field.__name__)
                val = field(self.config, is_header=True)
                header_list.append(val)
            else:
                if field == "__str__":
                    header_list.append(self.config.model._meta.model_name.upper())
                else:
                    field_obj = self.config.model._meta.get_field(field)
                    header_list.append(field_obj.verbose_name)
        return header_list

    def get_body(self):
        # 生成表数据列表
        new_data_list = []
        for obj in self.data_list:
            temp = []
            for field in self.config.get_list_display():
                if callable(field):
                    val = field(self.config, obj)
                else:
                    val = getattr(obj, field)
                    if field in self.config.list_display_links:
                        val = self.config.get_link_tag(obj, val)
                temp.append(val)
            new_data_list.append(temp)
        return new_data_list

咱们将如今的生成表头、表数据和分页的功能都放到该类中,初始化时的config参数就是ModelStark类的实例化对象,queryset是咱们从数据库中取出的须要渲染到页面上的数据

这时列表视图函数只要保留下面的内容就好了

# 查看数据视图
def change_list(self, request):
    self.request = request
    add_url = self.get_add_url()
    
    queryset = self.model.objects.filter(search_condition)
    cl = ChangeList(self, request, queryset)
    return render(request, "stark/change_list.html", locals())

页面渲染时咱们只要利用ChangeList类的实例化对象cl就能够渲染出咱们想要的内容

 

十二、search模糊查询

使用admin时咱们能定义一个search_fields列表来生成一个查询框,能够根据列表中的字段进行模糊查询

咱们也来定义这么一个参数

class ModelStark(object):
    list_display = ["__str__", ]

    model_form_class = None

    list_display_links = []

    search_fields = []

默认让他为一个空列表,当用户定义了值时,咱们就须要在页面生成一个搜索框,而且根据模糊查询获得须要的数据展现到页面上,这里须要注意若是用户在列表中定义了多个字段,那么多个字段查询时应该是或的关系

 

Q查询利用字符串进行查询的方式

在查询时因为是或的关系因此咱们要用到Q查询,可是咱们以前使用Q查询时都是直接使用的字段名,如今咱们只能拿到字段名的字符串,因此须要用Q查询的另一种方式

def get_search_condition(self):
    from django.db.models import Q
    search_condition = Q()
    search_condition.connector = "or"  # 设置关系为或
    if self.search_fields:
        key_word = self.request.GET.get("q")
        if key_word:
            for search_field in self.search_fields:
                search_condition.children.append((search_field + "__contains", key_word))
    return search_condition

先生成一个Q对象,设置为或的关系,而后经过循环将要查询的字段的字符串和查询关键字以元组的形式添加到Q对象中,这里要注意,因为是模糊查询,咱们在字段字符串后拼接了__contains

最后在列表视图函数中取到这个Q对象,根据他进行查询

# 查看数据视图
def change_list(self, request):
    self.request = request
    add_url = self.get_add_url()
    # 关于search的模糊查询
    search_condition = self.get_search_condition()
    queryset = self.model.objects.filter(search_condition)
    cl = ChangeList(self, request, queryset)
    return render(request, "stark/change_list.html", locals())

前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
</head>
<body>
<h3>数据展现</h3>

<div class="container">
    <div class="row">
        <div class="col-md-8">
            <a href="{{ add_url }}"><button class="btn btn-primary">添加数据</button></a>
            {% if cl.config.search_fields %}
                <div class="pull-right form-group">
                <form action="" method="get" class="form-inline">
                <input type="text" class="form-control" name="q"> <input type="submit" class="btn btn-primary" value="search">
            </form>
            </div>
            {% endif %}
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        {% for foo in cl.get_header %}
                        <td>{{ foo }}</td>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody>
                    {% for data in cl.get_body %}
                        <tr>
                            {% for item in data %}
                            <td>{{ item }}</td>
                            {% endfor %}
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
            <nav aria-label="Page navigation" class="pull-right">
              <ul class="pagination">
                {{ cl.pagination.page_html|safe }}
              </ul>
            </nav>
        </div>
    </div>
</div>

<script>
    $("#action-toggle").click(function () {
        if ($(this).prop("checked")){
            $("tbody :checkbox").prop("checked",true)
        }else{
            $("tbody :checkbox").prop("checked",false)
        }
    })
</script>
</body>
</html>

生成搜索框时须要作判断,若是用户没有定义search_fields,则不须要生成搜索框

 

1三、action功能(批量操做)

使用admin时,咱们发现有一个action功能,有一个下拉菜单能够选择功能批量操做,如今咱们也来实现这个功能

首先在ModelStark中定义一个变量actions,默认为一个空列表

class ModelStark(object):
    list_display = ["__str__", ]

    model_form_class = None

    list_display_links = []

    search_fields = []

    actions = []

这表示若是用户没有本身定制actions,那么则没有任何功能,可是咱们使用admin时,发现默认有一个批量删除的功能,因此咱们也来写一个批量删除

def patch_delete(self, queryset):
    queryset.delete()
patch_delete.desc = "批量删除"

python中一切皆对象,咱们给这个函数对象一个新的desc属性,这个属性的值就是咱们想要在页面上展现给别人看的这个函数的用途,而后咱们要将这个函数添加到actions中,同时也要考虑用户本身定制时的状况

# 获取真正展现的actions
def get_actions(self):
    temp = []
    temp.extend(self.actions)
    temp.append(ModelStark.patch_delete)
    return temp

这样经过ModelStark中的get_actions咱们就能拿到最终的actions列表,上面咱们本身定制了一个ChangeList类,专门为列表页面服务,actions功能也是在列表页面中使用的,因此咱们在ChangeList类中定义一个方法# ChangeList服务于change_list视图

class ChangeList(object):
    def __init__(self, config, request, queryset):
        self.config = config
        self.request = request
        self.queryset = queryset

        from stark.utils.page import Pagination
        current_page = self.request.GET.get("page", 1)
        all_count = self.queryset.count()
        base_url = self.request.path_info
        params = self.request.GET
        pagination = Pagination(current_page, all_count, base_url, params)
        data_list = self.queryset[pagination.start: pagination.end]
        self.pagination = pagination
        self.data_list = data_list
        # actions
        self.actions = self.config.get_actions()


    def handle_action(self):
        temp =[]
        for action_func in self.actions:
            temp.append({"name": action_func.__name__, "desc": action_func.desc})
        return temp

这里咱们经过这个方法得到的是一个列表,列表中有一个个的字典,字典里放着函数的名字和咱们要展现的描述

前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <style>
        .filter a{
            padding: 5px 3px;
            border: 1px solid grey;
            background-color: #336699;
            color: white;
        }
        .active{
            background-color: white!important;
            color: black!important;
        }
    </style>
</head>
<body>
<h3>数据展现</h3>

<div class="container">
    <div class="row">
        <div class="col-md-8">
            <a href="{{ add_url }}"><button class="btn btn-primary">添加数据</button></a>
            {% if cl.config.search_fields %}
                <div class="pull-right form-group">
                <form action="" method="get" class="form-inline">
                <input type="text" class="form-control" name="q"> <input type="submit" class="btn btn-primary" value="search">
            </form>
            </div>
            {% endif %}
            <form action="" method="post">
            {% csrf_token %}
            <div>
                <select class="form-control" name="action" id="" style="width: 200px;margin: 5px 0;display: inline-block;vertical-align: -1px" >
                        <option value="">---------</option>
                    {% for item in cl.handle_action %}
                        <option value="{{ item.name }}">{{ item.desc }}</option>
                    
                    {% endfor %}
                    
                </select>
                <button type="submit"  class="btn btn-default">Go</button>
            </div>
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        {% for foo in cl.get_header %}
                        <td>{{ foo }}</td>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody>
                    {% for data in cl.get_body %}
                        <tr>
                            {% for item in data %}
                            <td>{{ item }}</td>
                            {% endfor %}
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
            </form>
            <nav aria-label="Page navigation" class="pull-right">
              <ul class="pagination">
                {{ cl.pagination.page_html|safe }}
              </ul>
            </nav>
        </div>
        </div>
    </div>
</div>

<script>
    $("#action-toggle").click(function () {
        if ($(this).prop("checked")){
            $("tbody :checkbox").prop("checked",true)
        }else{
            $("tbody :checkbox").prop("checked",false)
        }
    })
</script>
</body>
</html>
View Code

这里每个下拉菜单的option的value值就是咱们定义的函数名,还要注意要将select标签和复选框标签都放到同一个form表单中,这样在发送数据时咱们发送了,选择的批量操做函数和被选中的数据的pk值

 

后端操做

后端接收到选择的批量操做函数和被选中的数据的pk值就能够进行操做了

# 查看数据视图
def change_list(self, request):
    if request.method == "POST":
        func_name = request.POST.get("action")
        pk_list = request.POST.getlist("_selected_action")
        queryset = self.model.objects.filter(pk__in=pk_list)
        func = getattr(self, func_name)
        func(queryset)
    self.request = request
    add_url = self.get_add_url()
    # 关于search的模糊查询
    search_condition = self.get_search_condition()
    queryset = self.model.objects.filter(search_condition)
    cl = ChangeList(self, request, queryset)
    return render(request, "stark/change_list.html", locals())

首先判断,当请求为POST请求时,取到批量操做函数的函数名,和选择数据的pk值列表(因为是复选框,可能选择了多个值,因此这里用getlist取值),而后经过pk值列表查找到要操做的数据的queryset集合,利用反射经过函数名的字符串取到批量操做函数,最后将取到的quertset传给函数执行

 

1四、多级过滤

和其它功能同样,咱们先在ModelStark中定义一个list_filter空列表

class ModelStark(object):
    list_display = ["__str__", ]

    model_form_class = None

    list_display_links = []

    search_fields = []

    actions = []
    # 多级过滤
    list_filter = []

当用户本身定义了这个列表时,咱们要取到列表中的字段,而后查处该字段对应的内容,显示到页面上,当用户点击某一个内容时,要过滤出和这个内容相关的数据

当用户点击这个内容的a标签时,咱们要向后台发送一个get请求,请求带着咱们要过滤的内容,内容的键为字段的名称,值为你选中值的pk值

第一步咱们要先想办法将list_filter中字段的对应数据都显示到页面上,先要取到全部数据

# ChangeList服务于change_list视图
class ChangeList(object):
    def __init__(self, config, request, queryset):
        self.config = config
        self.request = request
        self.queryset = queryset

        from stark.utils.page import Pagination
        current_page = self.request.GET.get("page", 1)
        all_count = self.queryset.count()
        base_url = self.request.path_info
        params = self.request.GET
        pagination = Pagination(current_page, all_count, base_url, params)
        data_list = self.queryset[pagination.start: pagination.end]
        self.pagination = pagination
        self.data_list = data_list
        # actions
        self.actions = self.config.get_actions()
        # filter
        self.list_filter = self.config.list_filter

    def get_filter_link_tag(self):
        # link_tags = []
        for filter_field_name in self.list_filter:
            current_id = int(self.request.GET.get(filter_field_name, 0))
            filter_field_obj = self.config.model._meta.get_field(filter_field_name)
            filter_field = FilterField(filter_field_name, filter_field_obj)

在ChangeList中咱们先拿到拿到list_filter,并定义self.list_filter,而后定义一个get_filter_link_tag方法,循环self.list_filter,循环的每个值就是各个字段的名称,而后经过字段名称拿到这个字段的对象filter_field_obj,而后经过字段名称和字段对象经过FilterField类实例化出一个对象,这个新的类内容以下

# 为每个过滤的字段封装成总体类
class FilterField(object):
    def __init__(self, filter_field_name, filter_field_obj):
        self.filter_field_name = filter_field_name
        self.filter_field_obj = filter_field_obj

    def get_data(self):
        if isinstance(self.filter_field_obj, ForeignKey) or isinstance(self.filter_field_obj, ManyToManyField):
            return self.filter_field_obj.rel.to.objects.all()
        elif self.filter_field_obj.choices:
            return self.filter_field_obj.choices
        else:
            pass

经过这个类的对象咱们能够调用get_data方法拿到须要的数据列表(queryset或元组里套元组),这里咱们暂时不考虑普通字段,因为须要的字段变多了,咱们将Book表的字段进行一些修改,增长外键和多对多的关系

from django.db import models

# Create your models here.


class Book(models.Model):
    title = models.CharField(verbose_name="标题", max_length=32)
    price = models.DecimalField(verbose_name="价格", decimal_places=2, max_digits=5, default=12)
    state = models.IntegerField(choices=((1, "已出版"), (2, "未出版")), default=1)
    publish = models.ForeignKey(to="Publish", default=1)
    authors = models.ManyToManyField(to="Author", default=1)

    def __str__(self):
        return self.title
    

class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    def __str__(self):
        return self.name


class Publish(models.Model):
    name = models.CharField(max_length=32)

    def __str__(self):
        return self.name

拿到数据的列表后,咱们再循环每个数据,经过判断字段对象的类型,生成不一样的a标签,同时咱们也取到当前get求情的数据,若是当前标签为选中的标签,咱们要给他增长一个active属性

还有须要保留以前的选择,咱们也要取到每次get请求的数据,保留到生成的a标签中

# ChangeList服务于change_list视图
class ChangeList(object):
    def __init__(self, config, request, queryset):
        self.config = config
        self.request = request
        self.queryset = queryset

        from stark.utils.page import Pagination
        current_page = self.request.GET.get("page", 1)
        all_count = self.queryset.count()
        base_url = self.request.path_info
        params = self.request.GET
        pagination = Pagination(current_page, all_count, base_url, params)
        data_list = self.queryset[pagination.start: pagination.end]
        self.pagination = pagination
        self.data_list = data_list
        # actions
        self.actions = self.config.get_actions()
        # filter
        self.list_filter = self.config.list_filter

    def get_filter_link_tag(self):
        # link_tags = []
        for filter_field_name in self.list_filter:
            current_id = int(self.request.GET.get(filter_field_name, 0))
            filter_field_obj = self.config.model._meta.get_field(filter_field_name)
            filter_field = FilterField(filter_field_name, filter_field_obj)

            def inner(filter_field, current_id):
                for obj in filter_field.get_data():
                    params = copy.deepcopy(self.request.GET)
                    params._mutable = True
                    if isinstance(filter_field.filter_field_obj, ForeignKey) or isinstance(filter_field.filter_field_obj, ManyToManyField):
                        params[filter_field.filter_field_name] = obj.pk
                        if current_id == obj.pk:
                            yield mark_safe("<a href='?%s' class='active'>%s</a>" % (params.urlencode(), obj))
                        else:
                            yield mark_safe("<a href='?%s'>%s</a>" % (params.urlencode(), obj))
                    elif filter_field.filter_field_obj.choices:
                        params[filter_field.filter_field_name] = obj[0]
                        if current_id == obj[0]:
                            yield mark_safe("<a href='?%s' class='active'>%s</a>" % (params.urlencode(), obj[1]))
                        else:
                            yield mark_safe("<a href='?%s'>%s</a>" % (params.urlencode(), obj[1]))
                    else:
                        pass

            yield inner(filter_field, current_id)
        #     link_tags.append(temp)
        # return link_tags

这里咱们使用的yield功能,在渲染模板时须要注意,当for循环中还套了一个for循环时,会先取到第一个for循环的全部内容,再进行内部的for循环,这在使用yield时会出现一些问题,因此咱们在内部的生成器函数中要直接将当前的数据传进去,避免出错

前端页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <style>
        .filter a{
            padding: 5px 3px;
            border: 1px solid grey;
            background-color: #336699;
            color: white;
        }
        .active{
            background-color: white!important;
            color: black!important;
        }
    </style>
</head>
<body>
<h3>数据展现</h3>

<div class="container">
    <div class="row">
        <div class="col-md-8">
            <a href="{{ add_url }}"><button class="btn btn-primary">添加数据</button></a>
            {% if cl.config.search_fields %}
                <div class="pull-right form-group">
                <form action="" method="get" class="form-inline">
                <input type="text" class="form-control" name="q"> <input type="submit" class="btn btn-primary" value="search">
            </form>
            </div>
            {% endif %}
            <form action="" method="post">
            {% csrf_token %}
            <div>
                <select class="form-control" name="action" id="" style="width: 200px;margin: 5px 0;display: inline-block;vertical-align: -1px" >
                        <option value="">---------</option>
                    {% for item in cl.handle_action %}
                        <option value="{{ item.name }}">{{ item.desc }}</option>
                    
                    {% endfor %}
                    
                </select>
                <button type="submit"  class="btn btn-default">Go</button>
            </div>
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        {% for foo in cl.get_header %}
                        <td>{{ foo }}</td>
                        {% endfor %}
                    </tr>
                </thead>
                <tbody>
                    {% for data in cl.get_body %}
                        <tr>
                            {% for item in data %}
                            <td>{{ item }}</td>
                            {% endfor %}
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
            </form>
            <nav aria-label="Page navigation" class="pull-right">
              <ul class="pagination">
                {{ cl.pagination.page_html|safe }}
              </ul>
            </nav>
        </div>
        <div class="col-md-4">
            <div class="filter">
                {% for filter_link_tag in cl.get_filter_link_tag %}
                    <p class="field">{% for data in filter_link_tag %}
                        {{ data }}
                    {% endfor %}
                    </p>
                {% endfor %}
                
            </div>
        </div>
    </div>
</div>

<script>
    $("#action-toggle").click(function () {
        if ($(this).prop("checked")){
            $("tbody :checkbox").prop("checked",true)
        }else{
            $("tbody :checkbox").prop("checked",false)
        }
    })
</script>
</body>
</html>
View Code

数据的过滤

上面咱们已经能在页面上生成对应的过滤标签了,当经过点击这些标签时,会向后端发送含有相应条件的GET请求,咱们要在后端拿到条件并进行过滤,将过滤后的数据显示在页面上,这个功能和咱们上面作的search相似

复制代码
# 获取filter的查询条件Q对象
def get_filter_condition(self):
    from django.db.models import Q
    filter_condition = Q()
    for field, val in self.request.GET.items():
        if field in self.list_filter:
            filter_condition.children.append((field, val))
    return filter_condition
# 查看数据视图
def change_list(self, request):
    if request.method == "POST":
        func_name = request.POST.get("action")
        pk_list = request.POST.getlist("_selected_action")
        queryset = self.model.objects.filter(pk__in=pk_list)
        func = getattr(self, func_name)
        func(queryset)
    self.request = request
    add_url = self.get_add_url()
    # 关于search的模糊查询
    search_condition = self.get_search_condition()
    # filter多级过滤
    filter_condition = self.get_filter_condition()
    queryset = self.model.objects.filter(search_condition).filter(filter_condition)
    cl = ChangeList(self, request, queryset)
    return render(request, "stark/change_list.html", locals())
复制代码

这里咱们须要作一次判断,当get请求的数据中有page时,须要过滤掉,否则会报错

普通字段

上面的过滤咱们都没有考虑普通字段,若是是一个普通字段,咱们能够按下面的代码执行

复制代码
# 针对((),()),[[],[]]数据类型构建a标签
class LinkTagGen(object):
    def __init__(self, data, filter_field, request):
        self.data = data
        self.filter_field = filter_field
        self.request = request

    def __iter__(self):
        current_id = self.request.GET.get(self.filter_field.filter_field_name, 0)
        params = copy.deepcopy(self.request.GET)
        params._mutable = True
        if params.get(self.filter_field.filter_field_name):
            del params[self.filter_field.filter_field_name]
            _url = "%s?%s" % (self.request.path_info, params.urlencode())
            yield mark_safe("<a href='%s'>所有</a>" % _url)
        else:
            _url = "%s?%s" % (self.request.path_info, params.urlencode())
            yield mark_safe("<a href='%s' class='active'>所有</a>" % _url)
        for item in self.data:
            if self.filter_field.filter_field_obj.choices:
                pk, text = str(item[0]), item[1]
            elif isinstance(self.filter_field.filter_field_obj, ForeignKey) or isinstance(self.filter_field.filter_field_obj, ManyToManyField):
                pk, text = str(item.pk), item
            else:
                pk, text = item[1], item[1]

            params[self.filter_field.filter_field_name] = pk
            _url = "%s?%s" % (self.request.path_info, params.urlencode())
            if current_id == pk:
                link_tag = "<a href='%s' class='active'>%s</a>" % (_url, text)
            else:
                link_tag = "<a href='%s'>%s</a>" % (_url, text)
            yield mark_safe(link_tag)


# 为每个过滤的字段封装成总体类
class FilterField(object):
    def __init__(self, filter_field_name, filter_field_obj, config):
        self.filter_field_name = filter_field_name
        self.filter_field_obj = filter_field_obj
        self.config = config

    def get_data(self):
        if isinstance(self.filter_field_obj, ForeignKey) or isinstance(self.filter_field_obj, ManyToManyField):
            return self.filter_field_obj.rel.to.objects.all()
        elif self.filter_field_obj.choices:
            return self.filter_field_obj.choices
        else:
            return self.config.model.objects.values_list("pk", self.filter_field_name)
复制代码

 

1四、POPUP功能

使用admin添加数据时咱们会发如今外键和多对多的字段旁边有一个小加号,点击后会弹出一个小窗口,在小窗口中能够直接添加外键和多对多字段对应的表的数据

这里咱们使用popup来实现这个功能,咱们的逻辑应该是在添加页面的视图函数中

复制代码
# 添加数据视图
def add_view(self, request):
    ModelFormClass = self.get_modelform_class()
    if request.method == "GET":
        form = ModelFormClass()
    else:
        form = ModelFormClass(data=request.POST)
        if form.is_valid():
            obj = form.save()
            pop_id = request.GET.get("pop_id")
            if pop_id:
                res = {"pk": obj.pk, "text": str(obj), "pop_id": pop_id}
                import json
                return render(request, "stark/pop_res.html", {"res": json.dumps(res)})
            return redirect(self.get_list_url())
    from django.forms.models import ModelChoiceField
    for bound_field in form:
        if isinstance(bound_field.field, ModelChoiceField):
            bound_field.is_pop = True
            app_label = bound_field.field.queryset.model._meta.app_label
            model_name = bound_field.field.queryset.model._meta.model_name
            _url = "%s_%s_add" % (app_label, model_name)
            bound_field.url = reverse(_url) + "?pop_id=id_%s" % bound_field.name
        else:
            bound_field.is_pop = False
            bound_field.url = None
    return render(request, "stark/add_view.html", locals())
复制代码

咱们的添加页面是利用ModelForm生成的,在请求过来时咱们会使用ModelForm实例化一个对象,咱们使用for循环遍历这个对象,能够获得这个对象的每个字段(其实就是ModelForm对应表的每个字段),这个字段是BoundField类型的,下面有两个方法.name.field.name能够获得字段的名称,而.field则能够获得字段在ModelForm中的类型,外键类型在Form中对应的是ModelChoiceField类型,而多对多在Form中对应的是ModelMultipleChoiceField类型(继承ModelChoiceField),因此咱们只要判断bound_field.field是不是ModelChoiceField类型的对象就能知道这个字段是不是外键或多对多的字段,若是是的话,咱们给这个字段对象添加一个is_pop=True的属性,不是则为False,在前端页面咱们能够根据这个属性判断是否须要在字段的框后面添加+号,这个+号应该绑定一个点击事件,点击后能够弹出一个窗口让咱们添加数据(pop请求),弹出窗口的url应该就是该字段对应表的添加url,如何取到字段对应的表呢?

前面咱们取到了外键和多对多在Form中对应的字段bound_field.field,这里面有一个queryset属性能够取到这个外键字段对应表中的数据集合,而queryset这个数据类型中有个model属性能够获得数据集合对应的表,这样咱们就能够经过bound_field.field.queryset.model获得外键或多对多对应的表,再取出表名和app名,经过反向解析就能够获得对应的添加url,这里要注意,咱们要在这个url后面加上一个?pop_id=id_当前字段名,这样当添加的数据经过post请求过来时,咱们才能知道是哪一个字段添加的数据,数据返回时咱们才能找到这个字段对应的select框,给他添加一个option标签

当添加的数据经过post请求过来时,若是数据验证成功了,咱们先取pop_id,若是能取到,说明这个请求是经过弹出框来的,咱们取到相关数据放到一个字典中,将字典返回给pop响应页面,响应页面中咱们能够经过opener得到是哪一个页面弹出的这个窗口,而后调用这个opener中的函数,并把后端接收的字典传给他,这样就能够利用这个函数在页面上添加option标签了。若是取不到pop_id则直接保存数据,跳转到列表页面便可。若是post请求发来的数据没有验证成功,那么咱们依然要作上面两段提到的内容,因此咱们将上面两段的逻辑放到了函数的最后

前端页面

add_view.html

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>添加</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <style>
        .form-group input,select{
            display: block;
            width: 100%;
            height: 34px;
            padding: 6px 12px;
            font-size: 14px;
            line-height: 1.42857143;
            color: #555;
            background-color: #fff;
            background-image: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
            box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
            -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
            -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
            transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
        }

    </style>
</head>
<body>
<h3>添加数据</h3>

{% include 'stark/form.html' %}

<script>
    function foo(res) {
        var res=JSON.parse(res);
        var ele_option=document.createElement("option");
        ele_option.value=res.pk;
        ele_option.innerHTML=res.text;
        ele_option.selected="selected";
        document.getElementById(res.pop_id).appendChild(ele_option)
    }
</script>
</body>
</html>
复制代码

这里的foo就是pop响应页面调用的函数

form.html

复制代码
<div class="container">
    <div class="row">
        <div class="col-md-6 col-xs-8">
            <form action="" method="post" novalidate>
    {% csrf_token %}
    {% for field in form %}
    <div class="form-group" style="position: relative">
        <label for="">{{ field.label }}</label>
        <div>
            {{ field }}
            <span class="error pull-right">
                {{ field.errors.0 }}
            </span>
        </div>
        {% if field.is_pop %}
            <a href="" onclick="pop('{{ field.url }}')" class="pop_btn" style="position: absolute;top: 45%;right: -23px"><span class="pull-right" style="font-size: 22px">+</span></a>
        {% endif %}
    </div>
    {% endfor %}

    <p><input type="submit" class="btn btn-default"></p>
</form>
        </div>
    </div>
</div>
<script>
    function pop(url) {
        window.open(url,"","width=500,height=400")
    }
</script>
复制代码

经过is_pop来判断能是否添加+号,并给这个加号绑定点击事件,弹出pop框

pop_res.html

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>

<script>
    opener.foo('{{ res|safe }}');
    window.close()
</script>
</body>
</html>
复制代码

执行opener的函数,并直接关闭弹出框

编辑页面和添加页面同时实现popup功能

经过上面的方式咱们就实现了添加页面的popup功能,可是当咱们点击编辑时,咱们发如今编辑页面上也须要有popup的功能,咱们能够将上面的逻辑再在编辑页面中写一份,可是这样的话会形成代码的重复

这里咱们使用自定义标签的形式,添加和编辑页面用的都是form.html页面中的内容,而这个页面中的内容中咱们只须要提供一个form

因此咱们在stark组件中建立一个目录templatetags,在该目录中自定义咱们的标签my_tags

复制代码
from django import template
from django.shortcuts import reverse
register = template.Library()


@register.inclusion_tag("stark/form.html")
def get_form(form):
    from django.forms.models import ModelChoiceField
    for bound_field in form:
        if isinstance(bound_field.field, ModelChoiceField):
            bound_field.is_pop = True
            app_label = bound_field.field.queryset.model._meta.app_label
            model_name = bound_field.field.queryset.model._meta.model_name
            _url = "%s_%s_add" % (app_label, model_name)
            bound_field.url = reverse(_url) + "?pop_id=id_%s" % bound_field.name
    return {"form": form}
复制代码

这样在添加和编辑视图中就不用再写这么多逻辑了,直接将form对象传给前端就好了

复制代码
# 添加数据视图
def add_view(self, request):
    ModelFormClass = self.get_modelform_class()
    if request.method == "GET":
        form = ModelFormClass()
    else:
        form = ModelFormClass(data=request.POST)
        if form.is_valid():
            obj = form.save()
            pop_id = request.GET.get("pop_id")
            if pop_id:
                res = {"pk": obj.pk, "text": str(obj), "pop_id": pop_id}
                import json
                return render(request, "stark/pop_res.html", {"res": json.dumps(res)})
            return redirect(self.get_list_url())
    return render(request, "stark/add_view.html", locals())


# 编辑数据视图
def change_view(self, request, id):
    edit_obj = self.model.objects.filter(pk=id).first()
    ModelFormClass = self.get_modelform_class()
    if request.method == "GET":
        form = ModelFormClass(instance=edit_obj)
        return render(request, "stark/change_view.html", locals())
    else:
        form = ModelFormClass(data=request.POST, instance=edit_obj)
        if form.is_valid():
            form.save()
            params = request.GET.get("list_filter")
            url = "%s?%s" % (self.get_list_url(), params)
            return redirect(url)
        else:
            return render(request, "stark/change_view.html", locals())
复制代码

前端收到这个form对象,直接调用自定义标签便可

复制代码
{% load my_tags %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>添加</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/jquery-3.2.1.min.js"></script>
    <style>
        .form-group input,select{
            display: block;
            width: 100%;
            height: 34px;
            padding: 6px 12px;
            font-size: 14px;
            line-height: 1.42857143;
            color: #555;
            background-color: #fff;
            background-image: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
            box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
            -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
            -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
            transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
        }

    </style>
</head>
<body>
<h3>编辑数据</h3>

{% get_form form %}

<script>
    function foo(res) {
        var res=JSON.parse(res);
        var ele_option=document.createElement("option");
        ele_option.value=res.pk;
        ele_option.innerHTML=res.text;
        ele_option.selected="selected";
        document.getElementById(res.pop_id).appendChild(ele_option)
    }
</script>
</body>
</html>
复制代码

1五、list_display补充

list_display能够定义咱们在列表页面上显示哪些内容,若是展现的内容是一个包含choices的字段的话(好比说Book的state字段),按咱们以前写的,在页面上只能看到1或者2这样的数字,若是想要看到已出版或未出版该怎么办呢,可让用户本身定制,在stark.py中

复制代码
class BookConfig(ModelStark):
    
    def state(self, obj=None, is_header=False):
        if is_header:
            return "状态"
        return obj.get_state_display()

    list_display = ["id", "title", "price", "publish", state]
    model_form_class = BookModelForm
    list_display_links = ["id"]
    search_fields = ["title", "price"]

    def patch_init(self, queryset):
        queryset.update(price=100)

    patch_init.desc = "批量初始化"
    actions = [patch_init, ]
    list_filter = ["title", "state", "publish", "authors"]
site.register(Book, BookConfig)
复制代码

本身定义一个函数,obj.get_state_display()方法就能够取到choice中的内容,这个方法的state是字段名,get和display是固定用法

 在list_display中增长多对多字段

在admin中咱们不能往list_display中增长多对多字段,在咱们本身写的stark中咱们来实现这一功能,其实就是在ChangList类中的get_body方法中多作一次判断

复制代码
def get_body(self):
    # 生成表数据列表
    new_data_list = []
    for obj in self.data_list:
        temp = []
        for field in self.config.get_list_display():
            if callable(field):
                val = field(self.config, obj)
            else:
                field_obj = self.config.model._meta.get_field(field)
                if isinstance(field_obj, ManyToManyField):
                    t = []
                    for i in getattr(obj, field).all():
                        t.append(str(i))
                    val = ",".join(t)
                else:
                    val = getattr(obj, field)
                    if field in self.config.list_display_links:
                        val = self.config.get_link_tag(obj, val)
            temp.append(val)
        new_data_list.append(temp)
    return new_data_list
复制代码

当list_display中的值是不可调用的时,咱们先取出其对应的字段对象,若是是多对多的类型,则经过getattr的方法拿到多对多的内容,并经过join生成字符串