Python Django 學習紀錄:架設個人部落格(七)
導航欄添加用戶操作
關於登錄登出還有許多不方便的操作尚未修正
也為了銜接之後的自定義用戶模型所以創建了用戶應用
並且將登錄登出的相關文件、方法遷移到用戶應用
而最後則是將上次撰寫的模態框登錄全局化
一.、功能設計
- 創建用戶應用,將登錄登出相關文件、方法移入其中
- 導航欄右側添加登錄、註冊與用戶訊息、登出
- 模態框登入全局化
二.、便捷登錄登出系統
1.創建用戶應用與遷移用戶相關文件、方法
(1)創建 user 應用
在虛擬環境下輸入以下指令:
python manage.py startapp user
(2)註冊 user 應用
在<settings.py>註冊user應用:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'ckeditor', 'ckeditor_uploader', 'blog', 'read_statistic', 'comment', 'likes', 'user', ]
記得進行數據庫遷移 (makemigrations)與應用 (migrate)
(3)遷移用戶相關文件、方法
將用戶相關的文件、方法移至 user 應用
a.遷移相關方法
原先在mysite專案<views.py>的以下方法移至 user 應用的 <views.py>:
login、login_for_modal、 register
b.遷移方法引用的套件
把這些方法會引用到的套件添加至user應用<views.py>
mysite專案的<views.py>可移除多餘的套件
c.遷移相關表單
將 mysite 專案的 <forms.py> 移至 user應用(此表單文件只與登入、註冊相關)
blog應用有引用<forms.py>文件,所以需要修改導入的路徑
由 mysite.forms 改為 user.forms
(3)遷移相關模板
在 user 應用創建 templates目錄
並在templates目錄中創建user文件夾
將登入、註冊模板<login.html>、<register.html>
遷移至user應用templates目錄的user文件夾中
而由於更動模板頁面的位置,在login、register方法中的路徑也要更改:
return render(request, "user/login.html", context) return render(request, "user/register.html", context)
(4)設置url路徑
將用戶相關方法遷移後,尚未設置路徑
在user應用創建<urls.py>文件
把原先在mysite的用戶相關方法路徑移至user應用的<urls.py>:
from django.urls import path from . import views urlpatterns = [ path('login/', views.login, name='login'), path('login_for_modal/', views.login_for_modal, name='login_for_modal'), path('register/', views.register, name='register'), ]
在mysite專案<urls.py>添加user應用路徑:
path('user/',include('user.urls')),
2.登出功能與個人資料頁面
(1)撰寫登出、個人資料方法
在user應用的<views.py>撰寫登出方法:
def logout(request): auth.logout(request) return redirect(request.GET.get('from',reverse('home'))) def user_info(request): context={} return render(request,'user/user_info.html',context)
(2)設置路徑
在user應用的<urls.py>添加登出路徑:
path('logout/', views.logout, name='logout'), path('user_info/', views.user_info, name='user_info'),
(3)個人資料頁面
由於用戶系統使用django內建的User.model
可以參考 Django2.0文件 auth套件的部分,來顯示用戶資料
並且使用javascript判斷是否登入,如果未登入就會跳轉到首頁:
{% extends "base.html" %} {% block title %} 個人資料 {% endblock %} {% block nav_blog_active %}active{% endblock %} {% block content %} <div class="container"> <div class="row"> <!-- 響應式 --> <div class="col-xs-10 col-xs-offset-1"> {% if user.is_authenticated %} <h2>{{ user.username }}</h2> <ul> <li>暱稱:<a href="#">修改暱稱</a></li> <li>信箱:{% if user.email %}{{ user.email }}{% else %}未綁定 <a href="#">綁定信箱</a>{% endif %}</li> <li>上一次登錄時間:{{ user.last_login|date:"Y-m-d H:i:s" }}</li> <li><a href="#">修改密碼</a></li> </ul> {% else %} <span>未登錄,跳轉到首頁...</span> <script type="text/javascript"> window.location.href = '/'; </script> {% endif %} </div> </div> </div> {% endblock %}
(4)已登錄頁面跳轉
如果用戶已經登錄,例如<login.html>的上一頁是<register.html>就會產生錯誤
所以加入一個判斷用戶是否登錄,如果已經登錄就會跳轉至首頁
以下為<login.html>的代碼:
<div class="col-xs-12 col-md-4 col-md-offset-4"> {% if not user.is_authenticated %} <!-- 添加面板 --> <div class="panel panel-default"> <!-- 面板標題 --> <div class="panel-heading">登錄</div> <div class="panel-body"> <!-- 表單 --> <form action="" method="POST"> {% csrf_token %} <!-- 進行表單細部調整 --> {% for field in login_form %} <!-- 移除冒號 --> <label for="{{ field.id_for_label }}">{{field.label}}</label> {{ field }} <!-- 錯誤訊息紅字顯示 --> <p class="text-danger">{{ field.errors.as_text }}</p> {% endfor %} <span class="pull-left text-danger">{{ login_form.non_field_errors }}</span> <input type="submit" value="登錄" class="btn btn-primary pull-right"> </form> </div> </div> {% else %} <span>已登錄,跳轉到首頁...</span> <script type="text/javascript"> window.location.href = '/'; </script> {% endif %} </div>
以下為<register.html>的代碼:
<div class="col-xs-12 col-md-4 col-md-offset-4"> {% if not user.is_authenticated %} <!-- 添加面板 --> <div class="panel panel-default"> <!-- 面板標題 --> <div class="panel-heading">註冊</div> <div class="panel-body"> <!-- 表單 --> <form action="" method="POST"> {% csrf_token %} <!-- 進行表單細部調整 --> {% for field in reg_form %} <!-- 移除冒號 --> <label for="{{ field.id_for_label }}">{{field.label}}</label> {{ field }} <!-- 錯誤訊息紅字顯示 --> <p class="text-danger">{{ field.errors.as_text }}</p> {% endfor %} <span class="pull-left text-danger">{{ reg_form.non_field_errors }}</span> <input type="submit" value="註冊" class="btn btn-primary pull-right"> </form> </div> </div> {% else %} <span>已登錄,跳轉到首頁...</span> <script type="text/javascript"> window.location.href = '/'; </script> {% endif %} </div>
3.導航欄添加用戶相關功能
(1)導入Bootstrap語法
參考Bootstrap中文文檔的導航條
在<base.html>頁面的導航條右側添加登入、註冊
使用者登入後則出現下拉選單(個人資料、登出)
以下為<base.html>添加的代碼:
<ul class="nav navbar-nav navbar-right"> {% if not user.is_authenticated %} <li><a href="{% url 'login' %}?from={{ request.get_full_path }}">登入</a></li> <li><a href="{% url 'register' %}?from={{ request.get_full_path }}">註冊</a></li> {% else %} <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ user.username }}<span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="{% url 'user_info' %}">個人資料</a></li> <li><a href="{% url 'logout' %}?from={{ request.get_full_path }}">登出</a></li> </ul> </li> {% endif %} </ul>
4.模態框登入全局化
(1)添加TEMPLATES全局變量
在user應用中創建<context_processors.py>,並撰寫以下代碼:
from .forms import LoginForm def login_modal_form(request): return {'login_modal_form' : LoginForm()}
在<settings.py>的TEMPLATES中添加以下代碼:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates'),], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'user.context_processors.login_modal_form', #模態框登入 ], }, }, ]
將blog應用<views.py>使用到LoginForm的部分移除
把<blog_detail.html>中引用'login_form'的地方改為'login_modal_form'
(2)模態框登入遷移至<base.html>
將<blog_detail.html>模態框登入的代碼遷移至<base.html>
{% content block %}{% endblock %}的下方
並且將對應的js代碼也遷移至<base.html>
以下為最後<base.html>的完整代碼:
{% load staticfiles %} <!-- 導入 staticfiles --> <!DOCTYPE html> <html lang="zh-TW"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- 對應IE瀏覽器 --> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 適應響應式設計 --> <!-- 將標題區塊化 --> <title>{% block title %}{% endblock %}</title> <link rel="stylesheet" href="{% static 'css/base.css' %}"> <!-- 導入 css 文件 --> <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}"> <link rel="shortcut icon" href="/static/favicon.ico"> {% block header_extends %}{% endblock %} <!-- 用於擴展額外 css --> </head> <body> <!-- bootstrap 響應式導航條 --> <div class="navbar navbar-default navbar-fixed-top" role="navigation"> <!-- 選用寬度填滿頁面的容器 --> <div class="container-fluid"> <!-- 導航條主標題 --> <div class="navbar-header"> <a class="navbar-brand" href="{% url 'home' %}">個人部落格</a> <!-- 響應式下拉選單 --> <button class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse"> <!-- 按鈕圖案上的三條線 --> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> </div> <!-- 導航條列表--> <div class="collapse navbar-collapse" id="navbar-collapse"> <ul class="nav navbar-nav"> <!-- 選取的列表加入active狀態--> <li class="{% block nav_home_active %}{% endblock %}"> <a href="{% url 'home' %}">首頁</a> </li> <li class="{% block nav_blog_active %}{% endblock %}"> <a href="{% url 'blog_list' %}">部落格</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> {% if not user.is_authenticated %} <li><a href="{% url 'login' %}?from={{ request.get_full_path }}">登入</a></li> <li><a href="{% url 'register' %}?from={{ request.get_full_path }}">註冊</a></li> {% else %} <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ user.username }}<span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="{% url 'user_info' %}">個人資料</a></li> <li><a href="{% url 'logout' %}?from={{ request.get_full_path }}">登出</a></li> </ul> </li> {% endif %} </ul> </div> </div> </div> {% block content %}{% endblock %} <div class="modal fade" id="login_modal" tabindex="-1" role="dialog"> <div class="modal-dialog modal-sm" role="document"> <div class="modal-content"> <form id="login_modal_form" action="" method="POST"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title">登錄</h4> </div> <div class="modal-body"> {% csrf_token %} <!-- 進行表單細部調整 --> {% for field in login_modal_form %} <label for="{{ field.id_for_label }}">{{field.label}}</label> {{ field }} {% endfor %} <!-- 錯誤訊息透過id由js傳入 --> <span class="text-danger" id="login_modal_tip"></span> </div> <div class="modal-footer"> <button type="submit" class="btn btn-primary">登錄</button> <button type="button" class="btn btn-default" data-dismiss="modal">關閉</button> </div> </form> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> <!-- 導入jquery和bootstrap(jquery必須在前) --> <script type="text/javascript" src="{% static 'jquery-3.3.1.min.js' %}"></script> <script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script> <script type="text/javascript"> $('#login_modal_form').submit(function(event){ //送出後阻止頁面刷新 event.preventDefault(); //送出後阻止頁面刷新 $.ajax({ url: "{% url 'login_for_modal' %}", type: 'POST', data: $(this).serialize(), //序列化表單值 cache: false, success: function(data){ console.log(data); if(data['status']=='SUCCESS'){ window.location.reload(); //當前窗口重新加載 }else{ $('#login_modal_tip').text('用戶名稱或密碼錯誤'); } } }); }); </script> {% block script_extends %}{% endblock %} </body> </html>