欢迎来到我的博客!

django博客开发:添加用户资料页面和头像上传功能

Django Aaron 1 年,9 月 5432 11评论

添加了用户登录、注册系统后,还需要一些其他的基本功能,比如:用户资料(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 }},区别就是前者颜值更高。laugh,来对比一下

关于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。

评论 5人参与 | 11条评论