继上次CRM项目之后 我们发现了django自带admin的强大之处以及灵活性,但是admin在企业中也一样很难做到完全的对接,因此编写自己的后台管理就显得至关重要。

本次自定义admin项目将接着上次crm项目来写 :

Django CRM客户关系管理系统

创建Easy_admim 后台管理应用

  • 创建app Python manage.py startapp easy_admin
  • 编辑setting文件 添加app /static文件
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'crm.apps.CrmConfig',
'easy_admin'
]
#...........

STATICFILES_DIRS=(
os.path.join(BASE_DIR,'static'),
)
  • 前端准备

    • bootsharp插件
    • jquery插件
  •  项目结构:

      

我们选着bootsharp官网的控制台实例作为easyadmin后台模板http://v3.bootcss.com/examples/dashboard/

  • 编写路由系统
#easy_crm/urls.py
from django.conf.urls import url,include
from django.contrib import admin urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'easyadmin',include('easy_admin.urls'))
]
#easyadmin/urls.py
from django.conf.urls import url
from easy_admin import views urlpatterns = [
url(r'^$', views.index,name="table_index"),
url(r'^/(\w+)/(\w+)/$', views.display_table_objs,name="table_objs"),
url(r'/(\w+)/$',views.menus_url_jump,name="menus_url"),
]
  • 前端代码
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/static/css/admin_index.css">
<title>Dashboard Template for Bootstrap</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
{% block css %}
{% endblock %}
</head>
<body> <nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<a class="navbar-brand" href="#">Brand</a>
</div> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ request.user }}</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav> <div class="row">
<aside class="col-xs-2 sidebar">
<div class="list-group ">
{% for role in request.user.userprofile.roles.all %}
{% for meun in role.menus.all %}
<a class="list-group-item" href={% url 'menus_url' meun.name %} >{{ meun }}</a>
{% endfor %}
{% endfor %}
</div>
</aside>
<main class="col-xs-10 main">
{% block main %}
{% endblock %}
</main>
</div> <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
{% block js %}
{% endblock %}
</body>
</html>

base.html

 {% extends 'easyadmin/base.html' %}
{% block main %}
<h1>首页</h1>
{% for app_name,app_tables in table_list.items %}
<div class="panel panel-info">
<div class="panel-heading"><h1 class="panel-title">{{ app_name }}</h1></div>
<div class="panel-body">
<table class="table table-hover">
<thead>
<tr><th>TB_NAME</th></tr>
</thead>
<tbody>
{% for table_name,admin in app_tables.items %}
<tr><td><a href={% url "table_objs" app_name table_name %}>{{ table_name }}</a></td><td>ADD<td>DEL</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div> {% endfor %} {% endblock %}

index.html

  • 我们在index中继承base.html
{% extends 'easyadmin/base.html' %}
  
  • views函数
def index(requests):
return render(requests,'easyadmin/index.html')

来吧 开始写功能了

  

目前完成主要的功能

  • 顶部菜单

    首先这里要说一下在模板渲染时 后端返回了一个request 而这个request对象依旧可以在模板中使用

    type(requests)

    

<class 'django.core.handlers.wsgi.WSGIRequest'>

  

    dir(requests)

   ['COOKIES', 'FILES', 'GET', 'META', 'POST', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_encoding', '_get_post', '_get_raw_host', '_get_scheme', '_initialize_handlers', '_load_post_and_files', '_mark_post_parse_error', '_messages', '_post_parse_error', '_read_started', '_set_post', '_stream', '_upload_handlers', 'body', 'build_absolute_uri', 'close', 'content_params', 'content_type', 'csrf_processing_done', 'encoding', 'environ', 'get_full_path', 'get_host', 'get_port', 'get_raw_uri', 'get_signed_cookie', 'is_ajax', 'is_secure', 'method', 'parse_file_upload', 'path', 'path_info', 'read', 'readline', 'readlines', 'resolver_match', 'scheme', 'session', 'upload_handlers', 'user', 'xreadlines']

  requests.user也就返回了我们当前所登陆的用户

  • 左侧菜单

      左侧菜单实现的原理 首先根据当前登录的用户获取当前登录用户属于的角色 再根据角色获取用户所有的菜单 然后遍历出来

      

#views.py
def index(requests):
return render(requests,'easyadmin/index.html')
def menus_url_jump(requests,menu_url):
return HttpResponse(menu_url)

  

  • 主体内容

     主体内容是admin的关键点有以下几个问题

    1.    如何只显示register注册的表?
    2.    如何自定义显示的字段?
    3.    在前端如何渲染?

      我们可以定义一个字典,通过register注册的表加入到这个字典,再把这个字典传递到前端遍历{tablename:tab_obj}

      那么我们又发现了一个问题,我们要如何把表归类到对应的app下呢? 很明显在前端做这个是比较麻烦的 因此我们队字典进行修改

{app_name:{tab_name:tab_obj}}

{app名字:{表一:表一对象,表二:表二对象}}

表写register函数

admin.site.register(models.CourseRecord) -->register(models.CourseRecprd)

通过表反射出app_name:

不知道是否对上次CRM中的meta方法是否有印象,在meta方法中封装了一些关于表的信息 verbose_name_plural。。等等 我们来看一下meta的防范有哪些

['FORWARD_PROPERTIES',
'REVERSE_PROPERTIES',
'__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'_expire_cache',
'_forward_fields_map',
'_get_fields',
'_get_fields_cache',
'_ordering_clash',
'_populate_directed_relation_graph',
'_prepare',
'_property_names',
'_relation_tree',
'abstract',
'add_field',
'add_manager',
'app_config',
'app_label',
'apps',
'auto_created',
'auto_field',
'base_manager',
'base_manager_name',
'can_migrate',
'concrete_fields',
'concrete_model',
'contribute_to_class',
'db_table',
'db_tablespace',
'default_apps',
'default_manager',
'default_manager_name',
'default_permissions',
'default_related_name',
'fields',
'fields_map',
'get_ancestor_link',
'get_base_chain',
'get_field',
'get_fields',
'get_latest_by',
'get_parent_list',
'get_path_from_parent',
'get_path_to_parent',
'has_auto_field',
'index_together',
'indexes',
'installed',
'label',
'label_lower',
'local_concrete_fields',
'local_fields',
'local_managers',
'local_many_to_many',
'managed',
'manager_inheritance_from_future',
'managers',
'managers_map',
'many_to_many',
'model',
'model_name',
'object_name',
'order_with_respect_to',
'ordering',
'original_attrs',
'parents',
'permissions',
'pk',
'private_fields',
'proxy',
'proxy_for_model',
'related_fkey_lookups',
'related_objects',
'required_db_features',
'required_db_vendor',
'select_on_save',
'setup_pk',
'setup_proxy',
'swappable',
'swapped',
'unique_together',
'verbose_name',
'verbose_name_plural',
'verbose_name_raw',
'virtual_fields']
app_name=models.tb._meta..app_label
创建自己的admin文件 easy_admin.py
from crm import models

enabled_admins = {}

class BaseAdmin(object):
list_display = []
list_filters = []
list_per_page = 20 class CustomerAdmin(BaseAdmin):
list_display = ['qq','name','source','consultant','consult_course','date','status']
list_filters = ['source','consultant','consult_course','status']
#model = models.Customer class CustomerFollowUpAdmin(BaseAdmin):
list_display = ('customer','consultant','date') def register(model_class,admin_class=None):
if model_class._meta.app_label not in enabled_admins:
enabled_admins[model_class._meta.app_label] = {} #enabled_admins['crm'] = {}
#admin_obj = admin_class()
admin_class.model = model_class #绑定model 对象和admin 类
enabled_admins[model_class._meta.app_label][model_class._meta.model_name] = admin_class
#enabled_admins['crm']['customerfollowup'] = CustomerFollowUpAdmin register(models.Customer,CustomerAdmin)
register(models.CustomerFollowUp,CustomerFollowUpAdmin)

表的详细信息

通过admin_class 反射出app,table_name
我们首先在模板中是不允许带"_"的查询,因此不能直接查询的到_meta,我们知道django内置了许多标签和过滤器,同时也支持自定义标签 只需要在app下创建templatetags目录 然后创建tags.py 同时在模板中加载标签{% load tags %} 那么我们写一个通过admin_class 返回table_name的标签吧

from django import template
register = template.Library() @register.simple_tag
def render_app_name(admin_class):
return admin_class.model._meta.verbose_name
 def display_table_objs(requests,app_name,table_name):
admin_class = easy_admin.enabled_admins[app_name][table_name]
return render(requests,'easyadmin/display_table.html',{"admin_class":admin_class})

OK,表头就写好了,接下来就是难点,记得我们需求里面提到过,我们的内容中显示的是我们需要显示的字段,而不是所有的字段 也就是说只显示list_display中的内容,并且是可以自定义的,那么我们如何获取list_display中的内容并且返回一个queryset呢?

一个方法是直接在views中处理返回一个是定义一个tag,如果我们需要重复的使用这个方法,那么定义一个tag可以节省代码量!

@register.simple_tag
def get_query_sets(admin_class):
return admin_class.model.objects.all() @register.simple_tag
def build_table_row(obj,admin_class):
row_ele = ""
for column in admin_class.list_display:
field_obj = obj._meta.get_field(column)
if field_obj.choices:#choices type
column_data = getattr(obj,"get_%s_display" % column)()
else:
column_data = getattr(obj,column) if type(column_data).__name__ == 'datetime':
column_data = column_data.strftime("%Y-%m-%d %H:%M:%S") row_ele += "<td>%s</td>" % column_data return mark_safe(row_ele)
<table class="table">
<thead>
<tr>
{% for obj in admin_class.list_display %}
<th>{{ obj }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% get_query_sets admin_class as query_sets %}
{% for obj in query_sets %}
<tr>
{% build_table_row obj admin_class %}
</tr>
{% endfor %}
</tbody>

过滤

  过滤函数

def table_filter(request,admin_class):
'''进行条件过滤并返回过滤后的数据'''
filter_conditions = {}
for k,v in request.GET.items():
if v:
filter_conditions[k] =v return admin_class.model.objects.filter(**filter_conditions),filter_conditions

views函数

def display_table_objs(request,app_name,table_name):
admin_class = easy_admin.enabled_admins[app_name][table_name]
object_list,filter_condtions = table_filter(request, admin_class)
return render(request,'easyadmin/display_table.html',{"admin_class":admin_class,"filter_condtions":filter_condtions,'object_list':object_list})
{% load tags %}
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/static/css/admin_index.css">
<title>Dashboard Template for Bootstrap</title>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
{% block css %}
{% endblock %}
</head>
<body> <nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<a class="navbar-brand" href="#">Brand</a>
</div> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ request.user }}</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav> <div class="row">
<main class="container">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">{% render_app_name admin_class %}</h3>
</div>
<div class="panel-body">
{% if admin_class.list_filters %}
<form class="" method="get">
{% for condtion in admin_class.list_filters %}
<div class="col-lg-2">
<span>{{ condtion }}</span>
{% render_filter_ele condtion admin_class filter_condtions %}
</div>
{% endfor %}
<button type="SUBMIT" class="btn btn-success">检索</button>
</form>
</div>
{% endif %}
<table class="table">
<thead>
<tr>
{% for obj in admin_class.list_display %}
<th>{{ obj }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for obj in object_list %}
<tr>
{% build_table_row obj admin_class %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</main>
</div> <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
{% block js %}
{% endblock %}
</body>
</html>

效果:

在html中 我们丢弃了get_query_sets 因为在后端帮我们返回了queryset对象

分页

分页功能实现http://www.cnblogs.com/tongchengbin/p/7697869.html

排序

排序涉及两个部分 一个是对从数据库中返回的object_list 另一个是前端标签的href

因为在考虑排序的同时还要考虑分页,那么先排序还是先分页呢?如果先分页,那么最后显示的数据第一页的数据依旧是最前面的数据 只是当前页的数据顺序变化,如果要达到整个数据的排序就要先排序在分页。如果没有排序我们就正常显示。另外出现的一个问题就是,默认按照id或者数据表的主键排序,而我们自定义排序的中也许没有主键或者id字段,当我们选择排序的时候最后无法回到初始的排序问题,因此可以增加一个排序还原连接,去掉排序关键字,在这里就不详细说明了。

排序函数

def easy_order(request,obj_list,admin_class):
'''从request中获取排序规则 从obj中获取数据,admin_class中对验证排序有效性
返回排序后的数据 和排序规则 排序规则用于前端生成href
'''
order_key = request.GET.get('o')
if not order_key or order_key.strip('-') not in admin_class.list_display :
return obj_list,None
else:
rule=0 if order_key[0]=='-' else 1
order_rule=[order_key.strip('-'),rule]
return obj_list.order_by(order_key),order_rule

  

views函数

def display_table_objs(request,app_name,table_name):
admin_class = easy_admin.enabled_admins[app_name][table_name]
object_list,filter_condtions = table_filter(request, admin_class)
#排序 order_rule 排序规则 [排序名字,排序顺序]
object_list,order_rule=easy_order(request,object_list,admin_class)
paginator = Paginator(object_list,admin_class.list_per_page) # Show 25 contacts per page
page = request.GET.get('page')
try:
object_list=paginator.page(page)
except PageNotAnInteger:
object_list=paginator.page(1)
except EmptyPage:
object_list=paginator.page(paginator.num_pages) return render(request,'easyadmin/display_table.html',{"admin_class":admin_class,"filter_condtions":filter_condtions,'object_list':object_list,'order_rule':order_rule})

  自定义标签

@register.simple_tag()
def render_order_url(filter_condtions,order_rule,obj):
#生产带过滤的排序url 避免排序后过滤功能失效
base_url="?"
for k,v in filter_condtions.items():
base_url+="%s=%s&" %(k,v)
if not order_rule or obj!=order_rule[]:
href=base_url+"o=%-s" % obj
return href
else:
if order_rule[]==:
href=base_url+"o=%s" % obj
else:
href=base_url+"o=-%s" % obj
return href

前端代码:

{% for obj in admin_class.list_display %}
<td><a href={% render_order_url filter_condtions order_rule obj %}><span>{{ obj }}</span></a></td>
{% endfor %}

 

搜索功能

搜索功能主要是对object_list中查找,在写搜索前需要在Easy_admin 的baseAdmin基类中添加搜索字段 并且 如果需要使用搜索功能需要覆盖基类中的空列表list_search 讲需要搜索的字段添加进去

注意注意注意:如果包含外键不能直接写字段需要注明外键字段 双下划线连接 search_fileds = ('book__name', 'book__title', 'book__price', 'category')

def easy_search(request,obj_list,admin_class):
'''从rquest中获取搜索关键字吗,admin_class中遍历需要搜索的字段'''
search_key=request.GET.get('_q')
con=Q()
#定义搜索方式 ’或‘搜索
con.connector='OR'
for column in admin_class.list_search:
#contains 模糊搜索
con.children.append(("%s__contains"%column,search_key))
return obj_list.filter(con)

位了不影响过滤和搜索同时使用,我们将搜索输入框和过滤选择框放在同一个表单下,同时为了让用户知道自己搜索的内容,我们将search_key 返回给用户并显示出来。

<form class="" method="get">
<div class="row">
{% for condtion in admin_class.list_filters %}
<div class="col-lg-2">
{% render_filter_ele condtion admin_class filter_condtions %}
</div>
{% endfor %}
<button type="SUBMIT" class="btn btn-success">检索</button>
</div>
<hr>
<div class="row">
<a></a>
<div class="col-lg-4"><input type="search" value={{ search_text }} name="_q" class="form-control"></div>
<button type="SUBMIT" class="btn btn-success">搜索</button>
</div>
</form>

跳转详情页

django 自带跳转详情页是通过list_display中第一个字段进行跳转的 跳转urll  url(r'^(\w+)/(\w+)/(\w+)/change/$',views.table_obj_change,name="menus_url"), 因为默认有id字段因此 我们把id作为(primary key 当然有的字段我们自定义了主键,那么我们就再cbaseadmin中定义一个属性吧 就叫pk_field 那么我们如何知道那个字段是主键了?百度好久没发现有案例,又想起了_meta,毕竟model中的信息都在里面,果然发现了有个pk属性,shell调试发现_meta.pk.name 返回的就是主键。开始写代码

def build_table_row(request,obj,admin_class):
pk_field=admin_class.model._meta.pk.name
obj_pk=getattr(obj,pk_field)
row_ele = ""
for index,column in enumerate(admin_class.list_display):
field_obj = obj._meta.get_field(column)
if field_obj.choices:#choices type
column_data = getattr(obj,"get_%s_display" % column)()
else:
column_data = getattr(obj,column)
if type(column_data).__name__ == 'datetime':
column_data = column_data.strftime("%Y-%m-%d %H:%M:%S")
if index==:
column_data = "<a href='{request_path}/{obj_pk}/change/'>{data}</a>".format(request_path=request.path,
obj_pk=obj_pk,
data=column_data)
row_ele += "<td>%s</td>" % column_data
return mark_safe(row_ele)

#主要该表就是在第一列加上了href 同时获取到主键字段,那么对应的后端取数据也应该是根据主键来取,而不是通过id字段,这样可以兼容后期所有的数据表结构。

编辑详情页

详细页的生成是自定义admin的重点,因为我们不可能针对每个表写一个前端的页面,那样也就不是自定义admin了,联想到django有没有自动生成html代码的模块?没错,就是modelfrom。那么问题又来了,虽然我们不需要一个一个写前端页面,但是我们还是需要一个一写modelfrom类,其实Python创建类的方式有很多,其中有一个自动创建类的方法type方法。关于类以及type方法可以参考:Python面向对象编程

froms.py

from django.forms import forms,ModelForm

def create_model_form(request,admin_class):
# def __new__(cls, *args, **kwargs):
# # super(CustomerForm, self).__new__(*args, **kwargs)
# print("base fields", cls.base_fields)
# for field_name, field_obj in cls.base_fields.items():
# print(field_name, dir(field_obj))
# field_obj.widget.attrs['class'] = 'form-control'
# # field_obj.widget.attrs['maxlength'] = getattr(field_obj,'max_length' ) if hasattr(field_obj,'max_length') \
# # else ""
# return ModelForm.__new__(cls)
class Meta:
model=admin_class.model
fields='__all__'
attrs={'Meta':Meta}
_model_form_class=type("DynamicModelForm",(ModelForm,),attrs)
# setattr(_model_form_class, '__new__', __new__)
return _model_form_class

views函数

instance的作用是填充前端的内容。

def table_obj_change(request,app_name,table_name,obj_pk):
'''修改内容'''
admin_class=easy_admin.enabled_admins[app_name][table_name]
model_form_class = create_model_form(request, admin_class)
#并不是所有的表都有id instance 填充数据
#pk_field=admin_class.model._meta.pk.name
obj=admin_class.model.objects.get(pk=obj_pk)
if request.method == "POST":
form_obj = model_form_class(request.POST, instance=obj) # 更新
if form_obj.is_valid():
form_obj.save()
else:
form_obj=model_form_class(instance=obj)
#object of type 'DynamicModelForm' has no len() 可以遍历但是没有len() 方法
return render(request,'easyadmin/table_obj_change.html',{"form_obj":form_obj})

html部分

    <div class="container">
<form class="form-horizontal" role="form" method="post">{% csrf_token %}
{% for field in form_obj %}
<div class="form-group"> {% if field.field.required %}
<label class="col-sm-2 control-label" style="font-weight: normal"><strong>{{ field.label }}</strong></label>
{% else %}
<label class="col-sm-2 control-label" style="font-weight: normal">{{ field.label }} </label>
{% endif %}
{{ field }}
</div>
{% endfor %}
<div class="form-group">
<div class="col-sm-10 ">
<button type="submit" class="btn btn-success pull-right">Save</button>
</div>
</div>
</form>
</div>

效果:

创建元素的话大同小异直接上代码吧 url 的话不用获取对象id,前端html都是后台生成的,样式可以自己修改。具体的可以看项目代码。

def table_obj_add(request,app_name,table_name):
'''添加内容'''
admin_class = easy_admin.enabled_admins[app_name][table_name]
model_form_class = create_model_form(request, admin_class)
# 并不是所有的表都有id instance 填充数据
if request.method == "POST":
form_obj = model_form_class(request.POST)#添加数据
if form_obj.is_valid():
form_obj.save()
else:
form_obj = model_form_class()
# object of type 'DynamicModelForm' has no len() 可以遍历但是没有len() 方法
return render(request, 'easyadmin/table_obj_change.html', {"form_obj": form_obj})

删除页

删除页和修改页url类似

url(r'^(\w+)/(\w+)/(\d+)/delete/$', views.table_obj_delete,name="obj_delete"),之所以把删除也作为一个功能是因为django的删除页实在是太厉害了,在删除一个对象时,会列出关于这个对象的所有信息,并且不是简单的遍历下去,根据字段的类型,返回信息的数量也是不一样的,相比之前的代码也最为复杂。
def table_obj_delete(request,app_name,table_name,pk_obj):
#删除对象
admin_class = easy_admin.enabled_admins[app_name][table_name]
obj = admin_class.model.objects.get(pk=pk_obj)
if request.method == "POST":
obj.delete()
return redirect(reverse('table_objs',args=(app_name,table_name)))
return render(request, "easyadmin/table_obj_delete.html", {"obj": obj,
"admin_class": admin_class,
"app_name": app_name,
"table_name": table_name
})

前端

{%  block main %}
{% display_obj_related obj %}
<form method="post">{% csrf_token %}
<input type="submit" class="btn btn-danger" value="Yes,I'm sure">
<input type="hidden" value="yes" name="delete_confirm">
<input type="hidden" value=" name="selected_ids">
<input type="hidden" value="" name="action">
<a class="btn btn-info" href="{% url 'table_objs' app_name table_name %}">No,Take me back</a>
</form>
{% endblock %}

tags部分

def recursive_related_objs_lookup(objs):
model_name = objs[]._meta.model_name
ul_ele = "<ul>"
for obj in objs:
li_ele = '''<li> %s: %s </li>'''%(obj._meta.verbose_name,obj.__str__().strip("<>"))
ul_ele += li_ele
for m2m_field in obj._meta.local_many_to_many: #把所有跟这个对象直接关联的m2m字段取出来了
sub_ul_ele = "<ul>"
m2m_field_obj = getattr(obj,m2m_field.name) #getattr(customer, 'tags')
for content in m2m_field_obj.select_related():# customer.tags.all()
li_ele = '''<li> %s: %s </li>''' % (m2m_field.verbose_name, content.__str__().strip("<>"))
sub_ul_ele +=li_ele sub_ul_ele += "</ul>"
ul_ele += sub_ul_ele #最终跟最外层的ul相拼接 for related_obj in obj._meta.related_objects:#获取与customer表有关联的所有表 ForeignKey('customer')
if 'ManyToManyRel' in related_obj.__repr__():#判断类型
if hasattr(obj, related_obj.get_accessor_name()): # hassattr(customer,'enrollment_set')
accessor_obj = getattr(obj, related_obj.get_accessor_name())
print("-------ManyToManyRel",accessor_obj,related_obj.get_accessor_name())
# 上面accessor_obj 相当于 customer.enrollment_set
if hasattr(accessor_obj, 'select_related'): # slect_related() == all()
target_objs = accessor_obj.select_related() # .filter(**filter_coditions)
# target_objs 相当于 customer.enrollment_set.all() sub_ul_ele ="<ul style='color:red'>"
print(target_objs,'ta')
for o in target_objs:
li_ele = '''<li> %s: %s </li>''' % (o._meta.verbose_name, o.__str__().strip("<>"))
sub_ul_ele += li_ele
sub_ul_ele += "</ul>"
ul_ele += sub_ul_ele elif hasattr(obj,related_obj.get_accessor_name()): # hassattr(customer,'enrollment_set')
accessor_obj = getattr(obj,related_obj.get_accessor_name())
#上面accessor_obj 相当于 customer.enrollment_set
if hasattr(accessor_obj,'select_related'): # slect_related() == all()
target_objs = accessor_obj.select_related() #获取关于customer的queryset
# target_objs 相当于 customer.enrollment_set.all()
else:
print("one to one i guess:",accessor_obj)
target_objs = accessor_obj
# print(target_objs,'target')
if len(target_objs) >:
#print("\033[31;1mdeeper layer lookup -------\033[0m")
#nodes = recursive_related_objs_lookup(target_objs,model_name)
nodes = recursive_related_objs_lookup(target_objs)#除了many_to_many类型都需要递归下去
ul_ele += nodes
ul_ele +="</ul>"
return ul_ele
@register.simple_tag
def display_obj_related(objs):
'''把对象及所有相关联的数据取出来'''
objs = [objs,] #fake
if objs:
model_class = objs[]._meta.model
return mark_safe(recursive_related_objs_lookup(objs))

效果图

很多方法在代码中都有注释,这里做个概述:后端返回obj对象,再前端调用标签

display_obj_related 这个讲obj转为列表是为了以后可以批量处理。再通过
recursive_related_objs_lookup(objs)函数递归出所有关于obj的信息显示给用户 在递归的过程中对于one_to_one,Foreignkey,many_to_many字段做了不同的处理,因为one_to_one 和foreignkey字段下的数据都是关于obj的 但是many_to_many 如果无限递归下去会出现于obj无关的数据,例如学生和老师的关系,如果无限递归下去就可能会获取到obj对应老师下的其他学生。因此对于many_to_many字段我们只显示该字段的下一级,也就是obj那个学生对应老师的信息,而不去获取老师的学生信息。


Action( 批处理)

django action动作给我们定义了一个函数 当然函数可以接受一个对象,也可以接收一组对象,但是action中的函数主要用于批量操作,虽然action也会用到函数,但是这个函数用来被views中的函数来调用的,因此不需要新的url。

流程:

前端提交表单 包含action函数 对象的主键,因为是post提交 所以app_name,table_name在后端可以获取到,然后在后端分析post请求还是get请求,如果是get请求就是数据获取,如果是post请求

table_obj.html

{% extends "easyadmin/base.html" %}
{% load tags %}
<body>
{% block main %}
<div class="row">
<main class="container">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">
<a>{% render_table_name admin_class %}</a>
<a href="{{ request.path }}/add/" class=" pull-right">Add</a>
</h3>
</div>
<div class="panel-body">
{% if admin_class.list_filters %}
<form class="" method="get">
<div class="row">
{% for condtion in admin_class.list_filters %}
<div class="col-lg-2">
{% render_filter_ele condtion admin_class filter_condtions %}
</div>
{% endfor %}
<button type="SUBMIT" class="btn btn-success">检索</button>
</div>
<hr>
<div title="搜索范围:{% for colum in admin_class.list_search %}{{ colum }} {% endfor %}" class="row">
<div class="col-lg-3"><input type="search" value="{{ search_text }}" name="_q" class="form-control"/></div>
<button type="SUBMIT" class="btn btn-success">搜索</button>
</div>
</form>
<hr>
{% endif %}
<form class="" method="post">{% csrf_token %}
<div class="row">
<div class="col-lg-3">
<input class="hidden" name="action_pks" id="action_pks" value="">
<select id="action_list" name="action_def" class="form-control" name="action">
<option value="">action</option>
{% for action in admin_class.actions %}<option value={{ action }}>{{ action }}</option>{% endfor %}
</select>
</div>
<button class="btn btn-success" onclick="return ActionSubmit(this)">GO</button>
</div>
</form>
<table class="table">
<thead>
<tr><td><input type="checkbox" onclick="CheckAllToggle(this)"></td>
{% for obj in admin_class.list_display %}
<td><a href={% render_order_url filter_condtions order_rule obj %}><span>{{ obj }}</span></a></td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for obj in object_list %}
<tr>
<td><input tag="obj_checkbox" type="checkbox" value={{ obj.pk }}></td>{% build_table_row request obj admin_class %}
</tr>
{% endfor %}
</tbody>
</table>
<div>共计{{ object_list.paginator.count }}记录</div>
<nav class="page pagination">
<ul class="pagination">
{% easypaginator request object_list %}
</ul>
</nav>
</div>
</div>
</main>
</div>
{% endblock %}
{% block js %}
<script>
function CheckAllToggle(ele){
if ( $(ele).prop("checked")){
$("input[tag='obj_checkbox']").prop("checked",true);
}else {
$("input[tag='obj_checkbox']").prop("checked",false);
}
}
function ActionSubmit(form_ele) {
var selected_pks = [];
var selected_action = $("#action_list").val();
$("input[tag='obj_checkbox']:checked").each(function () {
selected_pks.push($(this).val());
});
if (selected_pks.length == ){
alert("No object got selected!");
return false
}
if (selected_action.length== ){
alert("No action got selected!");
return false
}
$("#action_pks").val(selected_pks.toString());
return true
} </script>
{% endblock %}

easy_admin.py

from crm import models
from django.shortcuts import render,HttpResponse,redirect
enabled_admins = {} class BaseAdmin(object):
list_display = []
list_filters = []
list_per_page =
pk_field='id'
#list搜索列表 如果有外键 tab__name 需要用__连接
list_search=[]
actions = ["delete_selected_objs", ]
def delete_selected_objs(self, request, querysets):
app_name = self.model._meta.app_label
table_name = self.model._meta.model_name
print("--->delete_selected_objs", self, request, querysets)
if request.POST.get("delete_confirm") == "yes":
querysets.delete()
return redirect("/easyadmin/%s/%s" % (app_name, table_name))
action_pks = ','.join([str(i.pk) for i in querysets])
return render(request, "easyadmin/table_obj_delete.html", {"objs": querysets,
"admin_class": self,
"app_name": app_name,
"table_name": table_name,
"action_pks": action_pks,
"action_def": request._admin_action
}) class CustomerAdmin(BaseAdmin):
list_display = ['qq','name','source','consultant','consult_course','date','status']
list_filters = ['source','consultant','consult_course','status','date']
list_search = ['qq','name','consultant__name']
#model = models.Customer
list_per_page =
class CustomerFollowUpAdmin(BaseAdmin):
list_display = ('customer','consultant','date') def register(model_class,admin_class=BaseAdmin):
if model_class._meta.app_label not in enabled_admins:
enabled_admins[model_class._meta.app_label] = {} #enabled_admins['crm'] = {}
#admin_obj = admin_class()
admin_class.model = model_class #绑定model 对象和admin 类
if not admin_class.list_display:
admin_class.list_display=[ model_class._meta.fields[].verbose_name]
enabled_admins[model_class._meta.app_label][model_class._meta.model_name] = admin_class
#enabled_admins['crm']['customerfollowup'] = CustomerFollowUpAdmin register(models.Customer,CustomerAdmin)
register(models.CustomerFollowUp,CustomerFollowUpAdmin)
register(models.Tag)
{% extends "easyadmin/base.html" %}
{% load tags %}
{% block main %}
{% display_obj_related objs %}
<form method="post">{% csrf_token %}
<input type="submit" class="btn btn-danger" value="Yes,I'm sure">
<input type="hidden" value="yes" name="delete_confirm">
<input type="hidden" value="{{ action_pks }}" name="action_pks">
<input type="hidden" value="{{ action_def }}" name="action_def">
<a class="btn btn-info" href="{% url 'table_objs' app_name table_name %}">No,Take me back</a>
</form>
{% endblock %}

views函数修改

def display_table_objs(request,app_name,table_name):
admin_class = easy_admin.enabled_admins[app_name][table_name]
if request.method=="POST":
action=request.POST.get('action_def')
pk_list=request.POST.get('action_pks').split(',')
#生产上后台需要验证数据合法性
selected_objs = admin_class.model.objects.filter(pk__in=pk_list)
action_func = getattr(admin_class, action)
request._admin_action = action
return action_func(admin_class, request, selected_objs)
object_list,filter_condtions = table_filter(request, admin_class)
#搜索
object_list=easy_search(request,object_list,admin_class)
#排序 order_rule 排序规则 [排序名字,排序顺序]
object_list,order_rule=easy_order(request,object_list,admin_class) paginator = Paginator(object_list,admin_class.list_per_page) # Show contacts per page
page = request.GET.get('page')
try:
object_list=paginator.page(page)
except PageNotAnInteger:
object_list=paginator.page()
except EmptyPage:
object_list=paginator.page(paginator.num_pages) return render(request,'easyadmin/display_table.html',{"admin_class":admin_class,
"filter_condtions":filter_condtions,
'object_list':object_list,
'order_rule':order_rule,
'search_text':request.GET.get('_q','')})
def table_obj_delete(request,app_name,table_name,pk_obj):
#删除对象
admin_class = easy_admin.enabled_admins[app_name][table_name]
objs = admin_class.model.objects.get(pk=pk_obj)
if request.method == "POST":
objs.delete()
return redirect(reverse('table_objs',args=(app_name,table_name))) return render(request, "easyadmin/table_obj_delete.html", {"objs": objs,
"admin_class": admin_class,
"app_name": app_name,
"table_name": table_name
})

tags修改部分 判断是产出单个对象还是多个对象,后期优化,在vies中返回列表,避免判断。

@register.simple_tag
def display_obj_related(objs):
'''把对象及所有相关联的数据取出来'''
if not hasattr(objs,'all'):
objs = [objs,] #fake
print('单个对象')
if objs:
model_class = objs[]._meta.model
return mark_safe(recursive_related_objs_lookup(objs))

给前端页面加上class

首先form的前端代码时form生成的,如果需要对生成的代码修改就就只需要修改form方法。而forms是由我们动态生成的。这里就需要用到new方法,见另一篇文章。

我们的场景是定制一个readloly页面,这个页面不可以修改。那么久需要在easy_admin的baseadmin中定制。默认readonly_table=false,我们测试将Cusromer表设置为readonly

def create_model_form(request,admin_class):
def __new__(cls, *args, **kwargs):
# print("base fields", cls.base_fields)
for field_name, field_obj in cls.base_fields.items():
if admin_class.readonly_table:
field_obj.widget.attrs['disabled'] = 'disabled' #这里可给标签添加样式
return ModelForm.__new__(cls)class Meta:
model=admin_class.model
fields='__all__'
attrs={'Meta':Meta}
_model_form_class=type("DynamicModelForm",(ModelForm,),attrs)
setattr(_model_form_class, '__new__',__new__)
setattr(_model_form_class, 'clean', default_clean) return _model_form_class

对前端页面做一些修改 如果是readonly页面就不显示操作 同时需要在前端传入admin_class

{%  if not admin_class.readonly_table %}
<div class="form-group">
<div class="col-sm-10 ">
{% if obj_pk %}
<a class="bottom btn btn-danger" href={% url "obj_delete" app_name table_name obj_pk %}>删除</a>
{% endif %}
<button type="submit" class="btn btn-success pull-right">Save</button>
</div>
</div>
{% endif %}

为了防止恶意数据修改,在后端也需要验证,但是针对add,change页面分别做修改过于繁琐,但是add,delete,change页面都需要调用create_forms_table 所以我们在form中判断表单是否可以修改。这里会用到django form clean验证 参考文章

def create_model_form(request,admin_class):
def __new__(cls, *args, **kwargs):
# print("base fields", cls.base_fields)
for field_name, field_obj in cls.base_fields.items():
if admin_class.readonly_table:
field_obj.widget.attrs['disabled'] = 'disabled' #这里可给标签添加样式
return ModelForm.__new__(cls)
def default_clean(self):
'''给所有的form默认加一个clean验证 post才来'''
error_list = []
# readonly_table check raise errors
if admin_class.readonly_table:#如果是readonly table 触发一个错误,后面的obj操作都不会执行
raise ValidationError(
_('Table is readonly,cannot be modified or added'),
code='invalid'
)
# invoke user's cutomized form validation
self.ValidationError = ValidationError
response = admin_class.default_form_validation(self)
if response:
error_list.append(response)
if error_list:
raise ValidationError(error_list)
class Meta:
model=admin_class.model
fields='__all__'
attrs={'Meta':Meta}
_model_form_class=type("DynamicModelForm",(ModelForm,),attrs)
setattr(_model_form_class, '__new__',__new__)
setattr(_model_form_class, 'clean', default_clean) return _model_form_class

同时也需要在easy_admin中定义函数,default_form_validation返回的是对整个table的描述。


class BaseAdmin(object):
list_display = []
list_filters = []
list_per_page =
pk_field='id'
#list搜索列表 如果有外键 tab__name 需要用__连接
list_search=[]
actions = ["delete_selected_objs", ]
readonly_fields=[]
readonly_table = False
def delete_selected_objs(self, request, querysets):
app_name = self.model._meta.app_label
table_name = self.model._meta.model_name
print("--->delete_selected_objs", self, request, querysets)
if self.readonly_table:
errors = {"readonly_table": "This table is readonly ,cannot be deleted or modified!"}
else:
errors = {}
if request.POST.get("delete_confirm") == "yes":
querysets.delete()
return redirect("/easyadmin/%s/%s" % (app_name, table_name))
action_pks = ','.join([str(i.pk) for i in querysets])
return render(request, "easyadmin/table_obj_delete.html", {"objs": querysets,
"admin_class": self,
"app_name": app_name,
"table_name": table_name,
"action_pks": action_pks,
"action_def": request._admin_action,
"errors": errors
}) def default_form_validation(self):
'''用户可以在此进行自定义的表单验证,相当于django form的clean方法'''
pass

单一字段验证

对create_form_table中new方法的修改

    def __new__(cls, *args, **kwargs):
# print("base fields", cls.base_fields)
for field_name, field_obj in cls.base_fields.items():
if admin_class.readonly_table:
field_obj.widget.attrs['disabled'] = 'disabled' #这里可给标签添加样式
if hasattr(admin_class, "clean_%s" % field_name):
field_clean_func = getattr(admin_class, "clean_%s" % field_name)
setattr(cls, "clean_%s" % field_name, field_clean_func)
return ModelForm.__new__(cls)

字段验证方法: 我们给CustomerAdmin中name字段添加单一验证,如果有error那么在前端可以通过name.errors方法获取错误,注意这里的函数名也是有格式要求的。

    def clean_name(self):
#规则 clean_field
if not self.cleaned_data["name"]:
self.add_error('name', "cannot be null")

  前端代码:

 {% for field in   form_obj %}
<div class="form-group"> {% if field.field.required %}
<label class="col-sm-2 control-label" style="font-weight: normal"><strong>{{ field.label }}</strong></label>
{% else %}
<label class="col-sm-2 control-label" style="font-weight: normal">{{ field.label }} </label>
{% endif %}
{{ field }}
<span style="color: red">{{ field.errors }}</span>
</div>
{% endfor %}

效果:

Django 认证系统

因为之前的用户表是继承自userprofile表的,在这里我们也可以写自己的userprofile,因此扩折user需要重写userprofilr 关于扩展user的文档。在这里不进需要些userprofile表,同时要指定django默认的验证方式。在setting中加入

AUTH_USER_MODEL = 'crm.UserProfile' 而在我们创建用户时需要调用创建用户的函数,因此需要编写class UserProfileManager(BaseUserManager)类,类中定义了创建普通用户与超级用户的方法,以及密码的加密,然后再userprofile中通过objects指定创建用户的方法。而create_user接受的方法表示用户验证的方式 username对应email,password即为密码,同时在userprofile中 email制度按需要unique,
USERNAME_FIELD = 'email' 指定用户名。
class UserProfileManager(BaseUserManager):
def create_user(self, email, name, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError('Users must have an email address') user = self.model(
email=self.normalize_email(email),
name=name,
) user.set_password(password)
self.is_active = True
user.save(using=self._db)
return user def create_superuser(self,email, name, password):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
name=name,
)
user.is_active = True
user.is_admin = True
user.save(using=self._db)
return user class UserProfile(AbstractBaseUser,PermissionsMixin):
'''账号表'''
email = models.EmailField(
verbose_name='email address',
max_length=,
unique=True,
null=True
)
password = models.CharField(_('password'), max_length=,
help_text=mark_safe('''<a href='password/'>修改密码</a>'''))
name = models.CharField(max_length=)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
roles = models.ManyToManyField("Role", blank=True)
objects = UserProfileManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name'] def get_full_name(self):
# The user is identified by their email address
return self.email def get_short_name(self):
# The user is identified by their email address
return self.email def __str__(self): # __unicode__ on Python
return self.email def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
@property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
# class UserProfile(models.Model):
# '''账号表'''
# user = models.OneToOneField(User)
# name = models.CharField(max_length=)
# roles = models.ManyToManyField("Role",blank=True,null=True)
def __str__(self):
return self.name

编写完后需要重新生成数据表。

登陆注销:

官方文档

url部分

url(r'account/login',views.acc_login,name="acc_login"),
url(r'account.logout',views.acc_logout,name="acc_logout")

html

{% extends 'easyadmin/base.html' %}
{% block main %}
<div class="container col-lg-offset-4 ">
<form class="form-signin col-lg-3 pu " method="post" role="form">{% csrf_token %}
<h3 class="form-signin-heading">EasyCRM</h3>
<input type="email" name="email" class="form-control" placeholder="Email address" required autofocus>
<input type="password" name="password" class="form-control" placeholder="Password" required>
{% if errors %}
<span style="color: red;">{{ errors.error }}</span>
{% endif %}
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
</form> </div> <!-- /container --> {% endblock %}

views:因为django已经封装好了登陆注销的模块 ,我们直接拿来用就可以。

def acc_login(request):
errors = {}
if request.method == "POST":
_email = request.POST.get("email")
_password = request.POST.get("password")
user = authenticate(username = _email, password = _password)
if user:
login(request,user)
next_url = request.GET.get("next","/")
return redirect(next_url)
else:
errors['error'] = "Wrong username or password!" return render(request,"login.html",{"errors":errors}) def acc_logout(request): logout(request) return redirect("/account/login/")

接下来我们可以为其他页面添加认证,同样django也提供了认证的装饰器@login_required,如果登录则返回正常页面,如果没有登录自动条状 accounts_login 当然我们也可以指定没有登录跳转的页面,在setting中LOGIN_URL = "/account/login/"  如果没有登录 那么在跳转页面的时候回附带一个next值,我们可以在登录页中获取改值,登录成功后再次跳转到来的地方。

django 自己编写admin的更多相关文章

  1. django自定制Admin

    如果只是在admin中简单的展示及管理模型,那么在admin.py模块中使用admin.site.register将模型注册一下就好了: from django.contrib import admi ...

  2. 第一次在Django上编写静态网页

    新建一个Python Django工程: Win+R进入cmd命令界面,并cd到指定工程目录下,比如我的工程目录是E:\wamp\Apache24\www\ 输入E: 跳转E盘 输入cd wamp\A ...

  3. 4.django笔记之admin

    作者:刘耀 QQ:22102107 django-Admin django amdin是django提供的一个后台管理页面,改管理页面提供完善的html和css,使得你在通过Model创建完数据库表之 ...

  4. Django: TemplateDoesNotExist at /admin/

    最近用virtualenv 总出现 Django: TemplateDoesNotExist at /admin/的问题,报错TemplateDoesNotExist at /admin/admin/ ...

  5. Django学习--9 Admin

    1.vim settings.py 打开  'django.contrib.admin' vim urls.py 打开 from django.contrib import admin     (注意 ...

  6. Django学习(九)---Templates过滤器及Django shell和Admin增强

    一.Templates过滤器 过滤器属于django模板语言 修改模板中的变量,从而显示不同内容 {{ value | filter }} 举例:{{ list_nums | length}}    ...

  7. Django 1.9 admin 使用suit 小记

    最近项目做到了后台管理的部分.Django虽然提供了后台管理,但是界面不咋好看.所以我使用了suit.官网http://djangosuit.com/ 步骤: 1,安装suit 项目settings. ...

  8. django进阶-modelform&admin action

    先看效果图: 登陆admin后的界面: 查看作者: 当然你也可以定制admin, 使界面更牛逼 数据库表结构: app01/models.py from django.db import models ...

  9. Django中的admin组件分析

    admin的使用介绍 django-admin的使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.可以在项目的 setting ...

随机推荐

  1. 教程:将应用迁移到 DirectX* 12 – 第 1 部分

    原文地址 简介 随着微软* 新版操作系统 Windows 10* 的发布,核心图形技术将升级到最新的 DirectX* 12.要帮助拥有 DirectX* 开发经验的程序员熟悉 DirectX* 12 ...

  2. 使用IntelRealScene设备结合Cocos引擎实现体感游戏开发

    英特尔开发人员专区原文地址 Cocos游戏开发引擎对于广大开发者来说都比较熟悉,Intel RealScene是什么呢,简单理解是一种特殊的摄像头,可以捕捉用户的手势,面部表情等,进而实现AR,VR的 ...

  3. Vue学习计划基础笔记(二) - 模板语法,计算属性,侦听器

    模板语法.计算属性和侦听器 目标: 1.熟练使用vue的模板语法 2.理解计算属性与侦听器的用法以及应用场景 1. 模板语法 <div id="app"> <!- ...

  4. JAVA基础学习之路(十二)链表

    定义链表的基本结构: class Link {//外部类 //内部类,只为链表类服务 private class Node {//定义节点类 private String data;//保存的数据 p ...

  5. 高可用Kubernetes集群-7. 部署kube-controller-manager

    九.部署kube-controller-manager kube-controller-manager是Kube-Master相关的3个服务之一,是有状态的服务,会修改集群的状态信息. 如果多个mas ...

  6. 当Kubernets遇上阿里云 -之七层负载均衡(一).

    我们知道Kubernetes的service只能实现基于4层的负载均衡,无法提供7层之上的许多特性,诸如基于URL的负载均衡,SSL支持,三方授权等等:Ingress可以实现七层负载均衡的许多功能,唯 ...

  7. moment.js学习总结

    一. 介绍: moment.js不依赖任何第三方库,支持字符串.Date.时间戳以及数组等格式,可以像PHP的date()函数一样,格式化日期时间,计算相对时间,获取特定时间后的日期时间等等.下面是一 ...

  8. Lucky Conversion(找规律)

    Description Petya loves lucky numbers very much. Everybody knows that lucky numbers are positive int ...

  9. 复利计算器4.0之再遇JUnit

    复利计算器4.0之再遇JUnit 前言    虽然之前的复利计算器版本已经尝试过使用JUnit单元测试,但由于没有系统性地学习过JUnit的使用,用得并不好,主要问题表现在测试的场景太少,并没有达到测 ...

  10. 进击的SDN

    SDN是什么? 不再是OSI七层模型,全新的SDN三层模型. 起源于斯坦福大学博士生领导的一个项目Ethane:通过一个集中式控制器(NOX),网络管理员可以定义基于网络流的控制策略,并将这个策略用于 ...