Django官方文档
Django 项目生成时,会自动生成后台管理应用:Admin。
和创建 app 类似,在控制台输入
python manage.py createsuperuser
然后根据提示输入用户名、邮箱、密码,就创建超级用户成功了。
当创建了超级用户后,会在总路由里注册路由地址 admin,就可以使用这个 url 登录到默认的后台登录界面了。可以在后台界面添加用户、组、权限等。
通过在 settings.py 中修改语言、时区等设置,可以更改后台界面的
Xadmin 是基于原生 admin 的界面,在 github 上有相应的源码。它使用了基于 bootstrap 的样式,界面进行了美化。
使用 xadmin 需先安装
pip install xadmin-py3
如果我们自己写了相应的数据模型,例如用户信息,可以通过后台访问,这就是托管。
# models.py
from django.db import models# 客户用户表
class UserEntity(models.Model):name = models.CharField(max_length=20)age = models.IntegerField(default=0)phone = models.CharField(max_length=11)class Meta:# 表名db_table = 'user' # 别名verbose_name = '客户列表'# 复数别名,默认添加个 sverbose_name_plural = verbose_namedef __str__(self):return self.name
在 app 目录下的 admin.py 中可以托管本 app 使用的数据模型
from django.contrib import admin
# 导入数据模型
from app.models import UserEntity# 注册模型到 admin 站点中
admin.site.register(UserEntity)
然后在后台界面就能看到相应 app 下的数据模型了。需要注意的是,每条数据显示对象,所以必须使用 __str__ 方法来定制对象的输出字符串。
通过在 admin.py 文件中,定义 admin.ModelAdmin 的子类来自定义表单。
例如,添加 Store 的表单中只添加 name(自定义表单字段)
class StoreAdmin(admin.ModelAdmin):fields = ('name',) # 字段,元组类型admin.site.register(Store, StoreAdmin) # 注册模型和其相关配置
分栏显示
class StoreAdmin(admin.ModelAdmin):fieldsets = (['Main', {'fields': ('name',)}],['Advance', {'fields': ('address',),'classes': ('collapse',)}])
内联显示
# 为外表创建内联类
class FruitInline(admin.TabularInline):model = Fruit# 在主表设置内联
class StoreAdmin(admin.ModelAdmin):inlines = [FruitInline]
在 admin.ModelAdmin 的子类中,可以这样设置:
class StoreAdmin(admin.ModelAdmin):list_display = ('id', 'name', 'address','custom1') # 列表显示的字段,元组fields = ('id', 'name', 'address') # 表单中使用的字段(可以和显示的不同)list_per_page = 2 # 分页显示,每页显示2条记录list_filter = ('id', 'name') # 过滤器,一般配置分类过滤search_fields = ('name', 'address') # 搜索关键字# list_display 中定义了自定义的字段,非数据实体的字段# 在此定义自定义字段显示内容,参数 obj 是传入的数据实体对象def custom1(self, obj):return obj.email# 自定义字段显示的标题(字段名)custom1.short_description = 'E-mail'admin.site.register(Store, StoreAdmin) # 注册模型和其相关配置
django 自带了权限管理模块 auth,提供了用户身份认证、用户组和权限管理
与 auth 模块有关的数据库表有6个,分别是
| 表 | 作用 | 备注 |
|---|---|---|
| auth_user | 用户信息,包含 id、password、username、first_name、last_name、email、is_staff、is_active、date_joined | |
| auth_group | 组信息 | 每个组拥有id和name两个字段 |
| auth_user_groups | user和group之间的关系 | |
| auth_permission | 用户许可、权限 | 每条权限有id、name、content_type_id、codename四个字段 |
| auth_user_user_permissions | user和permission之间的关系 | |
| auth_group_permissions | 用户组和权限的对应关系 | 使用用户组管理权限是一个更方便的方法,group中包含多对多字段permissions |
需注意的是,django 的缓存机制:django 会缓存每个用户对象,包括其权限 user_permissions。当手动改变某一用户的权限后,必须重新获取改用户对象,才能获取最新的权限。如不重新载入用户对象,则权限还是没有刷新。
可以直接使用一些接口函数对内建的数据模型进行操作,从而做到权限及用户管理
创建用户
from django.contrib.auth.models import User
user = User.objects.create_user(username, email, password, is_staff) # is_staff 是个布尔值,表示是否登录admin后台
user.save()
用户认证
from django.contrib.auth import authenticate
# 认证用户的密码是否有效,认证成功返回代表该用户的 user 对象,否则返回 None。
# 需注意的是,此方法只检查认证用户是否有效,并不检查具体权限和是否激活(is_active)标识
user = authenticate(username=username, password=password)from django.contrib.auth.hashers import check_password
# 或使用 check_password 方法,返回布尔值
user = User.objects.filter(username=username)
check_password(password, user.password)
修改用户密码
user = auth.authenticate(username=usermane, password=old_password)
if user:user.set_password(new_password)user.save()
登录
from django.contrib.auth import login
user = authenticate(username=username, password=password) # 验证成功返回用户对象
if user: if user.is_active: # 用户已经激活# 登录,向 session 中添加 user 对象,便于对用户进行跟踪login(request, user)
获取当前用户
user = request.user
判断当前用户是否通过验证(即已经登录)
request.user.is_authenticated # 布尔值
# 如果是视图函数需要判断是否登录,还可以使用装饰器
@login_required
def inddex(request):pass
退出登录
from django.contrib.auth import logout
def logout_view(request):logout(request)
django.contirb.auth.models.Group 定义了用户组的模型,每个用户组拥有 id 和 name 两个字段,该模型在数据库被映射为 auth_group 表。User 对象中有一个名为 groups 的多对多字段,由 auth_user_group 表维护。Group 对象可以通过 user_set 反向查询用户组中的用户。
创建组
group = Group.objects.create(name=group_name)
group.save()
删除组
group.delete()
用户加入用户组
user.groups.add(group)
# 或者
group.user_set.add(user)
用户退出用户组
user.groups.reomve(group)
# 或者
group.user_set.remove(user)
用户退出所有用户组
user.groups.clear()
用户组清除所有用户
group.user_set.clear()
在定义 model 时可以使用 Meta 定义此模型的权限
class Discussion(models.Model):...class Meta:permissions = (('create_discussion', 'Can create a discussion'),('reply_discussion', 'Can reply discussion'),)
检查用户权限:user.has_perm 方法用于检查用户是否拥有操作某个视图或模型的权限,若有则返回 True
user.has_perm('blog.add_article')
user.has_perm('blog.change_article')
user.has_perm('blog.delete_article')
抛出异常的权限检查:has_perm 仅是进行权限检查,即使用户没有权限它也不会阻止程序执行相关操作。可以使用 @permission_required 装饰器代替 has_perm 并在用户没有相应权限时重定向到登录页或抛出异常。
在视图中使用这个装饰器验证是一个好的选择,但是如果使用视图类(CBV),而不是视图函数(FBV),则不能使用此装饰器。需要继承 RermissionRequiredMixin 这个类。
# permission_required(perm[, login_url=None, raise_exception=False])@permission_required('blog.add_article')
def post_atricle(request):pass
在模板中也可以进行权限验证:主要使用 perms 这个全局变量。perms 对当前用户的 user.has_module_perms 和 user.has_perm 方法进行了封装。例如判断当前用户是否拥有 blog 应用下所有的权限:
{{ perms.blog }}
这样结合 if 标签,可以选择性的根据用户权限显示不同内容了
{% if perms.blog.add_article %}
You can add atricles.
{% endif %}
{% if perms.blog.delete_article %}
You can delete atricles.
{% endif %}
每个模型默认拥有增(add)删(delete)的权限
# 添加用户权限
user.user_permissions.add(permission)
# 删除用户权限
user.user_permissions.delete(permission)
# 清空用户权限
user.user_permissions.clear()
用户拥有所在组的权限,使用用户组管理权限是一个更方便的方法。Group 中包含多对多字段 permissions,在数据库中由 auth_group_permissions 表进行维护
# 添加组权限
group.permissions.add(permission)
# 删除组权限
group.permissions.delete(permission)
# 清空组权限
group.permissions.clear()
还可以获取某个特定用户的相关权限信息
# 特定用户所在用户组的权限
user_A.get_group_permissions()
# 特定用户的所有权限
user_A.get_all_permissions()
当编辑某个 user 信息时,可以在 User permissions 栏为其设置权限。权限的规则是:
权限显示为:应用 | 模型 | 行为(增删改查等)
权限数据为:应用.行为_模型
其中 行为_模型 就是数据库表中的 codename,应用即 app_label
django 自带的权限管理机制是针对模型的,这就意味着如果一个用户对某一模型有权限,则此模型中的所有数据均有权限。如果希望实现针对单个数据的权限管理,则需要使用第三方库比如 django guardian 库。
django-guardian 官网英文文档
使用 pipy 安装
pip install django-guardian
安装完成后,需要将添加到项目中,首先添加 app,然后添加身份验证后端
# settings.pyINSTALLED_APPS = ( # ... 'guardian',
)AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', # 这是Django默认的'guardian.backends.ObjectPermissionBackend', # 这是guardian的
)
然后创建 guardian 的数据库表。创建完成后会多出两张表:guardian_groupobjectpermission 和 guardian_userobjectpermission,分别记录了用户组/用户与model及具体object的权限对应关系
python manage.py migrate
下面是表中各字段的含义
| 字段 | 说明 |
|---|---|
| id | 默认主键 |
| object_pk | object 的 id,标识具体是哪个对象需要授权,对应的是具体某一数据 |
| content_type_id | 记录具体哪个表的id,对应的是django系统表django_content_type内的某条数据,django所有注册的model都会在这个表里记录 |
| group_id/user_id | 记录是那个组/用户会有权限,对应的是auth_group/auth_user表里的某条记录 |
| permission_id | 记录具体的某个权限,对应的是auth_permission表里的某条记录 |
需注意的是,一旦将 django-guardian 配置进项目,当调用 migrate 命令时将会创建一个匿名用户的实例(名为 AnonymousUser)。guardina 的匿名用户与 django 的匿名用户不同,django 匿名用户在数据库中没有条目,但 guardian 匿名用户有。这意味着以下代码将会返回意外的结果:
request.user.is_anonymous = True
在 settings.py 中可以进行其他的一些配置
# 如果GUARDIAN_RAISE_403设置为True,guardian将会抛出django.core.exceptions.PermissionDenied异常,而不是返回一个空的django.http.HttpResponseForbidden
# 需注意的是GUARDIAN_RENDER_403和GUARDIAN_RAISE_403不能同时设置为True。否则将抛出django.core.exceptions.ImproperlyConfigured异常
GUARDIAN_RAISE_403 = False# 如果GUARDIAN_RENDER_403设置为True,将会尝试渲染403响应,而不是返回空的django.http.HttpResponseForbidden。模板文件将通过GUARDIAN_TEMPLATE_403来设置。
GUARDIAN_RENDER_403 = True
GUARDIAN_TEMPLATE_403 = '403.html'
# 用来设置匿名用户的用户名,默认为AnonymousUser
ANONYMOUS_USER_NAME = 'AnonymousUser'# Guardian支持匿名用户的对象级权限,但是在我们的项目中,我们使用自定义用户模型,默认功能可能会失败。这可能导致guardian每次migrate之后尝试创建匿名用户的问题。将使用此设置指向的功能来获取要创建的对象。一旦获取,save方法将在该实例上被调用。默认值为guardian.ctypes.get_default_content_type
GUARDIAN_GET_INIT_ANONYMOUS_USER = guardian.ctypes.get_default_content_type# Guardian允许应用程序提供自定义函数以从对象和模型中检索内容类型。当类或类层次结构以ContentType非标准方式使用框架时,这是有用的。大多数应用程序不必更改此设置。
# 例如,当使用django-polymorphic适用于所有子模型的基本模型上的权限时,这是有用的。在这种情况下,自定义函数将返回ContentType多态模型的基类和ContentType非多态类的常规模型。默认为guardian.ctypes.get_default_content_type
GUARDIAN_GET_CONTENT_TYPE = guardian.ctypes.get_default_content_type
此节引用的文章来自知乎
使用Guardian最直观的特色就是在django-admin页面可以图形化地使用对象权限功能。 首先,在admin.py开头,从guardian添加两个导入:
from guardian.admin import GuardedModelAdminMixin
from guardian.shortcuts import get_objects_for_user, assign_perm
GuardedModelAdminMixin 是一个类,包含权限管理的功能,其中Mixin(混入)代表这个类不能单独作为ModelAdmin类使用,需要与其他的ModelAdmin类共同作为子类的父类,新的子类即可既有ModelAdmin的功能也有Guardian权限管理的功能。 但是,GuardedModelAdminMixin本身的功能还是欠缺了点,或者说它本来就是希望开发者自定义重写的。网上有大神将此类继承后重写,完善了其功能,我们将代码抄过来即可(可根据自己项目的特点修改其代码):
class GuardedMixin(GuardedModelAdminMixin):# app是否在主页面中显示,由该函数决定def has_module_permission(self, request):if super().has_module_permission(request):return Truereturn self.get_model_objs(request,'view').exists()# 在显示数据列表时候,哪些数据显示,哪些不显示,由该函数控制def get_queryset(self, request):if request.user.is_superuser:return super().get_queryset(request)data = self.get_model_objs(request)return data# 内部用来获取某个用户有权限访问的数据行def get_model_objs(self, request, action=None, klass=None):opts = self.optsactions = [action] if action else ['view', 'change', 'delete']klass = klass if klass else opts.modelmodel_name = klass._meta.model_namedata = get_objects_for_user(user=request.user, perms=[f'{perm}_{model_name}' for perm in actions],klass=klass, any_perm=True)if hasattr(request.user, 'teacher'):data = teacher.objects.filter(id=request.user.teacher.id) | datareturn data# 用来判断某个用户是否有某个数据行的权限def has_perm(self, request, obj, action):opts = self.optscodename = f'{action}_{opts.model_name}'if hasattr(request.user, 'teacher') and obj == request.user.teacher:return Trueif obj:return request.user.has_perm(f'{opts.app_label}.{codename}', obj)else:return self.get_model_objs(request, action).exists()# 是否有查看某个数据行的权限def has_view_permission(self, request, obj=None):return self.has_perm(request, obj, 'view')# 是否有修改某个数据行的权限def has_change_permission(self, request, obj=None):return self.has_perm(request, obj, 'change')# 是否有删除某个数据行的权限def has_delete_permission(self, request, obj=None):return self.has_perm(request, obj, 'delete')# 用户应该拥有他新增的数据行的所有权限def save_model(self, request, obj, form, change):result = super().save_model(request, obj, form, change)if not request.user.is_superuser and not change:opts = self.optsactions = ['view', 'add', 'change', 'delete'][assign_perm(f'{opts.app_label}.{action}_{opts.model_name}', request.user, obj) for action in actions]return result
当然,这些代码不是尽善尽美的,我们可根据自己项目的特点适当修改这些代码。 而后,将这个我们自己写的GuardedMixin类作为我们自己原来的模型的ModelAdmin类的父类之一:
class TeacherAdmin(GuardedMixin,ModelAdmin):# 详情表单页inlines = [Class_head_yearInline,FamilyMemberInline]fieldsets = [# ...]
admin.py 就编辑完成了,在admin管理页面的Teacher页面中就可以设置某个管理员针对某个teacher对象的权限了。
在图形界面具体的数据对象详情页,会有 对象权限 ,即可以设置某一用户针对此对象的权限设置。共有增删改查四项。
除了图形界面外,在视图等地方需要使用代码来操作数据模型。guardian 使用的用户和组和 django 的一样,只有权限划分中有部分区别
和 django 一样,可以对模型进行自定义权限
class CommonTask(models.Model):...class Meta:permissions = (('view_task', '查看任务权限'),('change_task', '更改任务权限'),('stop_task', '停止任务权限'),)
可以使用 guardina.shortcuts.assign_perm() 方法来分配对象权限
from django.contrib.auth.models import User, Group
from guardian.shortcuts import assign_perm# 获取数据对象
from models import CommonTask
obj = CommonTask.objects.get(pk=1)# 获取用户对象
user = User.objects.get(name='test_account')
# 获取用户组对象
group = Group.objects.get(name='test')# 确认用户是否对数据对象有权限
if not user.has_perm('view_task', obj):# 给用户处理数据对象的权限assign_perm('view_task', user, obj) # 注:这里的 user 和 obj 都可以是 QuerySet,即可以将多个数据对象权限赋给多个用户# 用户加入组
user.groups.add(group)
# 确认用户是否对数据对象有权限
if not user.has_perm('view_task', obj):# 给组处理数据对象的权限assign_perm('view_task', group, obj) # 同样能将多个数据对象赋权给多个组
需要注意的是,guardian.shortcuts.assign_perm(perm, user_or_group, obj=None) 是针对某一具体对象赋权,但并没有对整个 model 赋权,所以 has_perm('app.view_task') 时,会返回 False。另外 assign_perm 方法的第三个参数如果使用 None,则第一个参数格式必须为 app.perm_codename,此时为赋予 model 的权限而不是具体数据对象。
可以使用 guardian.shortcuts.remove_perm(perm, user_or_group=None, obj=None) 方法移除授权,需注意的是第二个参数不能是QuerySet而必须是instance,所以不能同时去除多个用户的权限。移除完同样需要刷新用户对象,保证缓存最新的权限。
from guardian.shortcuts import remove_perm# 移除用户/组的特定数据对象权限
remove_perm('view_task', user, obj)
# 移除用户/组的所有对象(即整个数据模型)权限
remove_perm('app.view_task', group)
验证是否有权限可以使用上例中的 user.has_perm() 方法。此外,guardian 还提供了一些其他的方法
# get_perms(user_or_group,obj) 方法可以根据用户或组以及对象来获取权限(has_perm 不能通过组验证)
from guardian.shortcuts import get_permsget_perms(group, obj) # 返回一个权限列表 ['view_task']
'permcodename' in get_perms(user_or_group, obj) # 返回一个布尔值
# get_objects_for_user(user, perms, klass=None, use_groups=True, any_perm=False)
from guardian.shortcuts import get_objects_for_user# 此方法可以根据用户和权限获取数据对象,获取的是一个 QuerySet
get_objects_for_user(user, 'app.view_task')
# 第二个参数可以写成列表,返回同时满足权限的数据对象
get_objects_for_user(user, ['app.view_task', 'app.stop_task'])
# 或使用 any_perm=True,满足列表任意权限条件即可
get_objects_for_user(user, ['app.view_task', 'app.stop_task'], any_perm=True)
# get_users_with_perms(obj, attach_perms=False, with_superusers=False, with_group_users=True, only_with_perms_in=None)
from guardian.shortcuts import get_users_with_perms# 可以根据数据对象的权限获取用户,返回 QuerySet
get_users_with_perms(obj)
# 默认返回的用户中没有 superuser,可以通过 with_superusers=True 让超级用户包含在内
get_users_with_perms(obj, with_superusers=True)
# 参数 attach_perms=True 可以返回一个字典,可以查看各用户拥有的具体权限
get_users_with_perms(obj, with_superusers=True, attach_perms=True)
# 如果仅想查看某个权限的用户,可以使用 only_with_perms_in 参数
get_users_with_perms(obj, with_superusers=True, only_with_perms_in=['view_task'])
# 默认用户加入了组后拥有组的权限,如果不想看继承自组的权限,则使用 with_group_users=False
get_users_with_perms(obj, with_superusers=True, with_group_users=False)# get_groups_with_perms 与 get_users_with_perms 方法类似,但是只接收2个参数 obj 和 attach_perms
guardian 对装饰器 permission_required 做了扩展,能够对对象权限进行校验。使用方式同 permission_required,但是增加了第二个参数,这个参数是一个元组,格式为(model, model_field, value)。通过第二个参数能够获取到一个数据对象 QuerySet (查询逻辑为 model.objects.get(model_field=value)),如果用户对这个对象有第一个参数的权限,则可以使用视图函数。
guardian 也提供了模板标签,方便在模板中对数据对象的权限进行校验
{% load guardian_tags %}
{% get_obj_perms request.user for task as 'task_perms' %}
{% if 'view_task' in task_perms %}
显示有权限查看的数据
{% endif %}
举个例子,用户 A 拥有数据对象 O 的权限,某天用户 A 被删除了,但是分配的权限还存在数据库中,这个就是孤儿对象许可。如果又有一天,创建了用户 A ,则新建立的用户就立刻拥有了数据对象 O 的权限,这是不对的!因此当删除 User 和相关 Object 时,一定要删除相关的所有 UserObjectPermission 和 GroupObjectPermission 对象。
解决办法有三个:
python manage.py clean_orphan_obj_perms
guardian.utils.clean_orphan_obj_perms()第二和第三个方法不是合理的生产环境的解决办法,真正想要解决,还是需要手动编码,最优雅的方式是加上 post_delete 信号给User或Object 对象,例如
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.db.models.signals import pre_delete
from guardian.models import UserObjectPermission
from guardian.models import GroupObjectPermission
from models import Task # 自定义的模型def remove_obj_perms_connected_with_user(sender, instance, **kwargs):filters = Q(content_type=ContentType.objects.get_for_model(instance), object_pk=instance.pk)UserObjectPermission.objects.filter(filters).delete()GroupObjectPermission.objects.filter(filters).delete()pre_delete.connect(remove_obj_perms_connected_with_user, sender=Task)
有时候,在前端不好确定的代码,可以通过后端生成,然后传递给前端使用。这里以翻页的手动代码进行举例:
from django.utils.safestring import mark_safe # 将后端字符串标记为安全,可以传递到前端做为前端的 HTML 代码使用# 前端的翻页页码page_list = []# 计算出当前页的前三后三页if page >= 5: # 需要首页ele = f'q}&order={order}&by={by}&page=1">首页 'page_list.append(ele)for i in range(page - 3, page + 4):if i <= 0 or i > all_page:continueif i == page:ele = f'q}&order={order}&by={by}&page={i}">{i}(current) 'else:ele = f'q}&order={order}&by={by}&page={i}">{i} 'page_list.append(ele)if page <= all_page - 4:ele = f'q}&order={order}&by={by}&page={all_page}">尾页 'page_list.append(ele)page_str = mark_safe(''.join(page_list)) # 使用 mark_safe() 方法将字符串变为前台代码return render(request,'pn_list.html', page_str)
如果不使用 mark_safe 标记,传到前端的数据会以字符串形式呈现。标记后则前端认为字符串是就是前端代码。
django 可以通过设置响应来设置 cookie 信息,使用HttpResponse.set_cookie() 方法来设置 Response 的 cookie:
def set_cookie(request):resp = HttpResponse()resp.set_cookie('username', 'zhangsan', expires=datetime.now()+timedelta(days=3))return resp
设置 cookie 时可以设置 max-age 或 expire,来确定 cookie 的寿命周期。如未指定,则表示永久有效。
通过 request 可以获取 cookie 信息
name = request.COOKIES.get('username')
在响应中发送删除 cookie 的数据,浏览器接收到了就可以删除响应的 cookie 信息
def del_cookie(request):resp = HttpResponse()resp.delete_cookie('username')return resp
需要注意的有
django 的 session 依赖于 cookie 技术,因为使用 session 会自动生成 session_id,用来确定 session 信息的归属,而 session_id 会记录在 cookie 中。
django 的 session 数据默认存储在数据库的 django_session 表中,通过 cookie 中的 sessionid 获取相应
django中默认启用了session,如果要自定义添加 session,则需要在 settings.py 中的 INSTALLED_APP 中添加,并在 MIDDLEWARE 中添加 session 的中间件。
在 django 中,使用 request.session[key] = value 能够记录 session 的信息,同时会自动生成验证 session_id 返回到用户浏览器 cookie 中,并且将 session_id 和验证字符串都存储到数据库的 django_session 表中,以便下次使用时自动验证。
如果是已经记录 session 信息的用户再次访问,则请求信息的 cookie 中会有 session_id 。django 会从数据库中查询是否存在此 session_id,如果存在则能够获取设置的 session[key] 的 value ,如果不存在则不能够获取。所以使用 request.session.get(key) 来获取设置数据就能够自动检查 session 信息。
可以使用 request.session.set_expiry(sec) 来设置 session 的超时时间,sec 单位为秒。
可以使用 del request.session[key] 的方式删除指定的 session 信息,使用 request.session.clear() 方法可以清除 session 信息,即进行注销。
token 是身份令牌,表示一个有权限的访问用户成功登录。以后再访问时如果有 token (且 token 有效)就不再进行登录验证。token 的信息一般都是自定义的,较简单的方式是在成功登录后,产生一个 uuid 到 cookie 中,这个 uuid 就是 token。
def add_token(request):# 生成tokentoken = uuid.uuid4().hexresp = HttpResponse('增加了 token 到 cookie 中')resp.set_cookie('token', token, max_age=60*60*24)request.session['token'] = tokenreturn resp
token 和 sessionid 都是用来确定访问用户的,其最大的区别在于 session 会占用服务器资源,而 token 会交给客户端,通过请求头来维护,节省了服务器资源。session 的主要目的是给无状态的 HTTP 协议添加状态保持,通常在浏览器作为客户端的情况下使用;而 Token 主要目的是鉴权,所以更多用在第三方 API。所以目前基于Token的鉴权机制几乎已经成了前后端分离架构或者对外提供API访问的鉴权标准,得到广泛使用。
对于 JSON Web Token(JWT) 的方案,详细的可以看这里
Django+JWT实现Token认证
在 api 设计中,使用 django-RESTful 是个比较方便的方案,且 django-RESTful 框架包含了方便的 token 验证方案。
django 项目中也可以使用 ajax 技术
前端和其他 web 框架一样,需要注意的是,django 接收 POST 请求时,需要 csrf_token 进行验证,而在 ajax 中获取 csrf_token 比较麻烦。所以通常会在后端免除 csrf_token 验证。
{% extends 'layout.html' %}
{% block content %}任务管理
{% endblock %}
{% block js %}
{% endblock %}
{% extends 'layout.html' %}
{% block content %}任务管理
{% endblock %}
{% block js %}
{% endblock %}
后端部分只要将响应请求 url 绑定视图函数,则可以在视图函数中进行处理
def task_ajax(request):"""测试ajax"""return HttpResponse('成功')
可以使用 json 的 dumps 方法将字典转为 json 并返回
import jsondef task_ajax(request):res_dict = {'res':'ok', 'data':{'k1': 'v1', 'k2': 'v2'}}res_dict = json.dumps(res_dict)return HttpResponse(res_dict)
也可以直接使用 JsonResponse 返回数据
from django.http import JsonResponsedef task_ajax(request):res_dict = {'res':'ok', 'data':{'k1': 'v1', 'k2': 'v2'}}return JsonResponse(res_dict)
前端发送 post 请求时如果不加载 csrf_token,则后端需要禁用 csrf 校验检查
from django.views.decorators.csrf import csrf_exempt@csrf_exempt
def task_ajax(request):return HttpResponse('成功')
或在注册路由时注明
from django.views.decorators.csrf import csrf_exempturlpatterns = [path('goods/', csrf_exempt(views.goods), name='goods'),
]
或在settings.py 的中间件中取消 csrf 中间件(不推荐)。
Ajax 结合 ModelForm 在前端实际上几乎没有改变,要注意的也就是按钮绑定 Ajax 函数和不用 csrf_token。
在后端,ModelForm 其实接收到的数据也是 form 的字典格式,包含校验、保存等处理方式没有变化,只是在返回值时有了变化。
因为 Ajax 是接收处理数据,所以后端返回 重定位 信息是无法跳转的。如果希望跳转,则需要返回一个 json ,ajax 收到了这个特定的 json 数据后使用 js 进行页面跳转。
另外返回 ModelForm 错误信息时,form.字段.errors 获取的是一个字典,可以整理成 json 格式返回给 ajax 处理。
前端会将文件以 post 请求发送至后端,后端可以使用 request.FILES 来接收。需要注意的是 form 需要 enctype 属性。
file_obj = request.FILES.get('file') # 获取上传文件的对象
filename = request.POST.get('filename', file_obj.name) # 获取设置的文件名,如果没有则为原文件名。
with open(filename, mode='wb') as f:for chunk in file_obj.chunks(): # 将上传文件分块读取f.write(chunk)f.flush() # 文件写入完成,冲刷缓冲区
ajax 上传文件和发送 json 的不同在于,发送的数据是一个 FormData 对象,创建这个对象时可以将表单 form 的 dom 对象传入。
$("#upload").click(function () {var formData = new FormData($('#uploadForm')[0]);// 或使用 FormDate 对象添加文件对象的方式// var formData = new FormData();// formDate.append('file', this.file[0]); //这里的 this 指向上传文件的 input 标签的dom对象// formDate.append('key', value); // 可以添加其他的数据$.ajax({type: 'post',url: "https://****:****/fileUpload", //上传文件的请求路径必须是绝对路劲data: formData,cache: false,processData: false,contentType: false,}).success(function (data) {console.log(data);alert("上传成功"+data);filename=data;}).error(function () {alert("上传失败");});});
后端接收时,注意接收字段是前端定义的 name ,文件从 requests.FILES 里获取
uid = requests.POST.get('uid') # 获取数据
file = requests.FILES.get('file') # 获取文件
# files = requests.FILES.getlist('files') # 获取多个文件
使用 Form 时可以定义文件字段 FileField ,定义后就能够上传文件了。
class UpForm(forms.Form):name = forms.CharField(label='姓名')age = forms.IntegerField(label='年龄')img = forms.FileField(label='头像')def upload_form(request):form = UpForm(data=request.POST, files=request.FILES)if form.is_valid():print(form.cleaned_data)# 文件在 form.cleaned_data 的相应字段(img)内img = form.cleaned_data.get('img')file_path = os.path.join('app01', 'static', 'img', img.name) # 拼接文件路径with open(file_path,'wb') as f: # 写入文件for chunk in img.chunks():f.write(chunk)else:return render(request, 'upload.html', {'form': form})
通常使用的静态资源都在 static 文件夹内,如果想要用户上传文件到一个特定文件夹,例如项目根目录下的 media 文件夹,则需要在 urls.py 中进行设置启用。首先引入一些资源,再在 urls.py 文件的 urlpatterns 字段添加:
from django.views.static import serve
from django.urls import path, re_path
from django.conf import settingsre_path(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
然后在 settings.py 中进行配置:
import osMEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 项目根目录下的 media 目录
MEDIA_URL = '/media/'
此时上传文件保存的路径可以写为
media_file_path = os.path.join('media', image_obj.name)
这样将用户上传的文件放在 media 目录下,也可以通过 /media/文件名 的 url 来访问,例如 http://127.0.0.1:8000/media/001.png
ModelForm 组件可以自动上传文件,并存储保存路径到数据库,不用再写相应保存的代码,且能够自动进行重名处理。只是需要设置了上传保存目录。
需注意的是,保存到数据库中的文件路径也是 media 下的相对路径,并且不包含 media ,使用时候注意添加 media。
# models.pyclass City(models.Model):"""城市"""name = models.CharField(verbose_name='名称', max_length=32)count = models.IntegerField(verbose_name='人口')# 本质上数据库存储的是文件路径,也是CharField,可以自动保存数据# upload_to 指的就是上传文件到哪个目录,是 media 目录下的相对路径img = models.FileField(verbose_name='logo', max_length=128, upload_to='city/')
# views.pyclass UpModelForm(forms.ModelForm)class Meta:model = models.Cityfields = '__all__'def upload_modal_form(request):form = UpModelForm(data=request.POST, files=request.FIELS)if form.is_valid():# 保存文件,保存 form 字段信息和文件路径并写入数据库form.save()return HttpResponse('成功')
django 多人协作开发时,每人开发不同的 app 也可能会遇到一些问题:对于相同名称的模板或静态资源的使用问题。因为 django 搜索静态资源和模板是安装 app 注册的顺序,在各 app 目录下查找模板或静态资源文件。如果有相同名称的模板或静态资源文件,后注册的 app 就会使用先注册 app 的同名文件了。
所以对于模板文件来说,在各自 app 下的 templates 文件夹下建立 app 名称的文件夹,再建立模板文件,使得各模板文件引用时会加入各自 app 的名称,这样就不会出现引用路径相同的问题了。
对于静态资源文件,因为涉及公共静态资源,所以需要注册静态资源路径。
# settings.pySTATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static"), # 主静态文件目录os.path.join(BASE_DIR, "main", "static"), # main app 静态文件目录os.path.join(BASE_DIR, "login", "static"), # login app 静态文件目录
]
然后可以在每个APP下的static下建立以APP名相同的文件夹,比如在 login/static/login/ 放入样式JS CSS等
调用时使用 static 结构,加上 app 名称
{% static 'main/img/firefox-logo-small.jpg' %}{% static 'login/img/name.png' %}
另外对于静态文件的打包整合可以参考这篇文章:
解决django多个app下的静态文件无法访问问题