本文主要介紹我在Django部落格使用的許可權設定,我的部落格中幾乎所有的許可權設定都是我自己寫的。這麼做的原因是我在寫Flask部落格的時候,學到了一些這方面的知識,感覺很不錯。因此就借鑑裡面的邏輯,自己寫了這方面的程式碼。
1、關於文章的訪問許可權
我就從Model和View兩層來說。
首先我在Model中定義一個欄位,用來指示是否公開,是否允許別人可見: 原始碼如下:
1 2 3 4 5 6 7 8 9 10 11 |
ACCESS = { 100:u'公開', 200:u'私人可見' } class Article(models.Model): title = models.CharField(max_length=150,unique=True,verbose_name=u'標題') alias = models.CharField(max_length=150,verbose_name=u'英文標題') .......... .......... access = models.IntegerField(default=100,choices=ACCESS.items(),verbose_name=u'文章許可權,公開或者私人可見') |
access欄位指定該欄位是否公開。如果設為公開那麼所有人可以訪問;如果設為私密,那麼就不允許一些使用者訪問。那此時問題來了,該如何設定限制訪問邏輯。我的程式碼如下,該方法也定義在Model中:
1 2 3 4 5 6 7 8 |
def can_access(self,user): if self.access == 100: return True if self.access == 200: if user is None: return False else: return self.author.id == user.id or user.is_staff |
上面的程式碼很簡單,如果是私密的,只有文章作者或者管理員可以訪問。
在View中的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ArticleDetailView(BaseMixin,DetailView): queryset = Article.objects.filter(status=0) slug_field = 'alias' context_object_name = 'article' template_name = 'article.html' object = None def get(self, request, *args, **kwargs): alias = self.kwargs.get('slug') try: self.object = self.queryset.get(alias=alias) except Article.DoesNotExist: logger.error('article does not exsists') #I should rewrite the 404 html later return HttpResponseNotFound('Page not Found') # add permission,if the article has set permission,the web will raise one exveption if not self.object.can_access(request.user): raise PermissionDenied |
看這段程式碼最後面,如果can_acees方法返回False,就丟擲一個禁止Django內建的禁止訪問的異常,即返回403頁面。
2、自定義檢視許可權裝飾器
首先自己定義一個裝飾器函式,用來修飾要設定許可權的檢視函式或類方法。 裝飾器函式原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
from functools import wraps from django.http import HttpResponse,HttpResponseRedirect,HttpResponseNotFound def permission_forbidden(http_exception=403,next_url='/account/login/'): """ Usage: @permission_forbidden(403) def test(request): return HttpResposne('hello world') when decorated by permission_forbidden,if the user is not staff, it will raise one PerissionDenied exception :param http_exception: :return:the return value of decorated function """ def decorator(func): @wraps(func) def wrapper(request,**kwargs): if http_exception == 403: if request.user.is_staff: rv = func(request,**kwargs) return rv else: raise PermissionDenied elif http_exception == 401: if not request.user.is_authenticated(): return HttpResponseRedirect(next_url) rv = func(request,**kwargs) return rv return wrapper return decorator |
這是自己寫的一個三層裝飾器函式,程式碼很簡單。如果http_exception為403,當不是管理員許可權就丟擲禁止訪問異常;當http_exception為401,當使用者沒有登入就跳轉到登入頁面。
在View中:
1 2 3 4 5 6 |
@permission_forbidden(http_exception=401) def TestForm(request): if 'start' in request.GET and request.GET['start']: return render_to_response('cost.html',{'current':'haibo','start':request.GET['start']}) else: return render_to_response('cost.html',{'current':''}) |
這是一個用於測試的檢視函式。
上面講解了如何使用裝飾器函式裝飾檢視函式,那麼該如何裝飾基於類的檢視呢?
在Django中的官網上給出了答案。
最簡單的方法是在URLconf 中裝飾,裝飾類的as_view方法:
如現在我在View中定義了一個基於類的檢視:
1 2 3 4 5 6 7 8 9 10 11 |
class AuthorView(BaseMixin,ListView): template_name = 'author_information.html' context_object_name = 'categories' def get_queryset(self): categories = Category.available_categories() return categories def get_context_data(self,*args,**kwargs): return super(AuthorView,self).get_context_data(**kwargs) |
直接在URl中配置: url(r’^author/$’, permission_forbidden(http_exception=403)(AuthorView.as_view()),name=’blog_author’),
這樣就可實現上面對檢視函式相同的裝飾。這個方法在每個例項的基礎上運用裝飾器。如果想讓檢視的每個例項都被裝飾,你需要一種不同的方法。
下面是摘抄自官方文件:
類的方法和獨立的函式不完全相同,所以你不可以直接將函式裝飾器運用到方法上 —— 你首先需要將它轉換成一個方法裝飾器。
method_decorator 裝飾器將函式裝飾器轉換成方法裝飾器,這樣它就可以用於例項方法上。例如:
1 2 3 4 5 6 7 8 |
from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views.generic import TemplateView class ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super(ProtectedView, self).dispatch(*args, **kwargs) |
在這個例子中,ProtectedView 的每個例項都將有登入保護。
不過有一點要說明:我用這個方法測試沒有成功!!!
上面說了這麼多我自定義的許可權設定,這裡也說一下Django內建的許可權系統。
詳細的內容請參考下面的兩個連結,一個是個人部落格,一個是Django官方文件:
- http://www.cnblogs.com/esperyong/archive/2012/12/20/2826690.html
- http://python.usyiyi.cn/django/topics/auth/default.html
我這裡只說兩點:
一是分配許可權的問題。
預設情況下,Django會為每一個Model自動生成一add,delete,change的許可權,並儲存在auth_permission資料表中。
我們要想把許可權分配給使用者,就要用User的user_permissions屬性。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def user_gains_perms(request, user_id): user = get_object_or_404(User, pk=user_id) # any permission check will cache the current set of permissions user.has_perm('myapp.change_bar') permission = Permission.objects.get(codename='change_bar') user.user_permissions.add(permission) # Checking the cached permission set user.has_perm('myapp.change_bar') # False # Request new instance of User # Be aware that user.refresh_from_db() won't clear the cache. user = get_object_or_404(User, pk=user_id) # Permission cache is repopulated from the database user.has_perm('myapp.change_bar') |
對於超級使用者來講,他擁有所有的許可權,並不需要再做分配。
2、模板中的許可權變數
在setting.py中,我們定義了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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', ], }, }, ] |
context_processor會自動新增一些上下文,當然,其中包括perms.原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def auth(request): """ Returns context variables required by apps that use Django's authentication system. If there is no 'user' attribute in the request, uses AnonymousUser (from django.contrib.auth). """ if hasattr(request, 'user'): user = request.user else: from django.contrib.auth.models import AnonymousUser user = AnonymousUser() return { 'user': user, 'perms': PermWrapper(user), } |
它新增了,user和perms兩個變數,perms指的是當前使用者的所有許可權。 這樣,在模板中,就可以使用perms變數了。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式