在 Django Admin 中获取当前用户的关键在于理解请求上下文(request context)。每个 Admin 视图都会接收一个 request 对象,这个对象包含了当前 HTTP 请求的所有信息,其中就包括用户认证相关的数据。
Django 的认证系统会自动将当前登录用户附加到 request 对象上,我们可以通过 request.user 来访问:
python复制current_user = request.user
这个 user 对象可能是以下几种情况之一:
在使用 request.user 前,应该先检查用户是否已认证:
python复制if request.user.is_authenticated:
# 已登录用户处理逻辑
print(f"当前用户: {request.user.username}")
else:
# 匿名用户处理逻辑
print("用户未登录")
注意:在 Django Admin 中,默认情况下匿名用户是无法访问的,所以通常不需要处理匿名用户的情况。但如果是自定义的 Admin 视图,还是应该做好检查。
除了检查认证状态,我们还需要检查用户权限:
python复制# 检查是否是超级用户
if request.user.is_superuser:
# 超级用户特有逻辑
# 检查是否是员工
if request.user.is_staff:
# 员工特有逻辑
# 检查特定权限
if request.user.has_perm('app_label.permission_code'):
# 有特定权限的逻辑
ModelAdmin 提供了多个可以重写的方法,这些方法都会接收 request 参数:
python复制from django.contrib import admin
class MyModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
"""保存模型时获取用户"""
current_user = request.user
if change:
obj.last_modified_by = current_user
else:
obj.created_by = current_user
super().save_model(request, obj, form, change)
def delete_model(self, request, obj):
"""删除模型时获取用户"""
print(f"用户 {request.user.username} 删除了 {obj}")
super().delete_model(request, obj)
def get_queryset(self, request):
"""过滤查询集"""
qs = super().get_queryset(request)
if not request.user.is_superuser:
qs = qs.filter(created_by=request.user)
return qs
在自定义的 Admin 操作中,request 对象也是可用的:
python复制def custom_action(modeladmin, request, queryset):
"""自定义批量操作"""
for obj in queryset:
obj.processed_by = request.user
obj.save()
modeladmin.message_user(request, f"已处理 {queryset.count()} 条记录")
custom_action.short_description = "自定义处理"
class MyModelAdmin(admin.ModelAdmin):
actions = [custom_action]
在自定义的 ModelForm 中获取当前用户需要一些技巧,因为表单本身没有直接访问 request 的权限:
python复制from django import forms
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = '__all__'
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super().__init__(*args, **kwargs)
if self.request and self.request.user:
if not self.request.user.is_superuser:
self.fields['sensitive_field'].disabled = True
def save(self, commit=True):
instance = super().save(commit=False)
if self.request and hasattr(instance, 'last_modified_by'):
instance.last_modified_by = self.request.user
if commit:
instance.save()
return instance
python复制class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
def get_form(self, request, obj=None, **kwargs):
FormClass = super().get_form(request, obj, **kwargs)
class FormWithRequest(FormClass):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return FormClass(*args, **kwargs)
return FormWithRequest
python复制class MyInline(admin.TabularInline):
model = RelatedModel
extra = 1
def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj, **kwargs)
formset.request = request
return formset
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "assigned_to":
if request.user.department:
kwargs["queryset"] = User.objects.filter(
department=request.user.department
)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
在自定义的 Admin 模板中,可以直接访问 {{ request.user }}:
html复制{% extends "admin/change_list.html" %}
{% block content %}
<div class="user-info">
<p>当前用户: <strong>{{ request.user.username }}</strong></p>
<p>用户组:
{% for group in request.user.groups.all %}
{{ group.name }}{% if not forloop.last %}, {% endif %}
{% empty %}
无
{% endfor %}
</p>
</div>
{{ block.super }}
{% endblock %}
如果需要全局访问当前用户,可以使用中间件:
python复制# middleware.py
import threading
_thread_locals = threading.local()
def get_current_user():
return getattr(_thread_locals, 'user', None)
class ThreadLocalMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
_thread_locals.request = request
_thread_locals.user = getattr(request, 'user', None)
response = self.get_response(request)
for attr in ('request', 'user'):
if hasattr(_thread_locals, attr):
delattr(_thread_locals, attr)
return response
python复制from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
from django.contrib.contenttypes.models import ContentType
class AuditableModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
action_flag = CHANGE if change else ADDITION
LogEntry.objects.log_action(
user_id=request.user.id,
content_type_id=ContentType.objects.get_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=action_flag,
change_message=f'{"修改" if change else "添加"}了 {obj}'
)
python复制# utils.py
from django.contrib.auth import get_user_model
User = get_user_model()
def get_user_display_name(user):
return user.get_full_name() or user.username
def filter_queryset_by_user(queryset, user, field_name='created_by'):
if user.is_superuser:
return queryset
return queryset.filter(**{field_name: user})
如果在某些地方无法获取 request 对象,可以考虑:
在实际项目中,我经常遇到需要根据用户角色动态调整 Admin 界面的需求。一个实用的技巧是创建一个基础的 SecureModelAdmin,然后让其他 ModelAdmin 继承它:
python复制class SecureModelAdmin(admin.ModelAdmin):
"""基础安全 ModelAdmin,实现通用的权限控制"""
def get_queryset(self, request):
qs = super().get_queryset(request)
if not request.user.is_superuser:
qs = qs.filter(created_by=request.user)
return qs
def has_change_permission(self, request, obj=None):
if obj is None:
return True
return obj.created_by == request.user or request.user.is_superuser
class ProductAdmin(SecureModelAdmin):
"""产品管理,继承基础权限控制"""
list_display = ('name', 'price', 'created_by')
这种模式可以避免在每个 ModelAdmin 中重复实现相同的权限控制逻辑,提高代码的可维护性。