Skip to content

Blog Application

This example shows how to implement permissions for a typical blog application.

Models

# models.py
from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Post(models.Model):
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('review', 'In Review'),
        ('published', 'Published'),
    ]

    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
    is_featured = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    is_approved = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

Permissions

# permissions.py
from django_shield import rule

# User-level rules
@rule
def is_authenticated(user, obj=None):
    return user.is_authenticated

@rule
def is_staff(user, obj=None):
    return user.is_staff

@rule
def is_editor(user, obj=None):
    return hasattr(user, 'role') and user.role == 'editor'

# Post rules
@rule
def is_post_author(user, post):
    return post.author == user

@rule
def is_post_published(user, post):
    return post.status == 'published'

@rule
def can_view_post(user, post):
    if post.status == 'published':
        return True
    return post.author == user or user.is_staff

@rule
def can_edit_post(user, post):
    if user.is_staff:
        return True
    return post.author == user and post.status in ['draft', 'review']

@rule
def can_delete_post(user, post):
    if user.is_superuser:
        return True
    return post.author == user and post.status == 'draft'

@rule
def can_publish_post(user, post):
    return user.is_staff or (hasattr(user, 'role') and user.role == 'editor')

# Comment rules
@rule
def is_comment_author(user, comment):
    return comment.author == user

@rule
def can_moderate_comment(user, comment):
    return user.is_staff or comment.post.author == user

Views

Function-based Views

# views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django_shield import guard
from .models import Post, Comment

# Public: List published posts
def post_list(request):
    posts = Post.objects.filter(status='published')
    return render(request, 'blog/post_list.html', {'posts': posts})

# View single post
@guard('can_view_post', model=Post, inject='post')
def post_detail(request, pk, post):
    return render(request, 'blog/post_detail.html', {'post': post})

# Create new post
@guard('is_authenticated')
def post_create(request):
    if request.method == 'POST':
        post = Post.objects.create(
            title=request.POST['title'],
            content=request.POST['content'],
            author=request.user,
            status='draft'
        )
        return redirect('post_detail', pk=post.pk)
    return render(request, 'blog/post_form.html')

# Edit post
@guard('can_edit_post', model=Post, inject='post')
def post_edit(request, pk, post):
    if request.method == 'POST':
        post.title = request.POST['title']
        post.content = request.POST['content']
        post.save()
        return redirect('post_detail', pk=post.pk)
    return render(request, 'blog/post_form.html', {'post': post})

# Delete post
@guard('can_delete_post', model=Post, inject='post')
def post_delete(request, pk, post):
    if request.method == 'POST':
        post.delete()
        return redirect('post_list')
    return render(request, 'blog/post_confirm_delete.html', {'post': post})

# Publish post (staff/editor only)
@guard('can_publish_post', model=Post, inject='post')
def post_publish(request, pk, post):
    post.status = 'published'
    post.save()
    return redirect('post_detail', pk=post.pk)

# Add comment
@guard('is_authenticated')
def comment_create(request, post_pk):
    post = Post.objects.get(pk=post_pk)
    if request.method == 'POST':
        Comment.objects.create(
            post=post,
            author=request.user,
            content=request.POST['content']
        )
        return redirect('post_detail', pk=post_pk)
    return render(request, 'blog/comment_form.html', {'post': post})

# Delete own comment or moderate
@guard.any('is_comment_author', 'can_moderate_comment', model=Comment)
def comment_delete(request, pk):
    comment = Comment.objects.get(pk=pk)
    post_pk = comment.post.pk
    comment.delete()
    return redirect('post_detail', pk=post_pk)

# Approve comment (moderation)
@guard('can_moderate_comment', model=Comment, inject='comment')
def comment_approve(request, pk, comment):
    comment.is_approved = True
    comment.save()
    return redirect('post_detail', pk=comment.post.pk)

Class-based Views

# views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django_shield import guard
from .models import Post

class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    queryset = Post.objects.filter(status='published')

@guard('can_view_post')
class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/post_detail.html'

@guard('is_authenticated')
class PostCreateView(CreateView):
    model = Post
    fields = ['title', 'content']
    template_name = 'blog/post_form.html'

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.instance.status = 'draft'
        return super().form_valid(form)

@guard('can_edit_post')
class PostUpdateView(UpdateView):
    model = Post
    fields = ['title', 'content']
    template_name = 'blog/post_form.html'

@guard('can_delete_post')
class PostDeleteView(DeleteView):
    model = Post
    template_name = 'blog/post_confirm_delete.html'
    success_url = reverse_lazy('post_list')

URLs

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('posts/<int:pk>/', views.post_detail, name='post_detail'),
    path('posts/new/', views.post_create, name='post_create'),
    path('posts/<int:pk>/edit/', views.post_edit, name='post_edit'),
    path('posts/<int:pk>/delete/', views.post_delete, name='post_delete'),
    path('posts/<int:pk>/publish/', views.post_publish, name='post_publish'),
    path('posts/<int:post_pk>/comments/new/', views.comment_create, name='comment_create'),
    path('comments/<int:pk>/delete/', views.comment_delete, name='comment_delete'),
    path('comments/<int:pk>/approve/', views.comment_approve, name='comment_approve'),
]

Using Expression Syntax

For simpler cases, use expression syntax instead of named rules:

# Instead of defining can_view_post rule
@guard('obj.status == "published" or obj.author == user or user.is_staff', model=Post)
def post_detail(request, pk):
    ...

# Instead of defining can_edit_post rule
@guard('user.is_staff or (obj.author == user and obj.status in ["draft", "review"])', model=Post)
def post_edit(request, pk):
    ...

# Simple checks
@guard('user.is_authenticated')
def post_create(request):
    ...

Permission Summary

Action Rule Who Can Do It
View published post Anyone All visitors
View draft post can_view_post Author, Staff
Create post is_authenticated Logged in users
Edit own draft can_edit_post Author
Edit any post can_edit_post Staff
Delete own draft can_delete_post Author
Delete any post can_delete_post Superuser
Publish post can_publish_post Staff, Editor
Delete comment is_comment_author or can_moderate_comment Comment author, Post author, Staff
Approve comment can_moderate_comment Post author, Staff