python 全栈开发,Day116(可迭代对象,type创建动态类,偏函数,面向对象的封装,获取外键数据,组合搜索,领域驱动设计(DDD))...
昨日内容回顾
1. 三个类 ChangeList,封装列表页面需要的所有数据。StarkConfig,生成URL和视图对应关系 + 默认配置 AdminSite,用于保存 数据库类 和 处理该类的对象 的对应关系 + 路由分发_registry = {} 2. 知识点 inclusion_tagyieldurlencode_meta.model_name_meta.app_label深浅拷贝 QueryDict对象默认不可改 _mutable=True 生成器 路由分发:- include- ([],None,None)函数和方法的区别?Q的作用?构造复杂的查询条件models.User.object.filter(name__contains='李')models.User.object.filter(name__contains='李',email__contains='李')构造 or c1 = Q()c1.connector = 'OR'c1.children.append( ('name__contains','李') )c1.children.append( ('email__contains','李') )c2 = Q()c2.connector = 'ADN'c2.children.append( ('id__gt',2) )c3.children.append( ('age__lte',5) )c3 = Q()c3.connector = 'ADN'c3.add(c1,"ADN")c3.add(c2,"ADN")等同于(name=li or email = li) AND ( id>2 and age<=5)models.User.object.filter(con)反射 有2个地方用到了反射list_display: # 显示指定的列 row.name getattr(row,'name')# 批量操作response = getattr(self, action_name)(request)pass继承 class RoleConfig(StarkConfig):pass self到底是谁?反向生成URLreverse('xxx')reverse('namespace:xxx')多个namespace,也是以冒号分隔分页(保留原搜索条件) 跳转的url中保留_filter=原搜索条件ModelForm组件添加和修改functools- wraps,用于保留原函数的元信息(函数名/函数注释等信息)- partial,偏函数为函数默认传参。import functoolsdef func(a1,a2):print(a1+a2)new_func = functools.partial(func,8)new_func(7)new_func(2)new_func(8)预留可扩展位置request.GETrequest.GET.get('x')request.GET['x']request.GET.getlist('xxx')request.GET._mutable = True request.GET.copy()request.GET.urlencode()mark_safe xss攻击是什么?跨站脚本攻击一般用js代码,进行攻击。获取cookie,模拟登录,做非法操作!单例模式- 多个模块之间导入- 共享同一个数据时获取函数名__name__autodiscover_module自动发现模块装饰器 添加request参数,方便非视图函数调用requestorder_by排序展示__str__在models.py中使用3. QueryDict对象 params = request.GET.copy() # 使用深copyparams._mutable = True # 允许修改 params['k1'] = 'v1' # 添加单个params.setlist('k2',[11,22]) # 添加多个
View Code
一、可迭代对象
什么是可迭代对象
在之前学过的容器中,许多容器都是可迭代对象,可以直接用于for…in…循环的而对象都是可迭代对象,比如:list,tuple,dict,set,str等等。
可迭代对象满足条件:实现了__iter__方法
注意:__iter__方法必须返回一个迭代器(iter)
可迭代对象并不是一种具体的数据类型,比如list是可迭代对象,dict也是可迭代对象。
如何判断一个对象是否是可迭代对象? 使用isinstance()函数
from collections import Iterablea = isinstance("123",Iterable) b = isinstance(1,Iterable) print(a) #字符串是不是可迭代对象 返回True print(b) #数字是不是可迭代对象 返回False
举例1
看下面一段代码
class Foo(object):passobj = Foo() for item in obj:print(item)
执行报错:
TypeError: 'Foo' object is not iterable
提示不可迭代,怎么让它可以迭代?
先来看可迭代对象满足条件:实现了__iter__方法
那么在类中,定义一个__iter__方法,返回迭代器就可以了!
class Foo(object):def __iter__(self):# return iter([11,22,33,44]) # 返回迭代器yield 11 # 返回生成器(迭代器的一种)obj = Foo() for item in obj:print(item)
执行输出: 11
举例2
这是汽车之家的筛选页面,每一个行的数据是循环展示的!如何构造这些数据?
方法一:使用字典
data_list= [['1.0以下','1.1-1.6'],['汽油','柴油','混合动力','电动'], ]for row in data_list:for field in row:print(field)
执行输出:
1.0以下 1.1-1.6 汽油 柴油 混合动力 电动
View Code
方法二:使用对象+yield
class Row(object):def __init__(self,data):self.data = datadef __iter__(self):for item in self.data:yield itemdata_list= [Row(['1.0以下','1.1-1.6']),Row(['汽油','柴油','混合动力','电动']), ]for row in data_list:for field in row:print(field)
View Code
执行输出,效果同上!
为什么要用对象构造数据?因为要构造更复杂的数据结构!
class Row(object):def __init__(self,data):self.data = datadef __iter__(self):yield "<div>"yield '全部'for item in self.data:yield "<a href='/index/?p1=1.0'>%s</a>" %itemyield "</div>"data_list= [Row(['1.0以下','1.1-1.6']),Row(['汽油','柴油','混合动力','电动']), ]for row in data_list:for field in row:print(field)
View Code
执行输出:
<div> 全部 <a href='/index/?p1=1.0'>1.0以下</a> <a href='/index/?p1=1.0'>1.1-1.6</a> </div> <div> 全部 <a href='/index/?p1=1.0'>汽油</a> <a href='/index/?p1=1.0'>柴油</a> <a href='/index/?p1=1.0'>混合动力</a> <a href='/index/?p1=1.0'>电动</a> </div>
View Code
可以看出,这段数据,比上面的复杂!
如果用列表,那么有很多if判断!使用类,2层for循环就出来了!
为什么要输出a标签?因为前端渲染麻烦,后端生成a标签。前端容易展示,还可以做一些复杂的需求!
二、type创建动态类
前戏
举例1
def gen_cls():class Foo(object):passreturn Foocls = gen_cls()print(cls)
View Code
执行输出:
<class '__main__.gen_cls.<locals>.Foo'>
对象是由类创建的。那么类是由谁创建的?先带着这个疑问
举例2
name = "Foo" country = "中国" detail = lambda self, x: x + 1
根据以上三个参数创建一个类,类中有两个成员。实现的效果如下:
class Foo(object):country = '中国'def detail(self,x):return x + 1
如何用代码实现呢?
不能这么写
class name(object):country = '中国'...
那么class名就是name了,需要使用type
type创建动态类
type还有一种完全不同的功能,动态的创建类。
type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)
type可以像这样工作:
type(类名,由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
根据上面的3个条件,使用type创建一个类
name = "Foo" country = "中国" detail = lambda self, x: x + 1 # 使用type创建动态类 cls = type(name, (object,), {'country': '中国', 'detail': lambda self, x: x + 1})obj = cls() print(obj) print(obj.country) print(obj.detail(100))
View Code
执行输出:
<__main__.Foo object at 0x000001E0FBF2DA20> 中国 101
根据条件,由type动态创建了一个类。类的基本操作,比如实例化,获取静态字段,传参,都没有问题!
函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。type就是Python的内建元类
总结:对象是由类创建的。那么类默认是由type创建的
三、偏函数
什么是偏函数
偏函数是2.5版本以后引进来的东西。属于函数式编程的一部分,使用偏函数可以通过有效地"冻结"那些预先确定的参数,来缓存函数参数,然后在运行时,当获得需要的剩余参数后,可以将他们解冻,传递到最终的参数中,从而使用最终确定的所有参数去调用函数。
举例
示例函数
def func(a1,a2):return a1+a2res = func(1,3) print(res)
执行输出:4
使用偏函数
import functools def func(a1,a2):return a1+a2# 相当于给第一个参数,设置默认参数8 new_func = functools.partial(func,8) # 传入第二个参数 res = new_func(7) print(res)
执行输出:15
func有2个参数,怎么确定是给第一个参数,设置了默认参数?
import functools def func(a1,a2=22):return a1+a2# 相当于给第一个参数,设置默认参数8 new_func = functools.partial(func,8) # 传入第二个参数 res = new_func(7) print(res)
执行输出:15
注意:a1直接设置默认参数,是会报错的!默认参数必须在位置参数的后面!
在flask中,会用到偏函数
四、面向对象的封装
看下面一段数据
list_filter = ['全智贤','高圆圆','胡歌',]
如何用代码区分性别?
第一种方法:使用字典
list_filter = [{'name':'全智贤','sex':'女'},{'name':'高圆圆','sex':'女'},{'name':'胡歌','sex':'男'}, ]
再区分衣服的颜色呢?
list_filter = [{'name':'全智贤','sex':'女','color':'pink'},{'name':'高圆圆','sex':'女','color':'red'},{'name':'胡歌','sex':'男','color':'black'}, ]
如果还有其它属性呢?继续加?
使用字典也是一个封装思想
第二种方法:使用类(推荐)
class Option(object):def __init__(self,name,sex,color):self.name = nameself.sex = sexself.color = colorlist_filter = [# 字典对象做封装Option(name='全智贤',sex='女',color = 'pink'),Option(name='高圆圆',sex='女',color = 'red'),Option(name='胡歌',sex='男',color = 'black'), ]for item in list_filter:print(item.__dict__)
View Code
执行输出:
{'name': '全智贤', 'color': 'pink', 'sex': '女'} {'name': '高圆圆', 'color': 'red', 'sex': '女'} {'name': '胡歌', 'color': 'black', 'sex': '男'}
通过这2种方法对比,使用类更好一点!
如果使用字典,那么第一条数据,加了一个参数。而另外2条数据,却没有加!
那么页面展示就会错乱!
而使用类,则不会这个情况!另外一点,使用类,可以构造更加复杂的数据结构!
使用类,是新的 封装思想。可扩展功能比较方便
五、获取外键数据
外键分为3种: FK/M2M/O2O,分别对应一对多,多对多,一对一
给定2个参数
model_class = Depart # 模型表类 _field = "user" # 外键字段
要获取外键字段对应的所有数据,如何获取?
新建项目untitled4,注意:django版本为1.11
修改models.py,新建2个表
from django.db import models# Create your models here. class UserInfo(models.Model):title = models.CharField(verbose_name='标题',max_length=32)def __str__(self):return self.titleclass Depart(models.Model):name = models.CharField(verbose_name='部门名称',max_length=32)tel = models.CharField(verbose_name='联系电话',max_length=32)user = models.ForeignKey(verbose_name='负责人',to='UserInfo')def __str__(self):return self.name
View Code
使用2个命令生成表
python manage.py makemigrations python manage.py migrate
增加数据,使用Navicat打开sqlite3数据库,执行sql语句
INSERT INTO "app01_depart" ("name", "tel", "user_id") VALUES ('总经理', 23456342, 1); INSERT INTO "app01_depart" ("name", "tel", "user_id") VALUES ('技术部', 34565422, 1); INSERT INTO "app01_depart" ("name", "tel", "user_id") VALUES ('运营部', 34344523, 2);INSERT INTO "app01_userinfo" ("title") VALUES ('xiao'); INSERT INTO "app01_userinfo" ("title") VALUES ('zhang');
View Code
修改urls.py,增加路径
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [url(r'^admin/', admin.site.urls),url(r'^index/', views.index), ]
View Code
修改views.py,增加视图函数
from django.shortcuts import render,HttpResponse from app01 import models # Create your views here. def index(request):# 外键对象,通过get_field获取fk_obj = models.Depart._meta.get_field("user")print(fk_obj,type(fk_obj))# .all表示获取所有数据user_info_queryset = fk_obj.rel.model.objects.all()print(user_info_queryset)for i in user_info_queryset:print('row:',i) # 打印每一个行的数据return HttpResponse('...')
View Code
启动django项目,访问首页
查看Pycharm控制台输出:
app01.Depart.user <class 'django.db.models.fields.related.ForeignKey'> <QuerySet [<UserInfo: xiao>, <UserInfo: zhang>]> row: xiao row: zhang
注意:FK/M2M/O2O都是通过get_field来获取的
六、组合搜索
展示页面
务必下载github代码:
https://github.com/987334176/luffy_stark/archive/v1.2.zip
因为下面的内容,都是这份代码来修改的!
修改 stark-->templates-->stark-->changelist.html
{% extends 'stark/layout.html' %} {% load stark %}{% block css %}<style>.comb-search {padding: 5px 20px;}.comb-search .row .whole {width: 60px;float: left;}.comb-search .row .others {padding-left: 60px;}.comb-search .row a {display: inline-block;padding: 5px 8px;margin: 3px;border: 1px solid #d4d4d4; }.comb-search .row a {display: inline-block;padding: 5px 8px;margin: 3px;border: 1px solid #d4d4d4; }.comb-search a.active {color: #fff;background-color: #337ab7;border-color: #2e6da4; }</style> {% endblock %} {% block content %}<div>{#组合搜索#}<div class="comb-search"><div class="row"><div class="whole"><a href="#">全部</a></div><div class="others"><a href="#">条件1</a><a href="#">条件2</a><a href="#">条件3</a></div></div></div>{#添加按钮#}{% if cl.add_btn %}<div style="margin: 5px 0;">{{ cl.add_btn }}</div>{% endif %}{#搜索框#}{% if cl.search_list %}<div style="float: right;"><form method="GET" class="form-inline"><div class="form-group"><input class="form-control" type="text" name="q" value="{{ cl.q }}" placeholder="关键字搜索"><button class="btn btn-primary" type="submit"><i class="fa fa-search" aria-hidden="true"></i></button></div></form></div>{% endif %}<form class="form-inline" method="post">{% csrf_token %}{#批量操作#}{% if cl.action_list %}<div class="form-group"><select name="action" class="form-control" style="min-width: 200px;"><option>请选择功能</option>{% for item in cl.action_list %}<option value="{{ item.name }}">{{ item.text }}</option>{% endfor %}</select><input class="btn btn-primary" type="submit" value="执行"></div>{% endif %}{#使用table展示数据#}{% table cl %}{#分页展示#}<nav aria-label="Page navigation"><ul class="pagination">{{ cl.page.page_html|safe }}</ul></nav></form></div>{% endblock %}
View Code
访问url: http://127.0.0.1:8000/stark/app01/depart/list/
效果如下:
修改 stark-->server-->stark.py,增加变量 list_filter,增加一个钩子函数 get_list_filter
使用get_field获取字段
import functools from django.conf.urls import url from django.shortcuts import HttpResponse,render,redirect from types import FunctionType from django.utils.safestring import mark_safe from django.urls import reverse from django import forms from django.db.models import Q from django.http import QueryDictclass ChangeList(object):"""封装列表页面需要的所有功能"""def __init__(self,config,queryset,q,search_list,page):### 处理搜索 ###self.q = q # 搜索条件self.search_list = search_list # 搜索字段self.page = page # 分页# 配置参数self.config = config# 批量操作self.action_list = [{'name': func.__name__, 'text': func.text} for func in config.get_action_list()]# 添加按钮self.add_btn = config.get_add_btn()# ORM执行结果self.queryset = queryset# 显示的列self.list_display = config.get_list_display()class StarkConfig(object):def __init__(self,model_class,site):self.model_class = model_classself.site = site# 定义request变量,用于非视图函数使用。# 在wrapper装饰器中,对这个值重新赋值!self.request = None# url中的搜索条件,存在字典中。key为_filterself.back_condition_key = "_filter"def display_checkbox(self,row=None,header=False): # 显示复选框if header:# 输出中文return "选择"# 注意:这里要写row.pk,不能写row.id。你不能保证每一个表的主键都是idreturn mark_safe("<input type='checkbox' name='pk' value='%s' />" % row.pk)def display_edit(self, row=None, header=False):if header:return "编辑"return mark_safe('<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a>' % self.reverse_edit_url(row))def display_del(self, row=None, header=False):if header:return "删除"return mark_safe('<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>' % self.reverse_del_url(row))def display_edit_del(self, row=None, header=False):if header:return "操作"tpl = """<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a> |<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>""" % (self.reverse_edit_url(row), self.reverse_del_url(row),)return mark_safe(tpl)def multi_delete(self, request): # 批量删除"""批量删除的action:param request::return:"""pk_list = request.POST.getlist('pk')self.model_class.objects.filter(pk__in=pk_list).delete()# return HttpResponse('删除成功') multi_delete.text = "批量删除" # 添加自定义属性textdef multi_init(self,request): # 批量初始化print('批量初始化')multi_init.text = "批量初始化" # 添加自定义属性text order_by = [] # 需要排序的字段,由用户自定义list_display = [] # 定义显示的列,由用户自定义model_form_class = None # form组件需要的model_classaction_list = [] # 批量操作方法# 搜索字段,如果是跨表字段,要按照ORM语法来search_list = []list_filter = [] # 组合搜索def get_order_by(self): # 获取排序列表return self.order_bydef get_list_display(self): # 获取显示的列return self.list_displaydef get_add_btn(self): # 显示添加按钮return mark_safe('<a href="%s" class="btn btn-success">添加</a>' % self.reverse_add_url())def get_model_form_class(self):"""获取ModelForm类:return:"""if self.model_form_class:return self.model_form_classclass AddModelForm(forms.ModelForm):class Meta:model = self.model_classfields = "__all__"return AddModelFormdef get_action_list(self): # 获取批量操作方法val = [] # 空列表# 扩展列表的元素 val.extend(self.action_list)return valdef get_action_dict(self): # 获取匹配操作字典val = {}for item in self.action_list:# 以方法名为keyval[item.__name__] = itemreturn valdef get_search_list(self): # 获取搜索字段val = []val.extend(self.search_list)return valdef get_search_condition(self, request): # 根据关键字,组合ORM查询语句search_list = self.get_search_list() # ['name','tel']q = request.GET.get('q', "") # 搜索条件con = Q()con.connector = "OR" # 以OR作为连接符if q: # 判断条件不为空for field in search_list:# 合并条件进行查询, __contains表示使用like查询con.children.append(('%s__contains' % field, q))return search_list, q, condef get_list_filter(self): # 获取组合搜索条件val = []val.extend(self.list_filter)return valdef changelist_view(self, request):"""所有URL查看列表页面:param request::return:"""if request.method == 'POST':action_name = request.POST.get('action')action_dict = self.get_action_dict()if action_name not in action_dict:return HttpResponse('非法请求')response = getattr(self, action_name)(request)if response:return response### 处理搜索 ###search_list, q, con = self.get_search_condition(request)# ##### 处理分页 #####from stark.utils.pagination import Pagination# 总条数total_count = self.model_class.objects.filter(con).count()# 复制GET参数query_params = request.GET.copy()# 允许编辑query_params._mutable = True# 使用分页类Pagination,传入参数。每页显示3条page = Pagination(request.GET.get('page'), total_count, request.path_info, query_params, per_page=3)# 根据排序列表进行排序,以及分页功能queryset = self.model_class.objects.filter(con).order_by(*self.get_order_by())[page.start:page.end]cl = ChangeList(self, queryset, q, search_list, page)# ######## 组合搜索 ########## list_filter = ['name','user']list_filter = self.get_list_filter()for field in list_filter:# 如果field = "name" --> 查Depart所有数据# 如果field = "user" --> 查UserInfo所有数据_field = self.model_class._meta.get_field(field)print(_field,type(_field)) # 打印字段类型 context = {'cl': cl}# 注意:要传入参数return render(request,'stark/changelist.html',context)def add_view(self, request):"""所有的添加页面,都在此方法处理使用ModelForm实现:param request::return:"""# 添加数据,使用ModelFormAddModelForm = self.get_model_form_class()if request.method == "GET":form = AddModelForm()return render(request,'stark/change.html',{'form':form})form = AddModelForm(request.POST) # 接收POST数据if form.is_valid(): # 验证数据form.save() # 自动保存数据# 反向生成url,跳转到列表页面return redirect(self.reverse_list_url())# 渲染页面,此时会保存表单数据return render(request, 'stark/change.html', {'form': form})def change_view(self, request, pk):"""所有编辑页面:param request::param pk::return:"""# 查看单条数据obj = self.model_class.objects.filter(pk=pk).first()if not obj:return HttpResponse('数据不存在')# 获取model_form类ModelFormClass = self.get_model_form_class()if request.method == 'GET':# instance表示生成默认值form = ModelFormClass(instance=obj)# 渲染页面,添加和修改可以共用一个一个模板文件return render(request, 'stark/change.html', {'form': form})# instance = obj 表示指定给谁做修改form = ModelFormClass(data=request.POST, instance=obj)if form.is_valid():form.save() # 修改数据# 跳转到列表页面return redirect(self.reverse_list_url())return render(request, 'stark/change.html', {'form': form})def delete_view(self, request, pk):"""所有删除页面:param request::param pk::return:"""if request.method == "GET":# cancel_url表示用户点击取消时,跳转到列表页面return render(request, 'stark/delete.html', {'cancel_url': self.reverse_list_url()})# 定位单条数据,并删除!self.model_class.objects.filter(pk=pk).delete()return redirect(self.reverse_list_url())def wrapper(self, func):@functools.wraps(func)def inner(request, *args, **kwargs):self.request = requestreturn func(request, *args, **kwargs)return innerdef get_urls(self):info = self.model_class._meta.app_label, self.model_class._meta.model_nameurlpatterns = [url(r'^list/$', self.wrapper(self.changelist_view), name='%s_%s_changelist' % info),url(r'^add/$', self.wrapper(self.add_view), name='%s_%s_add' % info),url(r'^(?P<pk>\d+)/change/', self.wrapper(self.change_view), name='%s_%s_change' % info),url(r'^(?P<pk>\d+)/del/', self.wrapper(self.delete_view), name='%s_%s_del' % info),]extra = self.extra_url()if extra: # 判断变量不为空# 扩展路由 urlpatterns.extend(extra)# print(urlpatterns)return urlpatternsdef extra_url(self): # 额外的路由,由调用者重构passdef reverse_list_url(self): # 反向生成访问列表的urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespacename = '%s:%s_%s_changelist' % (namespace, app_label, model_name)list_url = reverse(name)# 获取当前请求的_filter参数,也就是跳转之前的搜索条件origin_condition = self.request.GET.get(self.back_condition_key)if not origin_condition: # 如果没有获取到return list_url # 返回列表页面# 列表地址和搜索条件拼接list_url = "%s?%s" % (list_url, origin_condition,)return list_urldef reverse_add_url(self): # 反向生成添加urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespacename = '%s:%s_%s_add' % (namespace, app_label, model_name)add_url = reverse(name)if not self.request.GET: # 判断get参数为空return add_url # 返回原url# request.GET的数据类型为QueryDict# 对QueryDict做urlencode编码param_str = self.request.GET.urlencode() # 比如q=xiao&age=20# 允许对QueryDict做修改new_query_dict = QueryDict(mutable=True)# 添加键值对. _filter = param_strnew_query_dict[self.back_condition_key] = param_str# 添加url和搜索条件做拼接add_url = "%s?%s" % (add_url, new_query_dict.urlencode(),)# 返回最终urlreturn add_urldef reverse_edit_url(self, row): # 反向生成编辑行内容的urlapp_label = self.model_class._meta.app_label # app名model_name = self.model_class._meta.model_name # 表名namespace = self.site.namespace # 命名空间# 拼接字符串,这里为changename = '%s:%s_%s_change' % (namespace, app_label, model_name)# 反向生成url,传入参数pk=row.pkedit_url = reverse(name, kwargs={'pk': row.pk})if not self.request.GET:return edit_urlparam_str = self.request.GET.urlencode()new_query_dict = QueryDict(mutable=True)new_query_dict[self.back_condition_key] = param_stredit_url = "%s?%s" % (edit_url, new_query_dict.urlencode(),)return edit_urldef reverse_del_url(self, row): # 反向生成删除行内容的urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespace# 注意:这里为delname = '%s:%s_%s_del' % (namespace, app_label, model_name)del_url = reverse(name, kwargs={'pk': row.pk})if not self.request.GET:return del_urlparam_str = self.request.GET.urlencode()new_query_dict = QueryDict(mutable=True)new_query_dict[self.back_condition_key] = param_strdel_url = "%s?%s" % (del_url, new_query_dict.urlencode(),)return del_url@propertydef urls(self):return self.get_urls()class AdminSite(object):def __init__(self):self._registry = {}self.app_name = 'stark'self.namespace = 'stark'def register(self,model_class,stark_config=None):# not None的结果为Tureif not stark_config:# 也就是说,当其他应用调用register时,如果不指定stark_config参数# 那么必然执行下面这段代码!# stark_config和StarkConfig是等值的!都能实例化stark_config = StarkConfig# 添加键值对,实例化类StarkConfig,传入参数model_class# self指的是AdminSite类self._registry[model_class] = stark_config(model_class,self)# print(self._registry) # 打印字典"""{app01.models.UserInfo:StarkConfig(app01.models.UserInfo)app02.models.Role:RoleConfig(app02.models.Role)}"""# for k, v in self._registry.items():# print(k,v)def get_urls(self):urlpatterns = []for k, v in self._registry.items():# k=modes.UserInfo,v=StarkConfig(models.UserInfo), # 封装:model_class=UserInfo,site=site对象# k=modes.Role,v=RoleConfig(models.Role) # 封装:model_class=Role,site=site对象app_label = k._meta.app_labelmodel_name = k._meta.model_nameurlpatterns.append(url(r'^%s/%s/' % (app_label, model_name,), (v.urls, None, None)))return urlpatterns@propertydef urls(self):# 调用get_urls方法# self.app_name和self.namespace值是一样的,都是starkreturn self.get_urls(), self.app_name, self.namespacesite = AdminSite() # 实例化类
View Code
修改 app01-->stark.py,增加list_filter属性
from stark.server.stark import site, StarkConfig from app01 import models from django import forms from django.shortcuts import render from django.conf.urls import urlclass UserInfoConfig(StarkConfig):list_display = ['id', 'username']class DepartModelForm(forms.ModelForm):class Meta:model = models.Departfields = "__all__"def clean_name(self): # 定义钩子# print(self.cleaned_data['name'])return self.cleaned_data['name']class DepartConfig(StarkConfig):list_display = [StarkConfig.display_checkbox,'id','name', 'tel', 'user',StarkConfig.display_edit_del]# model_form_class = DepartModelForm# 批量操作action_list = [StarkConfig.multi_delete,StarkConfig.multi_init]# 搜索字段,如果是跨表字段,要按照ORM语法来search_list = ['name', 'tel', 'user__username']list_filter = ["name","user"] # 组合搜索# def get_add_btn(self): # 返回None,表示不显示添加按钮# pass# def changelist_view(self, request): # 重写changelist_view方法# # 渲染自定义的列表页面# return render(request,'stark/custom_list.html')# def get_urls(self): # 自定义路由# info = self.model_class._meta.app_label, self.model_class._meta.model_name# # urlpatterns = [# url(r'^list/$', self.changelist_view, name='%s_%s_changelist' % info),# ]# return urlpatterns site.register(models.UserInfo, UserInfoConfig) site.register(models.Depart, DepartConfig)
View Code
刷新页面,查看Pycharm控制台输出:
app01.Depart.name <class 'django.db.models.fields.CharField'> app01.Depart.user <class 'django.db.models.fields.related.ForeignKey'>
ForeignKey表示一对多
修改 stark-->server-->stark.py,导入模块ForeignKey,判断类型。如果是FK,就跨表查询。
添加list_filter_rows列表,并传给模板
import functools from django.conf.urls import url from django.shortcuts import HttpResponse,render,redirect from types import FunctionType from django.utils.safestring import mark_safe from django.urls import reverse from django import forms from django.db.models import Q from django.http import QueryDict from django.db.models.fields.related import ForeignKeyclass ChangeList(object):"""封装列表页面需要的所有功能"""def __init__(self,config,queryset,q,search_list,page):### 处理搜索 ###self.q = q # 搜索条件self.search_list = search_list # 搜索字段self.page = page # 分页# 配置参数self.config = config# 批量操作self.action_list = [{'name': func.__name__, 'text': func.text} for func in config.get_action_list()]# 添加按钮self.add_btn = config.get_add_btn()# ORM执行结果self.queryset = queryset# 显示的列self.list_display = config.get_list_display()class StarkConfig(object):def __init__(self,model_class,site):self.model_class = model_classself.site = site# 定义request变量,用于非视图函数使用。# 在wrapper装饰器中,对这个值重新赋值!self.request = None# url中的搜索条件,存在字典中。key为_filterself.back_condition_key = "_filter"def display_checkbox(self,row=None,header=False): # 显示复选框if header:# 输出中文return "选择"# 注意:这里要写row.pk,不能写row.id。你不能保证每一个表的主键都是idreturn mark_safe("<input type='checkbox' name='pk' value='%s' />" % row.pk)def display_edit(self, row=None, header=False):if header:return "编辑"return mark_safe('<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a>' % self.reverse_edit_url(row))def display_del(self, row=None, header=False):if header:return "删除"return mark_safe('<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>' % self.reverse_del_url(row))def display_edit_del(self, row=None, header=False):if header:return "操作"tpl = """<a href="%s"><i class="fa fa-edit" aria-hidden="true"></i></a></a> |<a href="%s"><i class="fa fa-trash-o" aria-hidden="true"></i></a>""" % (self.reverse_edit_url(row), self.reverse_del_url(row),)return mark_safe(tpl)def multi_delete(self, request): # 批量删除"""批量删除的action:param request::return:"""pk_list = request.POST.getlist('pk')self.model_class.objects.filter(pk__in=pk_list).delete()# return HttpResponse('删除成功') multi_delete.text = "批量删除" # 添加自定义属性textdef multi_init(self,request): # 批量初始化print('批量初始化')multi_init.text = "批量初始化" # 添加自定义属性text order_by = [] # 需要排序的字段,由用户自定义list_display = [] # 定义显示的列,由用户自定义model_form_class = None # form组件需要的model_classaction_list = [] # 批量操作方法# 搜索字段,如果是跨表字段,要按照ORM语法来search_list = []list_filter = [] # 组合搜索def get_order_by(self): # 获取排序列表return self.order_bydef get_list_display(self): # 获取显示的列return self.list_displaydef get_add_btn(self): # 显示添加按钮return mark_safe('<a href="%s" class="btn btn-success">添加</a>' % self.reverse_add_url())def get_model_form_class(self):"""获取ModelForm类:return:"""if self.model_form_class:return self.model_form_classclass AddModelForm(forms.ModelForm):class Meta:model = self.model_classfields = "__all__"return AddModelFormdef get_action_list(self): # 获取批量操作方法val = [] # 空列表# 扩展列表的元素 val.extend(self.action_list)return valdef get_action_dict(self): # 获取匹配操作字典val = {}for item in self.action_list:# 以方法名为keyval[item.__name__] = itemreturn valdef get_search_list(self): # 获取搜索字段val = []val.extend(self.search_list)return valdef get_search_condition(self, request): # 根据关键字,组合ORM查询语句search_list = self.get_search_list() # ['name','tel']q = request.GET.get('q', "") # 搜索条件con = Q()con.connector = "OR" # 以OR作为连接符if q: # 判断条件不为空for field in search_list:# 合并条件进行查询, __contains表示使用like查询con.children.append(('%s__contains' % field, q))return search_list, q, condef get_list_filter(self): # 获取组合搜索条件val = []val.extend(self.list_filter)return valdef changelist_view(self, request):"""所有URL查看列表页面:param request::return:"""if request.method == 'POST':action_name = request.POST.get('action')action_dict = self.get_action_dict()if action_name not in action_dict:return HttpResponse('非法请求')response = getattr(self, action_name)(request)if response:return response### 处理搜索 ###search_list, q, con = self.get_search_condition(request)# ##### 处理分页 #####from stark.utils.pagination import Pagination# 总条数total_count = self.model_class.objects.filter(con).count()# 复制GET参数query_params = request.GET.copy()# 允许编辑query_params._mutable = True# 使用分页类Pagination,传入参数。每页显示3条page = Pagination(request.GET.get('page'), total_count, request.path_info, query_params, per_page=3)# 根据排序列表进行排序,以及分页功能queryset = self.model_class.objects.filter(con).order_by(*self.get_order_by())[page.start:page.end]cl = ChangeList(self, queryset, q, search_list, page)# ######## 组合搜索 ########## list_filter = ['name','user']list_filter = self.get_list_filter()list_filter_rows = []for field in list_filter:# 如果field = "name" --> 查Depart所有数据# 如果field = "user" --> 查UserInfo所有数据_field = self.model_class._meta.get_field(field)# print(_field,type(_field)) # 打印字段类型if isinstance(_field,ForeignKey):row = _field.rel.model.objects.all()else:row = self.model_class.objects.all()list_filter_rows.append(row)context = {'cl': cl,'list_filter_rows':list_filter_rows}# 注意:要传入参数return render(request,'stark/changelist.html',context)def add_view(self, request):"""所有的添加页面,都在此方法处理使用ModelForm实现:param request::return:"""# 添加数据,使用ModelFormAddModelForm = self.get_model_form_class()if request.method == "GET":form = AddModelForm()return render(request,'stark/change.html',{'form':form})form = AddModelForm(request.POST) # 接收POST数据if form.is_valid(): # 验证数据form.save() # 自动保存数据# 反向生成url,跳转到列表页面return redirect(self.reverse_list_url())# 渲染页面,此时会保存表单数据return render(request, 'stark/change.html', {'form': form})def change_view(self, request, pk):"""所有编辑页面:param request::param pk::return:"""# 查看单条数据obj = self.model_class.objects.filter(pk=pk).first()if not obj:return HttpResponse('数据不存在')# 获取model_form类ModelFormClass = self.get_model_form_class()if request.method == 'GET':# instance表示生成默认值form = ModelFormClass(instance=obj)# 渲染页面,添加和修改可以共用一个一个模板文件return render(request, 'stark/change.html', {'form': form})# instance = obj 表示指定给谁做修改form = ModelFormClass(data=request.POST, instance=obj)if form.is_valid():form.save() # 修改数据# 跳转到列表页面return redirect(self.reverse_list_url())return render(request, 'stark/change.html', {'form': form})def delete_view(self, request, pk):"""所有删除页面:param request::param pk::return:"""if request.method == "GET":# cancel_url表示用户点击取消时,跳转到列表页面return render(request, 'stark/delete.html', {'cancel_url': self.reverse_list_url()})# 定位单条数据,并删除!self.model_class.objects.filter(pk=pk).delete()return redirect(self.reverse_list_url())def wrapper(self, func):@functools.wraps(func)def inner(request, *args, **kwargs):self.request = requestreturn func(request, *args, **kwargs)return innerdef get_urls(self):info = self.model_class._meta.app_label, self.model_class._meta.model_nameurlpatterns = [url(r'^list/$', self.wrapper(self.changelist_view), name='%s_%s_changelist' % info),url(r'^add/$', self.wrapper(self.add_view), name='%s_%s_add' % info),url(r'^(?P<pk>\d+)/change/', self.wrapper(self.change_view), name='%s_%s_change' % info),url(r'^(?P<pk>\d+)/del/', self.wrapper(self.delete_view), name='%s_%s_del' % info),]extra = self.extra_url()if extra: # 判断变量不为空# 扩展路由 urlpatterns.extend(extra)# print(urlpatterns)return urlpatternsdef extra_url(self): # 额外的路由,由调用者重构passdef reverse_list_url(self): # 反向生成访问列表的urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespacename = '%s:%s_%s_changelist' % (namespace, app_label, model_name)list_url = reverse(name)# 获取当前请求的_filter参数,也就是跳转之前的搜索条件origin_condition = self.request.GET.get(self.back_condition_key)if not origin_condition: # 如果没有获取到return list_url # 返回列表页面# 列表地址和搜索条件拼接list_url = "%s?%s" % (list_url, origin_condition,)return list_urldef reverse_add_url(self): # 反向生成添加urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespacename = '%s:%s_%s_add' % (namespace, app_label, model_name)add_url = reverse(name)if not self.request.GET: # 判断get参数为空return add_url # 返回原url# request.GET的数据类型为QueryDict# 对QueryDict做urlencode编码param_str = self.request.GET.urlencode() # 比如q=xiao&age=20# 允许对QueryDict做修改new_query_dict = QueryDict(mutable=True)# 添加键值对. _filter = param_strnew_query_dict[self.back_condition_key] = param_str# 添加url和搜索条件做拼接add_url = "%s?%s" % (add_url, new_query_dict.urlencode(),)# 返回最终urlreturn add_urldef reverse_edit_url(self, row): # 反向生成编辑行内容的urlapp_label = self.model_class._meta.app_label # app名model_name = self.model_class._meta.model_name # 表名namespace = self.site.namespace # 命名空间# 拼接字符串,这里为changename = '%s:%s_%s_change' % (namespace, app_label, model_name)# 反向生成url,传入参数pk=row.pkedit_url = reverse(name, kwargs={'pk': row.pk})if not self.request.GET:return edit_urlparam_str = self.request.GET.urlencode()new_query_dict = QueryDict(mutable=True)new_query_dict[self.back_condition_key] = param_stredit_url = "%s?%s" % (edit_url, new_query_dict.urlencode(),)return edit_urldef reverse_del_url(self, row): # 反向生成删除行内容的urlapp_label = self.model_class._meta.app_labelmodel_name = self.model_class._meta.model_namenamespace = self.site.namespace# 注意:这里为delname = '%s:%s_%s_del' % (namespace, app_label, model_name)del_url = reverse(name, kwargs={'pk': row.pk})if not self.request.GET:return del_urlparam_str = self.request.GET.urlencode()new_query_dict = QueryDict(mutable=True)new_query_dict[self.back_condition_key] = param_strdel_url = "%s?%s" % (del_url, new_query_dict.urlencode(),)return del_url@propertydef urls(self):return self.get_urls()class AdminSite(object):def __init__(self):self._registry = {}self.app_name = 'stark'self.namespace = 'stark'def register(self,model_class,stark_config=None):# not None的结果为Tureif not stark_config:# 也就是说,当其他应用调用register时,如果不指定stark_config参数# 那么必然执行下面这段代码!# stark_config和StarkConfig是等值的!都能实例化stark_config = StarkConfig# 添加键值对,实例化类StarkConfig,传入参数model_class# self指的是AdminSite类self._registry[model_class] = stark_config(model_class,self)# print(self._registry) # 打印字典"""{app01.models.UserInfo:StarkConfig(app01.models.UserInfo)app02.models.Role:RoleConfig(app02.models.Role)}"""# for k, v in self._registry.items():# print(k,v)def get_urls(self):urlpatterns = []for k, v in self._registry.items():# k=modes.UserInfo,v=StarkConfig(models.UserInfo), # 封装:model_class=UserInfo,site=site对象# k=modes.Role,v=RoleConfig(models.Role) # 封装:model_class=Role,site=site对象app_label = k._meta.app_labelmodel_name = k._meta.model_nameurlpatterns.append(url(r'^%s/%s/' % (app_label, model_name,), (v.urls, None, None)))return urlpatterns@propertydef urls(self):# 调用get_urls方法# self.app_name和self.namespace值是一样的,都是starkreturn self.get_urls(), self.app_name, self.namespacesite = AdminSite() # 实例化类
View Code
修改 stark-->templates-->stark-->changelist.html,for循环list_filter_rows列表
{% extends 'stark/layout.html' %} {% load stark %}{% block css %}<style>.comb-search {padding: 5px 20px;}.comb-search .row .whole {width: 60px;float: left;}.comb-search .row .others {padding-left: 60px;}.comb-search .row a {display: inline-block;padding: 5px 8px;margin: 3px;border: 1px solid #d4d4d4; }.comb-search .row a {display: inline-block;padding: 5px 8px;margin: 3px;border: 1px solid #d4d4d4; }.comb-search a.active {color: #fff;background-color: #337ab7;border-color: #2e6da4; }</style> {% endblock %} {% block content %}<div>{#组合搜索#}<div class="comb-search">{% for row in list_filter_rows %}<div class="row"><div class="whole"><a href="#">全部</a></div><div class="others">{% for obj in row %}<a href="#">{{ obj }}</a>{% endfor %}</div></div>{% endfor %}</div>{#添加按钮#}{% if cl.add_btn %}<div style="margin: 5px 0;">{{ cl.add_btn }}</div>{% endif %}{#搜索框#}{% if cl.search_list %}<div style="float: right;"><form method="GET" class="form-inline"><div class="form-group"><input class="form-control" type="text" name="q" value="{{ cl.q }}" placeholder="关键字搜索"><button class="btn btn-primary" type="submit"><i class="fa fa-search" aria-hidden="true"></i></button></div></form></div>{% endif %}<form class="form-inline" method="post">{% csrf_token %}{#批量操作#}{% if cl.action_list %}<div class="form-group"><select name="action" class="form-control" style="min-width: 200px;"><option>请选择功能</option>{% for item in cl.action_list %}<option value="{{ item.name }}">{{ item.text }}</option>{% endfor %}</select><input class="btn btn-primary" type="submit" value="执行"></div>{% endif %}{#使用table展示数据#}{% table cl %}{#分页展示#}<nav aria-label="Page navigation"><ul class="pagination">{{ cl.page.page_html|safe }}</ul></nav></form></div>{% endblock %}
View Code
刷新页面,效果如下:
后台搜索
关于后面的详细步骤,没有时间写了。附上完整代码:
链接:https://pan.baidu.com/s/1fLOGH_3G7hPTvCYKX84UdQ 密码:m8rh
七、领域驱动设计(DDD)
什么是领域驱动设计(DDD)
2004年著名建模专家Eric Evans发表了他最具影响力的书籍:《Domain-Driven Design –Tackling Complexity in the Heart of Software》(中文译名:领域驱动设计—软件核心复杂性应对之道),书中提出了“领域驱动设计(简称 DDD)”的概念。
领域驱动设计事实上是针对OOAD的一个扩展和延伸,DDD基于面向对象分析与设计技术,对技术架构进行了分层规划,同时对每个类进行了策略和类型的划分。
领域模型是领域驱动的核心。采用DDD的设计思想,业务逻辑不再集中在几个大型的类上,而是由大量相对小的领域对象(类)组成,这些类具备自己的状态和行为,每个类是相对完整的独立体,并与现实领域的业务对象映射。领域模型就是由这样许多的细粒度的类组成。基于领域驱动的设计,保证了系统的可维护性、扩展性和复用性,在处理复杂业务逻辑方面有着先天的优势。
领域驱动设计的特点
领域驱动的核心应用场景就是解决复杂业务的设计问题,其特点与这一核心主题息息相关:
- 分层架构与职责划分:领域驱动设计很好的遵循了关注点分离的原则,提出了成熟、清晰的分层架构。同时对领域对象进行了明确的策略和职责划分,让领域对象和现实世界中的业务形成良好的映射关系,为领域专家与开发人员搭建了沟通的桥梁。
- 复用:在领域驱动设计中,领域对象是核心,每个领域对象都是一个相对完整的内聚的业务对象描述,所以可以形成直接的复用。同时设计过程是基于领域对象而不是基于数据库的Schema,所以整个设计也是可以复用的。
- 使用场景:适合具备复杂业务逻辑的软件系统,对软件的可维护性和扩展性要求比较高。不适用简单的增删改查业务。
举例:商品价格
一个商品,有商品名(name),原价(price),折扣价(discount)。如何用类来表示呢?
常规类
class Goods(object):def __init__(self,name,price,discount):self.name = nameself.price = priceself.discount = discount
如果要增加优惠券(满减,立减,折扣),怎么办?
领域驱动设计
class BaseCoupon(object):"""优惠券基础类"""passclass Coupon1(BaseCoupon):"""满减"""passclass Coupon2(BaseCoupon):"""立减"""passclass Coupon3(BaseCoupon):"""折扣"""passclass Price(object):"""商品价格"""def __init__(self,price,discount):self.price = priceself.discount = discountdef pay(self): # 交易价格passclass Goods(object):def __init__(self,name):self.name = name
View Code
重点就是建模
一般做3年开发,就可以领悟 领域驱动设计。具体还得看个人的领悟能力!
其他更多信息,请参考链接:
https://www.cnblogs.com/yihaha/p/3977496.html
关于python方面领域驱动设计的相关书籍,暂时还没有。
主流的是JAVA,C#,PHP
领域驱动设计它是一种编程思想,重点就是建模!对于开发一个大型项目,尤为重要!
在python源代码中,就利用这种思想。类中层层嵌套类!
对于个人编程能力的提升,可以看一下相关书籍!
未完待续...
转载于:https://www.cnblogs.com/xiao987334176/p/9575783.html
python 全栈开发,Day116(可迭代对象,type创建动态类,偏函数,面向对象的封装,获取外键数据,组合搜索,领域驱动设计(DDD))...相关推荐
- python 全栈开发,Day63(子查询,MySQl创建用户和授权,可视化工具Navicat的使用,pymysql模块的使用)...
昨日内容回顾 外键的变种三种关系:多对一:左表的多 对右表一 成立左边的一 对右表多 不成立foreign key(从表的id) refreences 主表的(id)多对多建立第三张表(foreign ...
- 路飞学城python全栈开发_[Python] 老男孩路飞学城Python全栈开发重点班 骑士计划最新100G...
简介 老男孩&路飞学城Python全栈开发重点班课程,作为Python全栈教学系列的重头戏,投入了全新的课程研发和教学精力,也是Python骑士计划的核心教学,由ALEX老师开班镇守,一线技术 ...
- python 全栈开发,Day133(玩具与玩具之间的对话,基于jieba gensim pypinyin实现的自然语言处理,打包apk)...
python 全栈开发,Day133(玩具与玩具之间的对话,基于jieba gensim pypinyin实现的自然语言处理,打包apk) 先下载github代码,下面的操作,都是基于这个版本来的! ...
- python 全栈开发,Day137(爬虫系列之第4章-scrapy框架)
python 全栈开发,Day137(爬虫系列之第4章-scrapy框架) 一.scrapy框架简介 1. 介绍 Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所 ...
- python全栈开发中级班全程笔记(第二模块、第四章)(常用模块导入)
python全栈开发笔记第二模块 第四章 :常用模块(第二部分) 一.os 模块的 详解 1.os.getcwd() :得到当前工作目录,即当前python解释器所在目录路径 impor ...
- Python全栈开发【基础-09】深浅拷贝+while循环
专栏介绍: 本专栏为Python全栈开发系列文章,技术包括Python基础.函数.文件.面向对象.网络编程.并发编程.MySQL数据库.HTML.JavaScript.CSS.JQuery.boots ...
- python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)
python 全栈开发,Day104(DRF用户认证,结算中心,django-redis) 考试第二部分:MySQL数据库 6. MySQL中char和varchar的区别(1分) char是定长, ...
- python 全栈开发,Day128(创建二维码,扫码,创建玩具的基本属性)
python 全栈开发,Day128(创建二维码,扫码,创建玩具的基本属性) 昨日内容回顾 1.app播放音乐plus.audio.createPlayer(文件路径/URL)player.play( ...
- python 全栈开发,Day86(上传文件,上传头像,CBV,python读写Excel,虚拟环境virtualenv)
python 全栈开发,Day86(上传文件,上传头像,CBV,python读写Excel,虚拟环境virtualenv) 一.上传文件 上传一个图片 使用input type="file& ...
最新文章
- debian10 更换阿里源
- ORB-SLAM3中的ORB提取
- IntelliJ IDEA绑定GitHub实现代码版本控制实例演示,IDEA上传、更新、同步项目到GitHub演示,Git的下载与安装
- 十三五乐山全力推进智慧城市和新能源汽车等项目
- Mysql Oracle 工具推荐
- AtCoder AGC033F Adding Edges (图论)
- verilog 不可综合语句 总结 汇总(Z)
- mysql导入数据库某张表_MSSQLServer2005 导出导入数据库中某张表的数据
- 设计实现优雅修改redux数据流的一个库 - redux-chef
- 【微信】微信小程序 应用内的页面跳转在添加了tab以后就跳转不成功的问题解决...
- 生信宝典联合科学出版社在双 11推出生物信息专题书单 5 折优惠!学起来!
- 好戏连台,BCH独领风骚
- 致敬CondConv!Intel提出即插即用的“万金油”动态卷积ODConv
- NAND Flash 芯片测试
- 现代OpenGL教程 01 - 入门指南
- ATTck 命令执行 —— 远程动态数据交换
- 使用WebSocket实现多组即时对战五子棋
- 安卓控件之竖向进度条
- 谷歌浏览器驱动官网下载
- 精选Java必看200道面试题
热门文章
- Faiss优化:针对OMP_NUM_THREADS环境变量设置的测试验证
- 3.1.1_Spring如何加载和解析@Configuration标签
- Java学习lesson 02
- 条款20:为指针的关联容器指定比较类型
- C# ref与out关键字解析
- SharePoint自动化系列——Solution auto-redeploy using Selenium(C#)
- HihoCoder#1051:补提交卡
- Windows sever 2008 动态硬盘数据恢复
- 体验一下Oracle 11g物理Active Data Guard实时查询(Real-time query)
- android data分区(标准)