添加了用户登录、注册系统后,还需要一些其他的基本功能,比如:用户资料(account profile)和头像上传。
准备工作
1. 操作环境:python3.4 、 django1.10
2. 安装django-imagekit(项目地址:https://github.com/matthewwithanm/django-imagekit),用来处理用户上传的头像图片,生成指定尺寸的缩略图(thumbnail)。django-imagekit需要用pillow来处理图像,因此也需要安装pillow。
安装django-crispy-forms(项目地址:https://github.com/django-crispy-forms/django-crispy-forms),一个模板标签就可以把表单变成bootstrap样式的表单,简直是神器
pip install pillow
pip install django-imagekit
pip install django-crispy-forms
3. 在settings.py中,把imagekit、crispy_forms加入到INSTALLED_APPS中,同时设置CRISPY_TEMPLATE_PACK, 表明crispy_forms生成表单的样式,可取的值有'bootstrap'、'bootstrap3'、'bootstrap4',分别代表bootstrap2、3、4。
INSTALLED_APPS = [
'crispy_forms',
'imagekit',
]
# 使用bootstrap3的样式,前端需要引入相应的css
CRISPY_TEMPLATE_PACK = 'bootstrap3'
修改Model用户模型
这里我使用的继承AbstractUser的方式扩展的用户模型,修改之前是这样的
blog/models.py
class User(AbstractUser):
nickname = models.CharField(max_length=30, blank=True, null=True, verbose_name='昵称')
qq = models.CharField(max_length=20, blank=True, null=True, verbose_name='QQ号码')
url = models.URLField(max_length=100, blank=True, null=True, verbose_name='个人网页地址')
avatar = models.ImageField(upload_to='avatar',default='avatar/default.png',
verbose_name='头像')
def __str__(self):
return self.username
avatar就是用户头像字段,把ImageField替换为imagekit的ProcessedImageField,ProcessedImageField和django的ImageFIeld相似,区别是前者可以处理图片,生成指定大小的缩略图,更多详细的说明请查看imagekit官方文档。
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
class User(AbstractUser):
nickname = models.CharField(max_length=30, blank=True, null=True, verbose_name='昵称')
qq = models.CharField(max_length=20, blank=True, null=True, verbose_name='QQ号码')
url = models.URLField(max_length=100, blank=True, null=True, verbose_name='个人网页地址')
avatar = ProcessedImageField(upload_to='avatar',
default='avatar/default.png',
verbose_name='头像',
#图片将处理成85x85的尺寸
processors=[ResizeToFill(85,85)],)
创建ModelForm
django提供两种form class,Form和ModelForm,ModelForm生成的表单,可以用来创建(create)或者更新(update)数据库模型。
blog/forms.py
from django.forms import ModelForm
from .models import User
class UserDetailForm(ModelForm):
class Meta:
# 关联的数据库模型,这里是用户模型
model = User
# 前端显示、可以修改的字段
fields = ('nickname', 'qq', 'url', 'avatar')
Meta内部类里的fields规定了前端表单可以显示和修改的字段,如何不写fields的话,显示已经可修改的内容会跟admin后台的用户页面一模一样,是非常不安全的。
添加View视图函数
blog/views.py
from .forms import UserDetailForm
from django.contrib.auth.decorators import login_required
# 使用login_required装饰器,用户只有登录了才能访问其用户资料
@login_required
def account_profile(request):
messages = []
# post请求 表明是在修改用户资料
if request.method == 'POST':
# 使用getattr可以获得一个querydict,里面包含提交的内容
#request_dic = getattr(request, 'POST')
#print(request_dic)
#print(request.FILES)
form = UserDetailForm(request.POST, request.FILES, instance=request.user)
if form.is_valid():
form.save()
messages.append('资料修改成功!')
# 如果是get请求,则使用user生成表单
form = UserDetailForm(instance=request.user)
return render(request, 'account/user_detail.html', context={'form':form,
'messages':messages,})
添加url
blog/urls.py
from django.conf.urls import url
from . import views
app_name = 'blog'
urlpatterns = [
url(r'^accounts/profile/$', views.account_profile, name='account_profile'),
]
添加模板
templates/account/user_detail.html
{% extends 'account/base.html' %}
{% load crispy_forms_tags %}
{% block account_profile %}
<div class="local-header"><h4>个人资料</h4></div>
<form class="profile" method="post" enctype="multipart/form-data"
action="{% url 'blog:account_profile' %}">
{% csrf_token %}
{{ form|crispy }}
<!--用户头像-->
<div class="avatar">
<img class="img-rounded" src="{{ request.user.avatar.url }}">
</div>
<button class="primaryAction" type="submit">更新资料</button>
</form>
{% endblock account_profile %}
在写<form>标签时,需要注意属性enctype="multipart/form-data", enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码, 在使用包含文件上传控件的表单时,必须使用值"multipart/form-data",否则不能上传文件。
之前安装的django-crispy-forms终于派上用场了,{% load crispy_forms_tags %},加载crispy-forms定义的模板标签,在处理表单字段时只需要使用{{ form|crispy }}即可轻松生成bootstrap样式的表单。
{{ form|crispy }}的用法类似于django自带的{{ form.as_p }},区别就是前者颜值更高。,来对比一下
关于crispy-forms的更多高端大气的用法,请移步至crispy-forms官方文档。
重写User模型的save()方法
完成上面的功能之后,我发现一个问题,就是所有上传的图片都放在用一个路径下,这样非常不便于管理,最好是为每个用户创建一个文件夹,用来存储头像图片。解决方法是重写User的save()方法,在保存的时候将图片保存到包含用户名(或者昵称)的路径下。
blog/models.py
class User(AbstractUser):
··· ··· ···
··· ··· ···
def save(self, *args, **kwargs):
# 当用户更改头像的时候,avatar.name = '文件名'
# 其他情况下avatar.name = 'upload_to/文件名'
if len(self.avatar.name.split('/')) == 1:
#print('before:%s' % self.avatar.name)
# 用户上传图片时,将avatar.name改为 用户名/文件名
self.avatar.name = self.username + '/' + self.avatar.name
super(User, self).save()
# 调用父类的save()方法后,avatar.name就变成了'upload_to/用户名/文件名'
#print('after:%s' % self.avatar.name)
#print('avatar_path: %s' % self.avatar.path)
这里需要在调用父类的save()方法之前,需要判断用户是否有上传图片,上传图片的时候 avatar.name 会是'文件名.jpg' 或者 '文件名.png' 的形式。如果没有这一步判断的话,每次调用save的时候会在avatar.name前加上username。