包之间的单例模式,模仿django admin开发一个自己的组件

单例模式的几种方法:

1、利用类的双下__new__方法实现单例模式:

每实例化一个对象都会new一次,每个对象都会新建一个新的内存地址,那么可以自定义new方法实现单例模式,即每创建一个对象都继用实例化的第一个对象的内存地址,不管对哪个对象进行操作,都是操作同一个对象

class Teacher:
    __new_teacher = False #私有化一个属性
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex

    def __new__(cls, *args, **kwargs):#实现了不管如何创建对象,都是返回这个私有属性,这个私有属性一直是第一次实例化的对象
        if cls.__new_teacher: #如果私有化属性不为空
            return cls.__new_teacher    #返回这个属性(对象)
        else:
            cls.__new_teacher = object.__new__(cls)  #否则就创建一个对象赋值给私有化的属性
            return cls.__new_teacher    #返回这个属性(对象)

aike = Teacher('aike','man')
aike1= Teacher('aike','man')
print(aike)#内存地址一样
print(aike1)#内存地址一样
aike.name = '艾克'
print(aike.name)#艾克
print(aike1.name)#艾克


#打印:
<__main__.Teacher object at 0x000001C184484D48>
<__main__.Teacher object at 0x000001C184484D48>
艾克
艾克

 1、利用python导包特性,实现单例模式

    Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。后续导入时,模块中的对象内存地址依然是指向第一次导入时的地址。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age


p1 = Person("aike", 9000)
from singleton import p1

print(id(p1))
print(p1.name)
p1.name = "Bob"

from singleton import p1

print(id(p1))
print(p1.name)

 

admin执行流程

  1、循环加载执行所有已经注册的app中的admin.py文件

def autodiscover():
    autodiscover_modules('admin', register_to=site)

  2、执行代码

#admin.py

class BookAdmin(admin.ModelAdmin):
    list_display = ("title",'publishDate', 'price')

admin.site.register(Book, BookAdmin) 
admin.site.register(Publish)

  3、admin.site  

    这里应用的是一个单例模式,对于AdminSite类的一个单例模式,执行的每一个app中的每一个admin.site都是一个对象

  4、执行register方法

admin.site.register(Book, BookAdmin) 
admin.site.register(Publish)
class ModelAdmin(BaseModelAdmin):pass

def register(self, model_or_iterable, admin_class=None, **options):
    if not admin_class:
            admin_class = ModelAdmin
    # Instantiate the admin class to save in the registry
    self._registry[model] = admin_class(model, self)

      到此,注册结束,然后进行url分发

  5、django2.2.10下,admin的URL配置

urlpatterns = [
    path('admin/', admin.site.urls),
]
    AdminSite类下进行一级分发:
def get_urls(self):
        from django.urls import include, path, re_path
        # 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 = [
            path('', wrap(self.index), name='index'),
            path('login/', self.login, name='login'),
            path('logout/', wrap(self.logout), name='logout'),
            path('password_change/', wrap(self.password_change, cacheable=True), name='password_change'),
            path(
                'password_change/done/',
                wrap(self.password_change_done, cacheable=True),
                name='password_change_done',
            ),
            path('jsi18n/', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
            path(
                'r/<int:content_type_id>/<path: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 += [
                path('%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
            ]  # 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 += [
                re_path(regex, wrap(self.app_index), name='app_list'),
            ]
        return urlpatterns

    @property
    def urls(self):
        return self.get_urls(), 'admin', self.name
一级分发
    ModelAdmin类下进行二级分发:
    def get_urls(self):
        from django.urls import path

        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 = [
            path('', wrap(self.changelist_view), name='%s_%s_changelist' % info),
            path('add/', wrap(self.add_view), name='%s_%s_add' % info),
            path('autocomplete/', wrap(self.autocomplete_view), name='%s_%s_autocomplete' % info),
            path('<path:object_id>/history/', wrap(self.history_view), name='%s_%s_history' % info),
            path('<path:object_id>/delete/', wrap(self.delete_view), name='%s_%s_delete' % info),
            path('<path:object_id>/change/', wrap(self.change_view), name='%s_%s_change' % info),
            # For backwards compatibility (was the change url before 1.9)
            path('<path:object_id>/', 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()
二级分发

  6、django1下的url()方法的分发,使用方法与django2中的path()同样适用

from django.shortcuts import HttpResponse
def test01(request):
    return HttpResponse("test01")

def test02(request):
    return HttpResponse("test02")

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

                    ],None,None)),

]
from django.conf.urls import url,include
from django.contrib import admin

from django.shortcuts import HttpResponse

def change_list_view(request):
    return HttpResponse("change_list_view")
def add_view(request):
    return HttpResponse("add_view")
def delete_view(request):
    return HttpResponse("delete_view")
def change_view(request):
    return HttpResponse("change_view")

def get_urls():

    temp=[
        url(r"^$".format(app_name,model_name),change_list_view),
        url(r"^add/$".format(app_name,model_name),add_view),
        url(r"^\d+/del/$".format(app_name,model_name),delete_view),
        url(r"^\d+/change/$".format(app_name,model_name),change_view),
    ]

    return temp


url_list=[]

for model_class,obj in admin.site._registry.items():

    model_name=model_class._meta.model_name
    app_name=model_class._meta.app_label

    # temp=url(r"{0}/{1}/".format(app_name,model_name),(get_urls(),None,None))
    temp=url(r"{0}/{1}/".format(app_name,model_name),include(get_urls()))
    url_list.append(temp)

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^xadmin/', (url_list,None,None)),
分发优化

 

自己实现:

1、启动

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


class XadminConfig(AppConfig):
    name = 'xadmin'

    def ready(self):
        autodiscover_modules('xadmin')
apps.py

2、app中注册 

from xadmin.service.start__xadim import site, ModelAdmin
from app01 import models
from django.utils.safestring import  mark_safe


class BookConfig(ModelAdmin):

    def add_tag_change(self, obj=None, is_header=False):
        if is_header:  # 如果是表头
            return '修改操作'
        return mark_safe('<a href="%s/change">修改</a>'%obj.pk)  # 如果是表单数据

    def add_tag_delete(self, obj=None, is_header=False):
        if is_header:
            return '删除操作'
        return mark_safe('<a href="%s/delete">删除</a>'%obj.pk)

    list_display = ['nid', 'title', 'price', add_tag_change, add_tag_delete]

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


site.register(models.Book, BookConfig)
site.register(models.Publish)
site.register(models.Author)
site.register(models.AuthorDetail)
xadmin.py

3、admin.site 服务:url分发、数据展示等

from django.urls import path, re_path
# from app01 import views as app01_views
from django.shortcuts import render, redirect, HttpResponse


class ModelAdmin:
    list_display = ['__str__']  # 用于自定义显示哪些字段,默认显示__str__

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


    def test(self, request):
        all_model_data = []  # 用于存放所有条数据,用于前端展示
        all_model_obj = self.model.objects.all()

        # 处理表头数据
        header_list = []
        for field in self.list_display:
            if isinstance(field, str):
                if field == '__str__':  # 如果使用默认字段,表头显示为表名大小
                    var = self.model._meta.model_name.upper()
                else:
                    field_obj = self.model._meta.get_field(field)  # 获取这个字段类,用于获取别名verbose_name,以在前端展示中文表头
                    # print(field_obj,type(field_obj))
                    var = field_obj.verbose_name
            else:
                var = field(self, is_header=True)  # is_header表示是表头

            header_list.append(var)
        # 处理表单数据
        for model_obj in all_model_obj:
            model_data = []  # 用于存放每一条数据
            for field in self.list_display:
                if isinstance(field, str):  # 这个字段是否是str类,不是则为函数,用于显示删除和修改
                    val = getattr(model_obj, field)  # 获取model.Book类中的字段类型,field为字符串类型,需要用反射执行
                else:
                    val = field(self, obj=model_obj, is_header=False)  # 执行函数,返回删除或者修改的标签, is_header表示不是表头, obj用于注册时的修改或者删除标签的链接拼接
                model_data.append(val)
            all_model_data.append(model_data)  # 将每一条数据添加到一个大列表
        return render(request, 'show_model_data.html', {'all_model_data': all_model_data, 'header_list': header_list})
        # return HttpResponse('ok')

    def add(self, request, id):
        return HttpResponse('ok')

    def delete(self, request, id):
        return HttpResponse('ok')

    def change(self, request, id):
        return HttpResponse('ok')

    @property
    def get_urls2(self):
        url_list = [
            re_path('^$', self.test),
            re_path('(?P<id>\d+)/add/', self.add),
            re_path('(?P<id>\d+)/delete/', self.delete),
            re_path('(?P<id>\d+)/change/', self.change),
        ]
        return url_list

    @property
    def urls2(self):
        return self.get_urls2, None, None


class XadminSite:
    def __init__(self):
        self._registry = {}  # {注册的model类对象: 这个类对象的配置类}


    @property
    def get_urls(self):
        url_list = []
        for model, model_admin in self._registry.items():
            app_model = model._meta.app_label  # app字符串名字
            model_str = model._meta.model_name  # 表名字符串名字
            url_list.append(path('%s/%s/' % (app_model, model_str), model_admin.urls2))
        return url_list

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

    def register(self, model, admin_class=None):
        if not admin_class:
            admin_class = ModelAdmin  # 没有设配置类时,配置类默认为modelAdmin
        self._registry[model] = admin_class(model, self)  # {Book:admin_class}


site = XadminSite()
start_xadmin.py

4、url

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

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

 

注意问题:

  1、单例模式,每个用户进来都是走site这一个对象

  2、每个注册过来的model将以{注册的model类对象: 这个类对象的配置类}字典的形式存放在_registry属性当中,如果不传配置类,将使用默认的配置值类

class XadminSite:
    def __init__(self):
        self._registry = {}  # {注册的model类对象: 这个类对象的配置类}

    def register(self, model, admin_class=None):
        if not admin_class:
            admin_class = ModelAdmin  # 没有设配置类时,配置类默认为modelAdmin
        self._registry[model] = admin_class(model, self)  # {Book:admin_class}


site = XadminSite()
class ModelAdmin:
    list_display = ['__str__']  # 用于自定义显示哪些字段,默认显示__str__

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

  3、关于url二级分发的问题,第二级分发为什么要放在配置类ModelAdmin中,而不是与第一级分发一样放在XadminSite中

    因为第一级分发时,循环_registry字典获取到了每一个model类对象,取到了它所在的app名与表名,当注册多少张表时,就能得到多少条一级url,取到model类对象的同时,还能取到相对应的配置类ModelAdmin,该配置类在注册时传有两个参数,一个model,即model类对象,一个self,即site单例对象。

    此时,我们将二级分发在ModelAdmin中实现,可以直接用当前model类对象对应的配置类调用这个二级分发方法。而二级分发后属于一个完整的url,需要紧跟着实现视图功能。这时,由于配置ModelAdmin中已经有两个类属性,一个是当前访问的model类对象,一个site对象。这时便可以直接利用这个model类对象获取数据库中的数据。

  4、获取app名、表名与字段别名verbose_name的方法:_meta

field_obj = self.model._meta.get_field(field)  # 获取这个字段类,用于获取别名verbose_name,以在前端展示中文表头:参数为一个字段名字的字符串形式,返回这个字段对象,可用于
app_model = model._meta.app_label  # app字符串名字
model_str = model._meta.model_name  # 表名字符串名字

     其中get_field方法接收一个字段的字符串形式,返回这个字段对象。这个字段对象的属性都能获取,例如max_length,verbose_name等,如果是关系字段,则可以获取关联表的所有对象:

field_obj = models.Book._meta.get_field(filter_field)
queryset_list = field_obj.remote_field.model.objects.all()
 # queryset_list = fields_obj.rel.to.objects.all()

 

posted @ 2020-02-26 21:26  aikell  阅读(230)  评论(0编辑  收藏  举报