[Python+Django]初心者筆記11(authentication,authorization,login驗證機制範例,permission權限機制範例)

[Python+Django]初心者筆記11(authentication,authorization,login驗證機制範例,permission權限機制範例)

這個驗證機制的範例就是要:listing the current user's books
就是要做authentication,authorization驗證
再說白話點就是做"登入"機制
首先回到catalog/models.py修改BookInstance的資料表結構
先在檔案最上面加入:

from datetime import date 

然後在類別BookInstance加入:

from django.contrib.auth.models import User
#表示借這本書的人是誰
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
#是否到期,自動回傳false or true
@property
def is_overdue(self):
	if self.due_back and date.today() > self.due_back:
		return True
	return False

改了db資料表,那麼當然要在終端機執行migration:

python manage.py makemigrations
python manage.py migrate

然後回到admin管理介面catalog/admin.py,把剛剛新增的borrower欄位補上:

接著請利用admin介面,幫某位使用者借個幾本書吧(幫他借個2~3本):


然後將會建立一個LoanedBooksByUserListView的頁面
先回到catalog/views.py幫此頁面加入server side行為:

#限制LoanedBooksByUserListView功能必須登入:LoginRequiredMixin
from django.contrib.auth.mixins import LoginRequiredMixin

class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
    """
    Generic class-based view listing books on loan to current user. 
    """
    model = BookInstance
    #自定義.html檔案的路徑,這次不使用預設路徑了!
    template_name ='catalog/bookinstance_list_borrowed_user.html'
    paginate_by = 10
    
    def get_queryset(self):
        return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back') 

接著於 /catalog/urls.py 設定url mapping:
直接加在程式碼的最下面即可囉

urlpatterns += [   
    path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
]

接著請新增這個檔案/catalog/templates/catalog/bookinstance_list_borrowed_user.html,檔案內容如下:

{% extends "base_generic.html" %}

{% block content %}
    <h1>Borrowed books</h1>

    <!-- bookinstance_list大概是強制為檔案名稱的前兩個字吧 -->
    {% if bookinstance_list %}
    <ul>

      {% for bookinst in bookinstance_list %} 
      <li class="{% if bookinst.is_overdue %}text-danger{% endif %}">
        <a href="{% url 'book-detail' bookinst.book.pk %}">{{bookinst.book.title}}</a> ({{ bookinst.due_back }})        
      </li>
      {% endfor %}
    </ul>

    {% else %}
      <p>There are no books borrowed.</p>
    {% endif %}       
{% endblock %}

接著只要打開網址http://127.0.0.1:8000/catalog/mybooks/
就可以查出目前登入者有借了哪幾本書囉~

為了方便以後快速看到自己借的書,把這段

<li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li>

加入到/locallibrary/catalog/templates/base_generic.html的<ul class="sidebar-nav">裡面吧:

如下圖,可看到albertu借了兩本書:

接著就來示範permission權限機制範例:
Django做權限的方式,是透過"user behavior"來做權限的分類,他先定義好所有不同的"user behavior"(定義在models.py),然後每個系統功能自行加上所需要的"user behavior"限制,例如某個user behavior叫做"can_view_all_books",然後在每個系統功能(即每個網頁功能)的Server side程式碼(即views.py)再去設定這個功能需要哪些user behavior權限,最後就是去User資料表,設定某某user有哪些的使用者權限,那麼user登入之後,就可以使用他有權限的系統功能了!
接著是個人對這個權限的作法的看法:
一般的權限作法,都是by 系統功能去做限制,第一次碰到這種用"user behavior"做限制的,感覺有點不太直覺,如果碰到某某系統功能很難定義為哪一種user behavior的時候,就會很難設定該功能的權限。
廢話不多說回來示範Django的permission權限範例示範:
所有預設定義好的user behavior都在每個系統功能建立完成的時候順便建立了,像是can_add_ooxx, can_change_ooxx, can_delete_ooxx之類的
像這個admin管理畫面顯示的是直接去改user的權限:

而這個顯示的是透過admin介面直接去改某某group有什麼權限:

user或group有什麼user behavior權限可用,直接用admin UI介面改一改即可,不過Django仍鼓勵programmer自訂權限,自訂方法請於catalog/models.py的某個class的Meta subclass定義,下面示範了在BookInstance類別加入了1個權限:can_view_all_borrowed_books
在Meta裡面加入這段內容即可:

#加上只有圖書館人員專用的權限
#can_view_all_borrowed_books是此權限的代碼名稱codename
#one can view all borrowed books是口語化的名稱name
permissions = (("can_view_all_borrowed_books", "one can view all borrowed books"),) 

加好之後看起來像是這樣:
改完這個models.py之後記得在終端機run一下migration
python manage.py makemigrations
python manage.py migrate
ps.補充一個當下發現的的admin管理介面的bug
在admin管理介面,當你要設定權限給group或是user的時候,你會發現畫面上只會顯示後面那項"can_edit_all_borrowed_books",經過測試的結果,只能一口氣給這兩個權限,或是一口氣移除這兩個權限!

然後於catalog/views.py加入server side行為:
用以設定這個系統功能需要有can_view_all_borrowed_books權限

#僅圖書館工作人員librarian可確認所有已經借出的書籍
from django.contrib.auth.mixins import PermissionRequiredMixin

class AllLoanedBooksListView(PermissionRequiredMixin, generic.ListView):
    #這個系統功能需要有can_view_all_borrowed_books權限
    permission_required = 'catalog.can_view_all_borrowed_books'
    model = BookInstance
    template_name ='catalog/bookinstance_list_borrowed_all.html'
    def get_queryset(self):
        #排序order by due_back desc
        return BookInstance.objects.filter(status__exact='o').order_by('-due_back')
    paginate_by = 10

然後在catalog/urls.py設定這個系統功能的url mapping:

#圖書館管理人員限定的all borrowed books網頁
urlpatterns += [   
    path('borrowed/', views.AllLoanedBooksListView.as_view(), name='all-borrowed'),
]


然後就是新增這個系統功能的.html檔locallibrary\catalog\templates\catalog\bookinstance_list_borrowed_all.html:檔案內容如下

{% extends "base_generic.html" %}

{% block content %}
    <h1>All Borrowed Books</h1>

    {% if bookinstance_list %}
    <ul>

      {% for bookinst in bookinstance_list %} 
      {% comment %} 如果書籍借閱過期了,就用紅色文字顯示 {% endcomment %}
      <li class="{% if bookinst.is_overdue %}text-danger{% endif %}">
            <a href="{% url 'book-detail' bookinst.book.pk %}">{{bookinst.book.title}}</a>
             ({{ bookinst.due_back }}) {% if user.is_staff %}- {{ bookinst.borrower }}{% endif %} 
      </li>
      {% endfor %}
    </ul>

    {% else %}
      <p>There are no books borrowed.</p>
    {% endif %}       
{% endblock %}


然後在locallibrary\catalog\templates\base_generic.html的side bar加入這段,方便圖書館人員快速點選這個有權限的功能:

{% if user.is_staff %}
	<hr />
	<ul class="sidebar-nav">
	<li>Staff</li>
	{% if perms.catalog.can_view_all_borrowed_books %}
	<li><a href="{% url 'all-borrowed' %}">All borrowed</a></li>
	{% endif %}
	</ul>
{% endif %}

加入之後看起來會像是這樣:

然後畫面左方就會有圖書館人員才看的到的All borrowed超連結:

點進去此超連結之後即可顯示所有已經被借閱的書籍:

permission權限的範例部分大概是這樣,這邊示範的是class-based view的權限限制作法,function-based view的權限限制作法就大同小異了,沒必要示範那麼詳細,以後需要再上網查吧……
這篇大概是這樣……


參考資料:
Django Tutorial Part 8: User authentication and permissions
https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Authentication