django 自己編寫admin


繼上次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
 1 {% extends 'easyadmin/base.html' %}
 2 {%  block main %}
 3     <h1>首頁</h1>
 4     {% for app_name,app_tables in table_list.items %}
 5         <div class="panel panel-info">
 6             <div class="panel-heading"><h1 class="panel-title">{{ app_name }}</h1></div>
 7              <div class="panel-body">
 8                 <table class="table table-hover">
 9                     <thead>
10                     <tr><th>TB_NAME</th></tr>
11                     </thead>
12                      <tbody>
13                         {% for table_name,admin  in app_tables.items %}
14                             <tr><td><a href={% url "table_objs" app_name table_name %}>{{ table_name }}</a></td><td>ADD<td>DEL</td></tr>
15                         {% endfor %}
16                     </tbody>
17                 </table>
18         </div>
19         </div>
20 
21     {% endfor %}
22 
23 {% 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']
View Code

 

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
1 def display_table_objs(requests,app_name,table_name):
2     admin_class = easy_admin.enabled_admins[app_name][table_name]
3     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[0]:
        href=base_url+"o=%-s" % obj
        return href
    else:
        if order_rule[1]==0:
            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==0:
            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[0]._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) >0:
                    #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[0]._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 == 0){
               alert("No object got selected!");
               return false
           }
           if (selected_action.length==0 ){
               alert("No action got selected!");
               return false
           }
          $("#action_pks").val(selected_pks.toString());
           return true
        }

    </script>
{% endblock %}
View Code

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 = 20
    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 = 10
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[1].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 %}
View Code

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 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,
                                                          '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[0]._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 = 20
    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=255,
            unique=True,
            null=True
        )
        password = models.CharField(_('password'), max_length=128,
                                    help_text=mark_safe('''<a href='password/'>修改密碼</a>'''))
        name = models.CharField(max_length=32)
        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 2
            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=32)
#     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值,我們可以在登錄頁中獲取改值,登錄成功后再次跳轉到來的地方。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM