Django 自带 user 字段扩展及头像上传
django 及 rest_framework 笔记链接如下:
django 入门笔记:环境及项目搭建
django 入门笔记:数据模型
django 入门笔记:视图及模版
django 入门笔记:Admin 管理系统及表单
django 入门笔记:通用视图类重构视图
django_rest_framework 入门笔记:Serializer
django_rest_framework 入门笔记:视图函数重构
django_rest_framework 入门笔记:分页,多条件筛选及权限认证设置
django 自带 user 字段扩展及头像上传
一. 扩展 django 自带的 User Model 字段
在实际项目中,django 自带的 user model 的字段很多时候不能够满足我们的需求,可能我们需要在 user 中加入商城积分等字段,这就需要通过扩展的 user model 字段来实现。目前扩展 user 字段我找到的有 4 种方式。
1. 通过新建 model 然后指定 OneToOne 的 user 字段(官方推荐)
class UserProfile(models.Model):
user = models.OneToOne(User, related_name="profile", on_delete=models.CASCADE)
phone_num = models.CharField(max_length=20, unique=True, blank=True)
# 当生成 user 的时候自动生成 UserProfile,参考之前的 token 生成
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile(sender, instance=None, created=False, **kwargs):
if created:
profile, created = UserProfile.objects.get_or_create(user=instance)
接着需要在 admin 做些设置
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class ProfileInline(admin.StackInlin):
model = UserProfile
can_delete = False
verbose_name_plural = "profile"
class UserAdmin(UserAdmin):
inlines = (ProfileInline,)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
然后通过迁移数据库,django 会同时生成 user 表和 user_profile 表
这个方法虽然是官方推荐的,但是个人还是觉得很麻烦,获取 profile 的字段值还需要通过 user 表的外键获取 profile 实例,然后再获取字段值,再考虑到 django 的通过外键生成反向字段也需要做些处理,如果说我们只需要额外添加一个字段,需要做那么多工作,我是拒绝的。
2. 通过继承 AbstractUser 来实现
首先需要在 models.py 文件中创建自己的 user model
from django.contrib.auth.models import AbstractUser
from django.db import models
# 有个坑,扩展字段后密码会明文,当我们提交的时候需要进行设置,在后面的代码中会提到
class User(AbstractUser):
phone_num = models.CharField(max_length=20, unique=True, blank=True)
然后我们需要在 settings.py 文件中设置 AUTH_USER_MODEL 的值
# settings.py
AUTH_USER_MODEL = '[your_app_name].[your_user_model_name]' # 例如我这边设置的值为 'expert.User'
在 admin 中,我们按照之前的方法照常设置就行
这种方法,需要在我们做数据库迁移之前,先定义好我们自己的 User Model 然后将 settings.py 文件中的 AUTH_USER_MODEL 替换后再进行数据库迁移,需要告诉 django 系统,这个项目我要用自己的 User 了,不用你的。这种方法适合只需要扩展 User Model 中的字段,会继承之前 AbstractUser 的全部字段,系统自带的 User 也是通过继承 AbstractUser 来实现的
# 系统的 User 源码,AbstractUser 的源码也不是很多,可以自行查看
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username, password and email are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
如果说系统自带的 user 很多字段都不符合项目的需要,基本上需要完全替换,那目前提到的两种方法都不适合,需要通过第三种方法来替换
3. 通过 AbstractBaseUser 替换 User
这种方法会比较麻烦,说实话前两种方法已经可以解决我们的问题,不到迫不得已个人觉得没必要用这种方法
# AbstractUser 就是通过继承 AbstractBaseUser 来实现的,
# 我们写这部分时候可以通过修改 AbstractUser 的源码实现
from django.db import models
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
# 假如我们不需要原来 User 中的 username 和 is_staff 字段,直接通过 email 作为用户名
class User(AbstractUser, PermissionsMixin):
email = models.EmailField(_('邮箱'), unique=True)
phone = models.CharField(_('手机号'), unique=True, blank=True)
first_name = models.CharField(_('名'), max_length=30, blank=True)
last_name = models.CharField(_('姓'), max_length=30, blank=True)
date_joined = models.DateTimeField(_('创建时间'), auto_now_add=True)
is_active = models.BooleanField(_('激活状态'), default=True)
picture = models.ImageField(upload_to='pictures/', null=True, blank=True)
objects = UserManager()
# 定义登录的字段
USERNAME_FIELD = 'email'
# 通过 createsuperuser 命令行时候必填字段
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_full_name(self):
full_name = '{} {}'.format(self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
return self.first_name
def email_to_user(self):
# 具体发送邮件的逻辑可以自行查找实现,邮箱验证的时候需要用到
pass
def message_to_user(self):
# 具体发送短信逻辑可以自行查找实现
pass
在 UserManager 中,需要通过 create_user
和 create_superuser
的方法来创建用户和超级用户,所以必须重写这些方法才可以,这里根据系统的 UserManager 源码进行修改
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('必须设置邮箱')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(email, password, **extra_fields)
替换 User 中的 UserManager,然后还是需要在 settings.py 文件中设置 AUTH_USER_MODEL 为自定义的 User Model
通过 2 , 3 方法扩展 User Model 后,当别的表和 User 表有链表结构的时候,最好通过 settings.AUTH_USER_MODEL 进行导入,而不是通过 User 直接导入,还有需要注意的是,一定要先把扩展的 User Model 先进行数据迁移,否则会产生不必要的麻烦。而官方推荐的则可以不用考虑这个问题
4. 通过元类进行扩展
在网上还找到一种通过元类来扩展字段的方法,这边附上链接 通过元类扩展 User 字段 不多讲述。
5. User 字段扩展实践
# models.py
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
class User(AbstractUser):
phone_num = models.CharField(max_length=20, blank=True, unique=True)
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_token(sender, instance, created, **kwargs):
if created:
token, created = Token.objects.get_or_create(user=instance)
# serializers.py
from rest_framework import serializers
from expert_user.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
# views.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from expert_user.serializers import UserSerializer
from expert_user.models import User
class RegisterView(APIView):
def post(self, request, format=None):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
# 将密码转为密文,前面留下的坑,这一步必须要有,否则登录的时候会出问题,
# 不管是哪种方法进行扩展都需要
user.set_password(user.password)
User.objects.filter(id=user.id).update(password=user.password)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from expert_user import views
app_name = "user"
urlpatterns = [
url(r'^register/$', views.RegisterView.as_view(), name="register"),
]
urlpatterns = format_suffix_patterns(urlpatterns)
扩展后的返回字段如下图
二. 通过 ImageField 上传头像
上传头像之前,需要先安装 Pillow 模块 pip install pillow
,然后在我们的 settings.py 文件中加入如下代码
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace("\\", "/")
接着在 model 类中加入 ImageField 属性的字段,例如 avatar,再做数据库迁移,如果没有安装 pillow 模块,数据库迁移是会被打断的,然后提示你需要安装 pillow
class CustomUser(models.Model):
# ...
avatar = models.ImageField(upload_to="/avatar/", blank=True, null=True)
数据库迁移后修改 serializers 文件,添加 UploadAvatarSerializer,我们需要客户端回传的数据包括图片,以及图片的后缀名,如果自己去获取后缀名,通过 pillow 转换再取后缀可能会比较麻烦,所以直接让前端上传,所以我们定义的 serializer 如下:
class UploadAvatarSerializer(serializers.ModelSerializer):
avatar = serializers.ImageField(max_length=None, allow_empty_file=False, use_url=True)
suffix = serializers.CharField(allow_blank=False)
class Meta:
model = CustomUser
fields = ['avatar', 'suffix']
接着来完成我们的 view 视图
class AvatarUploadView(APIView):
throttle_classes = ()
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
parser_classes = (parsers.FormParser, parsers.MultiPartParser,
parsers.JSONParser, parsers.FileUploadParser,)
renderer_classes = (renderers.JSONRenderer,)
def post(self, request, format='json'):
serializer = UploadAvatarSerializer(data=request.data)
if serializer.is_valid():
# 时间戳作为头像的名称
name = str(time.time()).split('.')[0].strip()
# 上传头像的后缀
suffix = request.data['suffix'].split(r'"')[1].strip()
# 图像名称
avatar_name = "avatar/{}/{}.{}".format(request.user.username, name, suffix)
# pillow 打开图像,保存副本
avatar = Image.open(request.data['avatar'])
# 判断文件的父文件是否存在,不存在创建
if not os.path.exists('media/avatar/' + request.user.username):
os.makedirs('media/avatar/' + request.user.username)
# 文件保存的具体路径
avatar_file_path = os.path.join(settings.MEDIA_ROOT, avatar_name).replace("\\", "/")
avatar.save(avatar_file_path)
# 将保存的路径更新到数据库
CustomUser.objects.filter(id=request.user.id).update(avatar=avatar_file_path)
# 返回结果
return Response({'code': '200', "avatar": avatar_file_path},
status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
注释能看懂的小伙伴就可以跳过这部分啦,没完全懂的小伙伴继续跟上节奏~首先我们需要添加权限,需要用户登录后才可以进行修改或者上传头像,所以指定 permission_classes 为 permissions.IsAuthenticated,可以看下 IsAuthenticated 的源码
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
# 当用户登录了而且获取认证才返回 True
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
认证由于用的是 Android 端,所以直接使用系统的 TokenAuthentication,parser_classes 和 renderer_classes 基本每个 view 都类似,但是涉及到图片上传,一定要添加 FileUploadParser
用户端上传图片后,我们直接保留到项目下。首先需要通过 is_valid()
判断上传的数据是否有效,有效则通过 pillow 的 Image 模块的 open 打开上传图片,否则返回 HTTP_400_BAD_REQUEST
打开上传的图片后会产生一个副本,这个副本用来保存到项目下,也就是用户的头像文件。那么就需要设计头像的文件名,用来保存头像的副本文件。至于如何设计每个人都不同,我的思路是每个用户的头像都保存到各自以用户名为名的文件夹下,为了防止前一张头像被覆盖,所以用时间戳作为图像名,然后加上客户端回传的后缀名就可以生成我们的图片文件了。
通过 request.user.username
获取用户名,time.time()
来获取时间戳,通过 ‘.’ 切割时间戳,然后把毫秒数省去,通过 split('.')[0]
来获取,那么我们保存到项目下的文件名可以通过如下方式拼接生成
"avatar/{}/{}.{}".format(request.user.username, name, suffix)
在保存副本之前,需要通过 os.path.exists
判断该文件的父文件夹是否存在,如果不存在的话就需要通过手动创建,创建完父文件夹后,就可以通过 os.path.join()
来拼接文件的完整路径了。
定义好文件路径后,接着通过 save()
函数保存图片副本,还有很重要的一步就是需要把文件路径更新到数据库。如果是在本地测试,数据库显示的文件地址会是你本地项目的地址,但是当你从客户端发出请求后,收到的会是服务器地址,没毛病,不要以为自己保存的路径错了。例如我保存的地址为 C:/ProjectsFloder/PythonProjects/expert/expert_project/media/avatar/kuky_xs/1525787433.jpg
实际在客户端返回的地址为
http://192.168.0.103:8080/media/C%3A/ProjectsFloder/PythonProjects/expert/expert_project/media/avatar/kuky_xs/1525787433.jpg
内容不多,但是一定要细心注意文件夹的名称,一定要细心注意文件夹的名称,一定要细心注意文件夹的名称,重要的事说三遍,反正我在文件名上卡了好久,最后发现少加了一层,还是不够仔细啊。
作者:Kuky_xs
链接:https://www.jianshu.com/p/d45a687c3f41
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
Django 自带 user 字段扩展及头像上传的更多相关文章
- Django实现注册页面_头像上传
Django实现注册页面_头像上传 Django实现注册页面_头像上传 1.urls.py 配置路由 from django.conf.urls import url from django.cont ...
- [Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能
很久没有更新博客了,再不写点东西都烂了. 这次更新一个小内容,是两个插件的组合使用,实现头像上传功能. 业务需求: 头像上传功能,要对上传的文件进行剪切,且保证头像到服务器时必须是正方形的. 优化&l ...
- 强大的flash头像上传插件(支持旋转、拖拽、剪裁、生成缩略图等)
今天介绍的这款flash上传头像功能非常强大,支持php,asp,jsp,asp.net 调用 头像剪裁,预览组件插件. 本组件需要安装Flash Player后才可使用,请从http://dl.pc ...
- 【Bootstrap-插件使用】Jcrop+fileinput组合实现头像上传功能
作者:Dreawer链接:https://zhuanlan.zhihu.com/p/24465742来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者:梦游的龙猫(转 ...
- Canvas处理头像上传
未分类 最近社区系统需要支持移动端,其中涉及到用户头像上传,头像有大中小三种尺寸,在PC端,社区用Flash来处理头像编辑和生成,但该Flash控件的界面不友好而且移动端对Flash的支持不好,考虑到 ...
- Html5实现头像上传和编辑,保存为Base64的图片过程
一.Html5实现头像上传和编辑 插件地址: html5手机端裁剪图片上传头像代码 本地项目引入注意事项: 1.将html的js搬到外面的js文件中,便于管理 2.图片样式在html都是在页面写死,需 ...
- php头像上传预览
php头像上传带预览: 说道上传图片,大家并不陌生,不过,在以后开发的项目中,可能并不会让你使用提交刷新页面式的上传图片,比如上传头像,按照常理,肯定是在相册选择照片之后,确认上传,而肯定不会通过fo ...
- spring--mvc添加用户及用户头像上传
spring--mvc添加用户及用户头像上传 添加用户步骤: 1.用ajax获取省份信息 2.添加用户 代码:register.jsp <meta http-equiv="Conten ...
- Yii2.0 集成使用富头像上传编辑器
在开发过程中,我们会用到头像上传的功能.这里给大家推荐一款比较流行的头像上传组件,FullAvatarEditor 2.3(富头像上传编辑器). 实际效果如图所示: 1.下载组件,下载地址:http: ...
随机推荐
- 第05课:GDB常用命令详解(中)
本科核心内容: info和thread命令 next.step.util.finish和return命令 5.1info和thread命令 在前面使用info break命令查看当前断点时介绍过,in ...
- Web Service Demo
有了Web Service的一些基础,具体如何实现,通过亲自写一个Demo来理解一下. 1.创建一个空的Web项目 2.在Web项目下ADD一个Web Service 3.在Web service中写 ...
- pikachu-xss和csrf
简介 XSS是一种发生在Web前端的漏洞,所以其危害的对象也主要是前端用户 XSS漏洞可以用来进行钓鱼攻击.前端js挖矿.盗取用户cookie,甚至对主机进行远程控制 攻击流程 假设存在漏洞的是一个论 ...
- 漫谈 MyCat 配置系统
上篇文章<MyCat 启蒙:分布式系统的数据库架构演变>中,我们通过一个项目从零到百万级访问的变化,展示了这个过程中的数据层架构变化.其中说到了数据层架构变化所带来的三个问题: 读写数据源 ...
- linux命令历史
本人qq群也有许多的技术文档,希望可以为你提供一些帮助(非技术的勿加). QQ群: 281442983 (点击链接加入群:http://jq.qq.com/?_wv=1027&k=29Lo ...
- Flask【第10篇】:自定义Form组件
自定义Form组件 一.wtforms源码流程 1.实例化流程分析 1 # 源码流程 2 1. 执行type的 __call__ 方法,读取字段到静态字段 cls._unbound_fields 中: ...
- ESP8266--TCP Server
所谓server,可以简单理解为提供服务,提供数据的一个地方 ESP8266上建立一个server是比较简单的,不过是属于局域网内的server,因为真正意义上的server并不是这样的,大伙了解一个 ...
- 【NOIP2016提高A组五校联考2】tree
题目 给一棵n 个结点的有根树,结点由1 到n 标号,根结点的标号为1.每个结点上有一个物品,第i 个结点上的物品价值为vi. 你需要从所有结点中选出若干个结点,使得对于任意一个被选中的结点,其到根的 ...
- electron打包成.exe后限制只启动一个应用
注意:这是2.x的文档 const {app} = require('electron') let myWindow = null const shouldQuit = app.makeSingleI ...
- PHP培训教程 php几个不起眼儿的小技巧
说是不起眼儿的小技巧,其实应该说是不常用的常规应用吧.很多事情就是这样,知道是一马事儿,会用是一马事儿,精习又是另外一马事儿.而成为高手更是需要扎实的基本功. str_repeat 重复输出字符串就靠 ...