Django搭建个人博客:上传头像图片

4648阅读 · 31评论 · 2018/12/06发布   前往评论

到目前为止我们的博客处理的都是文字。现代互联网早就进入了“读图”时代,图片的维护、展示也就相当重要。

上一章中预留了avatar字段,用来保存用户上传的头像,现在我们来实现这个功能。

必要的设置

图片属于一种媒体文件,它与静态文件类似,需要设置一个统一的目录,便于集中存储和访问。

这类需要框架统一设置的参数,当然应该在/my_blog/settings.py中。在底部加上:

/my_blog/settings.py

...

# 媒体文件地址
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

MEDIA_ROOTMEDIA_URL是用户上传文件保存、访问的位置:

  • 在前面的Profile中我们设置了upload_to参数。有了这个参数,文件上传后将自动保存到项目根目录的media文件夹中。 os.path.join(MEDIA_ROOT, 'media/')指定了media文件夹的位置。

  • MEDIA_URL代表用户通过URL来访问这个本地地址的URL。设置好这个参数后,用户就可以通过解析url,很方便的获取文件的地址。这样做的好处是避免的硬编码,让代码更容易维护。

Django框架擅长的是对逻辑的处理,而对图片这类文件的处理则非常的耗时。因此在实际的生产环境中(即产品上线之后),通常是有专门的Web服务器来处理文件的访问。

而在开发阶段我们不会在意效率问题,所以Django也提供了方法,让开发服务器能够处理图片。

/my_blog/urls.py添加下面的语句:

/my_blog/urls.py

...
# 新引入的模块
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    ...
]

#添加这行
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

这样就为以后上传的图片配置好了URL路径。

编写MTV

回顾一下,avatar的字段已经在上一章写好了:

/userprofile/models.py

...
class Profile(models.Model):
    ...
    avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True)
    ...

upload_to指定了图片上传的位置,即/media/avatar/%Y%m%d/%Y%m%d是日期格式化的写法,会最终格式化为系统时间。比如说图片上传是2018年12月5日,则图片会保存在/media/avatar/2018205/中。

注意ImageField字段不会存储图片本身,而仅仅保存图片的地址。

记得用pip指令安装Pillow。

表单类在前面也写好了,不用修改:

/userprofile/forms.py

...
class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('phone', 'avatar', 'bio')

接着需要修改视图,使之能够对图片进行处理:

/userprofile/views.py

...
@login_required(login_url='/userprofile/login/')
def profile_edit(request, id):
    ...

    # 修改本行
    # 上传的文件保存在 request.FILES 中,通过参数传递给表单类
    profile_form = ProfileForm(request.POST, request.FILES)

    if profile_form.is_valid():
        ...

        # 添加在 profile.bio = profile_cd['bio'] 后面
        # 如果 request.FILES 存在文件,则保存
        if 'avatar' in request.FILES:
            profile.avatar = profile_cd["avatar"]

        ...
  • 表单上传的文件对象存储在类字典对象request.FILES中,因此需要修改表单类的参数,将它一并传递进去。
  • 如果request.FILES中存在键为avatar的元素,则将其赋值给profile.avatar(注意保存的是图片地址);否则不进行处理。

修改模板文件,添加代码显示、处理用户的头像:

/templates/userprofile/edit.html

...

{% if profile.avatar %}
    <div class="col-md-4">头像</div>
    <img src="{{ profile.avatar.url }}" style="max-width: 20%; border-radius: 15%;" class="col-md-4">
{% else %}
    <h5 class="col-md-4">暂无头像</h5>
{% endif %}
<br>
<br>
<form ... enctype="multipart/form-data">{% csrf_token %}
    <!-- avatar -->
    <div class="form-group">
        <label for="avatar">上传头像</label>
        <input type="file" class="form-control-file" name="avatar" id="avatar">
    </div>

    ...
  • 模板语法{% if ... %}判断用户是否上传头像。
  • <img>标签用于显示图片内容;在style属性中规定了图片的最大宽度并带有一点的圆角。
  • 注意,表单必须设置enctype="multipart/form-data"属性,才能够正确上传图片等文件。
  • 添加<input type="file" ...>标签用于上传图片。

启动服务器,刷新用户信息页面:

点击选择图片,上传一张图片后点击提交

查看一下项目目录,生成了新的文件夹media/avatar/20181205/,其中存储了该头像文件;在SQLiteStudio中查看avatar字段,其保存的是文件的url地址。

除了上传,图片的处理还包括验证格式、改变尺寸、更名、裁剪、美化等多种多样的需求。

如果上传的图片重名,会导致报错吗?请试试看。

更改图片仅仅会改变字段中存储的url,并不会真正删除图片本身。因此在处理大容量文件时要小心,需要额外的方法进行清理。

总结

本章学习了通过表单上传文件的基础知识。更加高级的文件处理手段还需在探索中不断发掘。你还可以利用BootStrap知识,美化个人信息外观,使它像一个完善的产品级页面。




本文作者: 杜赛
发布时间: 2018年12月06日 - 11:54
最后更新: 2019年01月28日 - 20:44
知识共享许可协议   转载请保留原文链接及作者


登录 后回复

共有31条评论

avatar
用户6531503148 么么哒! 3

暂无头像

edit.html这个标签手误了

11个月前 回复


avatar
杜赛 [博主] 用户6531503148 么么哒! 3

感谢指出!

反复调整外观的时候出错了。已经改过来了

11个月前 回复


avatar
yenava 么么哒! 3

上传文件并提交时遇到如下问题:

The joined path (F:\PYTHON\Django_parctice\my_blog\media\avatar\20190304\cigar.jpg) is located outside of the base path component (F:\PYTHON\Django_parctice\my_blog\media\)

9个月前 回复


avatar
杜赛 [博主] yenava 么么哒! 3

settings.py 中有关 media 的设置正确吗?

注意斜杠不要打错、增加或者漏掉。

9个月前 回复


avatar
yenava 杜赛 [博主] 么么哒! 3

加上.replace('\\','/')就可以了

MEDIA_ROOT = os.path.join(BASE_DIR, "media").replace('\\','/')

9个月前 回复


avatar
杜赛 [博主] yenava 么么哒! 3

嗯嗯,系统地址解析出的问题

9个月前 回复


avatar
haha 么么哒! 3

照片和视频存储在什么专门的服务器上呢?有推荐的吗?

7个月前 回复


avatar
杜赛 [博主] haha 么么哒! 3

小尺寸的图片可以就放在项目部署的服务器。

大尺寸图片和视频要考虑专门的云对象存储服务商。比如七牛云、又拍云、阿里云。

7个月前 回复


avatar
pidada 么么哒! 3
本回复已被 pidada 删除

6个月前 回复


avatar
ac1864 么么哒! 4

这个地方卡了2小时,不知道原因。

最后把github的 /templates/userprofile/edit.html

copy  过来,一切都搞定了。

懒得找原因。

先用最快的速度通关,然后再复习一遍。然后,再自己做一遍。

6个月前 回复


avatar
shenhanlin 么么哒! 2

博主这个地方好像有问题

# 媒体文件地址
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

那个os.path.join(BASE_DIR, 'media/' )多了个“/”,然后修改用户信息的时候会报 Exception Type:SuspiciousFileOperation 这个错,然后去掉“/ ”就正常了

5个月前 回复


avatar
杜赛 [博主] shenhanlin 么么哒! 3

我这边测试是正常的。

可能是系统或者版本的兼容问题,解决了就好

5个月前 回复


avatar
github653224 么么哒! 4

报错了, 上传的图片成功了,但是就是找不到上传的图片

你们的图片最终地址是这个吗: http://127.0.0.1:9090/media/avatar/20190707/xiaoniu_ZCnilQm.png

[07/Jul/2019 20:24:11] "GET /static/bootstrap/css/bootstrap.min.css.map HTTP/1.1" 200 562427
Not Found: /media/avatar/20190707/xiaoniu.png
[07/Jul/2019 20:24:45] "GET /media/avatar/20190707/xiaoniu.png HTTP/1.1" 404 2374
Not Found: /media/avatar/20190707/xiaoniu.png
[07/Jul/2019 20:26:53] "GET /media/avatar/20190707/xiaoniu.png HTTP/1.1" 404 2374
Not Found: /userprofile/media/avatar/20190707/xiaoniu.png
[07/Jul/2019 20:27:26] "GET /userprofile/media/avatar/20190707/xiaoniu.png HTTP/1.1" 404 3231
Not Found: /userprofile/edit/media/avatar/20190707/xiaoniu.png
[07/Jul/2019 20:27:44] "GET /userprofile/edit/media/avatar/20190707/xiaoniu.png HTTP/1.1" 404 3246
Not Found: /userprofile/edit/8/media/avatar/20190707/xiaoniu.png
[07/Jul/2019 20:27:55] "GET /userprofile/edit/8/media/avatar/20190707/xiaoniu.png HTTP/1.1" 404 3252
[07/Jul/2019 20:28:09] "GET /userprofile/edit/8/ HTTP/1.1" 200 5098
Not Found: /media/avatar/20190707/xiaoniu.png
[07/Jul/2019 20:28:09] "GET /media/avatar/20190707/xiaoniu.png HTTP/1.1" 404 2374
<MultiValueDict: {}>
[07/Jul/2019 20:28:13] "POST /userprofile/edit/8/ HTTP/1.1" 302 0
[07/Jul/2019 20:28:13] "GET /userprofile/edit/8/ HTTP/1.1" 200 5098
Not Found: /media/avatar/20190707/xiaoniu.png
[07/Jul/2019 20:28:13] "GET /media/avatar/20190707/xiaoniu.png HTTP/1.1" 404 2374
Not Found: /media/avatar/20190707/xiaoniu.png
[07/Jul/2019 20:34:08] "GET /media/avatar/20190707/xiaoniu.png HTTP/1.1" 404 2374
<MultiValueDict: {'avatar': [<InMemoryUploadedFile: xiaoniu.png (image/png)>]}>
[07/Jul/2019 20:45:32] "POST /userprofile/edit/8/ HTTP/1.1" 302 0
[07/Jul/2019 20:45:32] "GET /userprofile/edit/8/ HTTP/1.1" 200 5106
Not Found: /media/avatar/20190707/xiaoniu_PnK29rD.png
[07/Jul/2019 20:45:32] "GET /media/avatar/20190707/xiaoniu_PnK29rD.png HTTP/1.1" 404 2398
<MultiValueDict: {'avatar': [<InMemoryUploadedFile: xiaoniu.png (image/png)>]}>
[07/Jul/2019 20:47:43] "POST /userprofile/edit/8/ HTTP/1.1" 302 0
[07/Jul/2019 20:47:43] "GET /userprofile/edit/8/ HTTP/1.1" 200 5166
Not Found: /media/avatar/20190707/xiaoniu_ZCnilQm.png
[07/Jul/2019 20:47:43] "GET /media/avatar/20190707/xiaoniu_ZCnilQm.png HTTP/1.1" 404 2398
[07/Jul/2019 20:47:55] "GET /userprofile/edit/8/ HTTP/1.1" 200 5166
Not Found: /media/avatar/20190707/xiaoniu_ZCnilQm.png
[07/Jul/2019 20:47:55] "GET /media/avatar/20190707/xiaoniu_ZCnilQm.png HTTP/1.1" 404 2398
 

5个月前 回复


avatar
杜赛 [博主] github653224 么么哒! 3

静态文件配置写对了吗?比如MEDIA_ROOTMEDIA_URL,还有路由里的static设置。IP端口是9090吗?

直接在浏览器里打开http://127.0.0.1:9090/media/xxx/xxx/xxx.png正常不

5个月前 回复


avatar
CearlERO 么么哒! 3

博主大大,我没报错,上传的图片显示是图片文件名字,没有显示图片。而且根目录下面也没有新建的media文件夹

4个月前 回复


avatar
杜赛 [博主] CearlERO 么么哒! 2

那说明你图片没传上去。检查一下数据库,看看图片的数据表正常不正常

4个月前 回复


avatar
tan920229 杜赛 [博主] 么么哒! 3

我的也是这样只有图片名称没有图片。数据库这个字段是空的

4个月前 回复


avatar
tan920229 杜赛 [博主] 么么哒! 3

[30/Jul/2019 14:18:49] "POST /userprofile/edit/7/ HTTP/1.1" 302 0
[30/Jul/2019 14:18:49] "GET /userprofile/edit/7/ HTTP/1.1" 200 5091
[30/Jul/2019 14:18:49] "GET /static/layer/theme/default/layer.css?v=3.1.1 HTTP/1.1" 404 1719
我的传上去这种反应

4个月前 回复


avatar
杜赛 [博主] tan920229 么么哒! 3

只是数据库条目中没地址吗?

目录中的图片保存上了没有?其他字段能不能正确保存?

后台有什么报错没有?

4个月前 回复


avatar
tan920229 杜赛 [博主] 么么哒! 3

老师,我昨天前天都能运行Django的项目,今天一启动服务器登陆http://127.0.0.1:8000/就报404  后台报错Not Found: / 

  1. 一直找不到原因
  2. Django version 2.1, using settings 'my_blog.settings'
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CTRL-BREAK.
    Not Found: /
    这样报错

4个月前 回复


avatar
杜赛 [博主] tan920229 么么哒! 3

根目录有解析吗?其他页面正常不?

4个月前 回复


avatar
tan920229 杜赛 [博主] 么么哒! 3

博主你好,就是我用这个框架的时候,新增一个页面,继承的也是base.html,但是新增的这个页面上半部分一大片是空白,下半部分才是我画的图。这是什么问题导致的

3个月前 回复


avatar
杜赛 [博主] tan920229 么么哒! 2

空白是什么意思?是已有的内容被覆盖掉了,还是说就是多出来一片白色的区域?

3个月前 回复


avatar
sys0613 么么哒! 3

您好,博主,我想问下这里的MEDIA_URL这个名字是django规定的名字么?还是说我们自己随意起的,有点想不明白为何就能自动往这个路径上传图片了。谢谢

# 媒体文件地址
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

3个月前 回复


avatar
杜赛 [博主] sys0613 么么哒! 3

这是Django给的默认的命名。只要你用了这个变量名,Django运行时自己会在后台进行处理。

3个月前 回复


avatar
sys0613 杜赛 [博主] 么么哒! 3

收到,非常感谢

3个月前 回复


avatar
nbyue 么么哒! 4

 <div class="form-group col-md-4">
        <label for="avatar">上传头像</label>
        <input type="file" class="form-control-file" name="avatar" id="avatar">
    </div>

这里需要加个栅栏。才和之前的对齐

1个月前 回复


avatar
Sunbeam520 么么哒! 1

博主你好,我是一步步跟着您的博客做下来的,首先真的很感谢您写下这篇对新手很友好的教程。

我在上传用户头像这步遇到了上述评论一样的问题。就是  点击上传头像选择文件按钮后,图像并没有显示出来,只是“未选择任何文件”这里变成了图片的名称如‘jay.jpg',但url地址改变了,增加了一长串字符。

我查看了数据库里面的数据,发现在user_profile中avatar这个是没有数据的。也就是没有成功提交图片数据。

虽然发现了问题,但是查看了代码却也不知道如何处理,只好向您请教一下。下面是相关代码片段

edit.html

//edit.html
<div class="container">
    <div class="row">
        <div class="col-12">
            <br>
            <div class="col-md-4">用户名: {{ user.username }}</div>
<!--            {% if profile.avatar %}-->
                <div class="col-md-4">头像</div>
                <img src="{{ profile.avatar.url }}" style="max-width: 20%; border-radius: 15%;" class="col-md-4">
<!--            {% else %}-->
                <h5 class="col-md-4">暂无头像</h5>
<!--            {% endif %}-->
            <br>
            <br>
            <form method="post" action="." enctype="multipart/form-data">
                {% csrf_token %}
                <!-- avatar -->
                <div class="form-group col-md-4">
                    <label for="avatar">上传头像</label>
                    <input type="file" class="form-control-file" name="avatar" id="avatar" value="{{profile.avatar}}}">
                </div>
                <!-- phone -->
                <div class="form-group col-md-4" >
                    <label for="phone">电话</label>
                    <input type="text" class="form-control" id="phone" name="phone" value="{{ profile.phone }}">
                </div>
                <!-- bio -->
                <div class="form-group col-md-4">
                    <label for="bio">简介</label>
                    <textarea type="text" class="form-control" id="bio" name="bio" rows="12">{{ profile.bio }}</textarea>
                </div>


                <!-- 提交按钮 -->
                <button type="submit" class="btn btn-primary">提交</button>
            </form>
        </div>
    </div>
</div>

userprofile/views.py 

@login_required(login_url='/userprofile/login/')
def profile_edit(request, id):
    user = User.objects.get(id=id)
    # user_id 是 OneToOneField 自动生成的字段
    if Profile.objects.filter(user_id=id).exists():
        profile = Profile.objects.get(user_id=id)
    else:
        profile = Profile.objects.create(user=user)

    if request.method == 'POST':
        # 验证修改数据者,是否为用户本人
        if request.user != user:
            return HttpResponse("你没有权限修改此用户信息。")

        profile_form = ProfileForm(request.POST, request.FILES)
        if profile_form.is_valid():
            # 取得清洗后的合法数据
            profile_cd = profile_form.cleaned_data
            profile.phone = profile_cd['phone']
            profile.bio = profile_cd['bio']
            if 'avatar' in request.FILES:
                profile.avatar = profile_cd["avatar"]
            profile.save()
            # 带参数的 redirect()
            return redirect("userprofile:edit", id=id)
        else:
            return HttpResponse("注册表单输入有误。请重新输入~")

    elif request.method == 'GET':
        profile_form = ProfileForm()
        context = { 'profile_form': profile_form, 'profile': profile, 'user': user }
        return render(request, 'userprofile/edit.html', context)
    else:
        return HttpResponse("请使用GET或POST请求数据")

其他配置部分均与博客内容相同

23天前 回复


avatar
杜赛 [博主] Sunbeam520 么么哒! 1

粗看了一下你给的片段,暂时没发现问题。

在视图里的各个地方把 avatar 或上传图片相关的字段 print 出来,看看字段是在什么位置断掉的

21天前 回复


avatar
wongxy 么么哒! 2

pillow是django在处理图片时自己内部调用的吗

13天前 回复


avatar
杜赛 [博主] wongxy 么么哒! 1

不一定,看你怎么用了

10天前 回复