模仿admin写一个类似功能的组件,叫做stark
admin流程之启动
单例模式
单例:只允许一个类实例化出一个对象
使用 new
为了使类只能出现一个实例,我们可以使用 new 来控制实例的创建过程,代码如下:
class Singleton(object):
_instance = None
def __new__(cls, *args, **kw):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kw) # 构建一个实例对象
return cls._instance
egon=Animal()
alex=Animal()
wupeiqi=Animal()
print(id(egon))
print(id(alex))
print(id(wupeiqi))
#三者完全相同
在上面的代码中,我们将类的实例和一个类变量 _instance 关联起来, 如果 cls._instance 为 None 则创建实例,否则直接返回 cls._instance。
使用模块
其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成.pyc文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。 因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。 如果我们真的想要一个单例类,可以考虑这样做: #mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
将上面的代码保存在文件 mysingleton.py 中,然后这样使用:
from mysingleton import my_singleton
my_singleton.foo()
启动
(settings---->installapp------>admin)
def autodiscover():autodiscover_modules('admin', register_to=site)
admin流程之注册
admin.site : 单例对象
class AdminSite():
def __init__(self, name='admin'):
self._registry = {}
#model就是表对象(Book),admin_class就是表的配置类(BookConfig)
def register(self, model, admin_class=None, **options):
if not admin_class:
#配置类
admin_class = ModelAdmin
self._registry[model] = admin_class(model, self)
site=AdminSite()
admin.site.register(Book,BookConfig)
admin.site.register(Book) # site._registry:{Book:ModelAdmin(Book)}
admin.site.register(Publish) # site._registry:{Book:ModelAdmin(Book),Publish:ModelAdmin(Publish)}
#site._registry:{模型类:该模型类的配置类对象,........}
知识点:url分发扩展
url()方法的扩展应用
from django.shortcuts import HttpResponse
url(r'^index/', index), # 一旦匹配成功, index(request)
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^yuan/', ([
url(r'^test01/', ([
url(r'^test1_1/', test01),
url(r'^test1_2/', test01),
],None,None)),
url(r'^test02/', test02),
url(r'^test03/', test03),
],None,None)),
]
格式,元祖里面是一个列表+None+None
admin流程之设计url
admin中:
from app01 import models
admin.site.register(models.Book)
admin.site.register(models.Publish)
admin.site.register(models.Author)
urls.py中:
from django.conf.urls import url
from django.contrib import admin
from django.shortcuts import render, HttpResponse, redirect
def list_view(request):
return HttpResponse("list_view")
def change(request, id):
return HttpResponse("change")
def delete(request, id):
return HttpResponse("delete")
def add(request):
return HttpResponse("add")
def get_urls2():
temp = [
url("^add/$", add),
url("^$", list_view),
url("^(\d+)/change/$", change),
url("^(\d+)/delete/$", delete)
]
return temp
def get_urls():
temp = []
print("_registry==>", admin.site._registry.items())
#{Book:ModelAdmin(Book),....}
#(<class 'app01.models.Book'>, <django.contrib.admin.options.ModelAdmin object at 0x00000234441F99B0>)
#Django启动会先加载settings,因此_registry中有值
for model, model_class_obj in admin.site._registry.items():
app_name = model._meta.app_label
#model._meta.app_label可以获取当前model所在app的名字
model_name = model._meta.model_name
#model._meta.model_name可以获得当前model的名称字符串
#用stark+app的名字+模型表的名字
#举例:http://127.0.0.1:8000/stark/app01/book/02/change/
temp.append(url(r"%s/%s/" % (app_name, model_name), (get_urls2(), None, None)))
return temp
urlpatterns = [
url(r'^admin/', admin.site.urls),
# [
# url(r'^test01/', test01),
# url(r'^test02/', test02),
# ]
url(r"^stark",(get_urls(),None,None))
]
最终url示例:
^stark auth/group/
^stark auth/user/
^stark app01/book/ ^add/$
^stark app01/book/ ^$
^stark app01/book/ ^(\d+)/change/$
^stark app01/book/ ^(\d+)/delete/$
^stark app01/publish/
^stark app01/author/
知识点回顾
函数和实例方法的区别
class Animal():
def running(self):
print("running...")
#从实例对象调
alex = Animal()
#alex会作为self参数传进去
alex.running()
#结果:
#running...
#当做函数调,必须传个参数
Animal.running(123)
#结果:
#running...
合并列表
temp = []
temp.append(1)
l = [2, 3, 4]
temp.extend(l)
print(temp)
#结果:
[1, 2, 3, 4]
根据字符串名称拿字段对象和数据
Model _meta API 它提供的方法可用于: 检索模型的所有字段实例 按名称检索模型的单个字段实例
from django.contrib.auth.models import User
# A field on the model
User._meta.get_field('username')
<django.db.models.fields.CharField: username>
# A field from another model that has a relation with the current model
User._meta.get_field('logentry')
<ManyToOneRel: admin.logentry>
按名称(字符串)检索模型的单个字段实例
filter_field_obj = self.model._meta.get_field(filter_field)
print("filter_field_obj==>", filter_field_obj)
得到app01.Book.publish和app01.Book.authors
拿对象下面的数据 .rel .to 来自于ForeignKey和ManyToManyField这俩类 .rel可以看到对象的数据类型,加上.to可以拿到对象下面的数据,.to只对一对一,一对多,多对多有效
print(filter_field_obj.rel.to)
结果:
< class 'app01.models.Publish'>
< class 'app01.models.Author'>
data_list = filter_field_obj.rel.to.objects.all()
print(filter_field_obj.rel.to)
结果:
< QuerySet[ < Publish: 图灵新知 >, < Publish: dzm >, < Publish: 老男孩出版社 >] >
< QuerySet[ < Author: egon >, < Author: alex >] >
获取字段对象的中文名
from app01.models import Book
#price是book表中一个字段对象
obj = Book._meta.get_field("price")
obj.verbose_name
结果:
'价格'
没有单独设置verbose_name时,则返回英文名,若想使用中文名则要在models定义verbose_name 例:
class Book(models.Model):
nid = models.AutoField(primary_key=True, verbose_name="编号")
title = models.CharField(max_length=32, verbose_name="名称")
price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="价格")
总结:自制Admin组件
这里我们叫它Stark
从访问说起:下面的思路是模拟Django原生Admin的写法
url流程
1.urls.py中:
from stark.service.sites import site
urlpatterns = [
# url(r"^stark",(get_urls(),None,None))
url(r'^stark/', site.urls)
]
2.site是StarkSite这个类的实例化结果,如下面
site = StarkSite()
3.site调用urls,返回的是get_urls中的temp列表
model的注册
在app01目录下面有stark.py文件 我们在这个文件中对model进行注册 在Django启动的时候会遍历每个app中是否有stark.py文件(settings中设置的),找到后会运行, 因此里面的内容在Django启动时就运行过了
1.site.register(Book) 注册Book表 2.register方法会执行:
self._registry[model] = admin_class(model, self)
3.也就是向_registry = {}这个字典中添加了如上内容 内容简单理解为:{Book:ModelStark(Book)},这里的键Book是model对象 此时_registry = {}中就有了内容
访问,url分发开始
1.循环_registry = {},使用site._registry.items()分别得到表的对象和配置类 2.用表对象得到app的名字和表的名字,由此拼出一级分发 3.二级分发用配置类调用urls2得出 配置类对象是ModelStark(Book) 接下来我们去找ModelStark这个类来看一下
class ModelStark()就是默认的配置类,如果注册表的时候没有自定义配置类则走这里, 同时自定义配置类也要继承ModelStark作为父类 在这个类下面:
class ModelStark(): #配置类
list_display = ["__str__", ]
def __init__(self, model, site):
self.model = model
这个self.model = model是关键,谁调用的它self.model就是那个表对象 如果是Book的话,那么self.model = Book
我们回到urls2
urls2返回值构建了一个return self.get_urls2(), None, None的格式 我们再去找get_urls2()这个函数 get_urls2和urls2都是class ModelStark下面的方法
get_urls2里面负责处理二级分发 这里举例访问到视图函数list_view中
def list_view(self, request):
data_list = self.model.objects.all()
此时model是Book,所以可以进行查询
self.model是谁
注:在视图函数中self.model到底是谁?这里是原生Admin组件写法的关键 这要看self是谁,一层一层往上找,最后会找到model_class_obj.urls2 model_class_obj来自于site._registry循环结果中的配置类,配置类是stark.py中注册的一个个模型的配置类 循环到谁就是谁调用urls2,self.model也就是那个对应的模型对象
注:我们无法确定用户访问那个url,因此提前准备好所有url,此时访问book,后面的model就是book,因此可以拿到相应数据
表单数据之多对多字段的处理
方法1 使用函数
在stark.py的BookConfig类中再增加一个函数
def display_atuhors(self, obj=None, is_header=False):
#判断is_header有没有值,有值的话就表示这是标题行,直接返回一个固定标题名
if is_header:
return "作者"
s = []
#obj是传过来的一个个的对象<Book: go语言第二版>, <Book: linux>,
#把这个对象中authors字段所有的内容循环起来分别添加到一个列表中
for author in obj.authors.all():
s.append(author.name)
return ",".join(s)
list_display = ["nid", "title", "price", "publish", display_atuhors,]
方法2
通过判断是不是多对多函数来做处理
data_list = self.model.objects.all()
for obj in data_list:
temp = []
#循环需要显示的字段
for field in self.list_display:
#判断这个字段是不是字符串
if isinstance(field, str):
try:
#引入ManyToManyField
from django.db.models.fields.related import ManyToManyField
#获取相应名称字段的对象
field_obj = self.model._meta.get_field(field) #注意,这里没法找出__str__,因此会报错
#判断这个对象是不是多对多
if isinstance(field_obj, ManyToManyField):
l = []
#利用反射,循环obj.field.all(),例如<Book: Book object>.authors,再加上.all()取这个对象下authors字段的全部内容
for i in getattr(obj, field).all():
#把循环结果添加到l列表
l.append(str(i)) #l==> ['egon', 'alex']
#用逗号把列表里面的元素拼起来
val = ",".join(l)
except Exception as e:
val = getattr(obj, field)
#添加到temp列表
temp.append(val)