上传图片与显示图片是Web开发中的一个重要环节。本文是个Django实战小项目,教你如何使用Django自带的通用视图(Generic View)实现图片的上传与显示。我们还会教你如何设置上传路径,以及动态地分配图片名。如果你还不懂什么是通用视图,请阅读我们之前发布的千字长文Django核心基础(3): 视图(View)的编写及通用视图。在你开始练习这个项目前,请确保你使用的是Django 2.0及以上版本,而且你已经通过pip安装好了Python的图片库pillow。
项目分析
我们首先使用django-admin.py startapp pic_upload创建一个小应用app,名叫pic_upload。它将包含3个简单功能性页面:
查看图片清单
查看图片详情
上传新图片
为了简化这个项目,我们的图片将只会有3个属性:标题,图片与上传日期。
第一步:编写模型Model
Django Web开发的第一步永远是编写模型。关于如何正确的编写模型,请看我们原创文章Django核心技术基础(1): Models模型。本项目的pic_upload/models.py代码如下所示。
from django.db import models
from datetime import date
from django.urls import reverse
# Create your models here.
class Picture(models.Model):
title = models.CharField("标题", max_length=100, blank=True, default='')
image = models.ImageField("图片", upload_to="mypictures", blank=True)
date = models.DateField(default=date.today)
def __str__(self):
return self.title
# 对于使用Django自带的通用视图非常重要
def get_absolute_url(self):
return reverse('pic_upload:pic_detail', args=[str(self.id)])
这个模型中有两点是需要值得你注意的:
当你使用ImageField或FileField的时候,你必须定义upload_to选项,这是文件上传后所在的文件夹。数据库本身并不存储文件,而只是存储文件的链接。如果项目的媒体文件根目录是media, 那么本例中上传后的图片会被存储在media/mypictures/。
Django自带通用视图在完成对象编辑或创建后需要一个返回页面,所以我们必需在模型里定义一个返回链接get_absolute_url。在本项目中,图片上传成功后,用户会跳转到图片详情页面。
第二步:URL的设计与配置
因为我们有3个功能性页面,所以我们需要编写3个URLs。更多URL编写知识见Django核心技术基础(2): URL的设计与配置。你需要在pic_upload文件夹里,创建urls.py. 并加入如下代码:
from django.urls import path, re_path
from . import views
# namespace
app_name = 'pic_upload'
urlpatterns = [
# 展示所有图片
path('', views.PicList.as_view(), name='pic_list'),
# 上传图片
re_path(r'^pic/upload/$',
views.PicUpload.as_view(), name='pic_upload'),
# 展示图片
re_path(r'^pic/(?P\d+)/$' ,
views.PicDetail.as_view(), name='pic_detail'),
]
第三步:视图(View)的编写
我们需要编写3个视图来处理URL发过来的请求。我们将用Django的ListView展示图片清单,DetailView展示图片详情,CreateView用来上传新图片。pic_upload/views.py中代码如下图所示:
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView
from .models import Picture
# Create your views here.
class PicList(ListView):
queryset = Picture.objects.all().order_by('-date')
# ListView默认Context_object_name是object_list
context_object_name = 'latest_picture_list'
# 默认template_name = 'pic_upload/picture_list.html'
class PicDetail(DetailView):
model = Picture
# DetailView默认Context_object_name是picture
# 下面是DetailView默认模板,可以换成自己的
# template_name = 'pic_upload/picture_detail.html'
class PicUpload(CreateView):
model = Picture
# 可以通过fields选项自定义需要显示的表单
fields = ['title', 'image']
# CreateView默认Context_object_name是form。
# 下面是CreateView默认模板,可以换成自己模板
# template_name = 'pic_upload/picture_form.html'
当我们使用Django的通用视图时,Django会使用默认内容对象名字context_object_name和模板名字template_name。如果你不喜欢他们,可以换成自己的。
第四步:模板(template)的编写
在pic_upload文件夹里创建一个templates文件夹,然后在templates文件夹里再创建一个pic_upload文件夹,把所有模板文件放里面。至于为什么我们这么布局,请阅读Django项目推荐性的文件与文件夹布局。我们的3个模板文件代码如下:
# picture_list.html
{% block content %}
图片列表
{% if latest_picture_list %}
{% for picture in latest_picture_list %}
href="{% url 'pic_upload:pic_detail' picture.id %}">{{ picture.title }}
- {{ picture.date| date:"Y-m-j" }}
{% endfor %}
{% else %}
还没有上传新图片
{% endif %}
href="{% url 'pic_upload:pic_upload' %}">上传新图片
{% endblock %}
# picture_detail.html
{% block content %}
{{ picture.title }}
{% if picture.image %}
src="{{ picture.image.url }}"/>
{% endif %}
上传日期: {{ picture.date | date:"Y-m-j" }}
|
href="{% url 'pic_upload:pic_upload' %}">上传新图片
{% endblock %}
# picture_form.html
{% block content %}
上传新图片
{% endblock %}
关于在模板中使用图片有两点需要值得你的注意:
上传图片的表单form里, 必需加上enctype="multipart/form-data", 否则图片无法上传成功。这相当于告诉表单,有数据或文件通过表单上传。
在模板里使用图片的方式是, 而不是
第五步:完成项目配置
现在你可以在myproject/settings.py和myproject/urls.py里把你新建的app pic_upload加进去了。
在myproject/settings.py你同时需要设置MEDIA_ROOT和MEDIA_URL,如下图所示。你同时应该在myproject文件夹下新建一个叫media的文件夹。这样图片就会上传到myproject/media/mypictures/里了。
# myproject/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'pic_upload',
]
# 设置媒体文件夹, 对于图片和文件上传很重要
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
在myproject的urls.py里,你不仅要把app的urls加进去,你还要在最后通过static方法,把MEDIA_URL加进去,这样模板中才能正确显示图片。因为图片属于静态文件。
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
# 对于显示静态文件非常重要
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('pic_upload.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
第六步:运行看效果
CMD终端里连续输入python manage.py makemigrations, python manage.py migrate和python manage.py runserver, 打开http://127.0.0.1:8000, 你就看到如下三个页面了。
# 展示图片列表
# 查看图片详情
#上传图片
第7步:思考下有没有改进的地方?有没有Bug?
我们这个案例里没有对上传图片的名字做任何修改。假如用户上传了2张同样名字的图片,后面那张将覆盖前面那张,这显然不是我们所想要的。所以我们实际在操作用户上传的图片过程中有必要动态地定义文件上传的路径,并分配一个随机地的名字。Django可以很轻松地让我们实现这个目标。
自定义动态上传路径和文件名
下例中我们自定义了一个path_and_rename的方法,然后models里改成upload_to = path_and_rename即可。这样所有上传的图片都会以随机的uuid字符串命名。在实际应用中,我们可能还希望用户只上传到自己的文件夹里,这个也可以很轻易地实现。这个问题就留给读者思考吧。
from django.db import models
from datetime import date
from django.urls import reverse
from uuid import uuid4
import os
def path_and_rename(instance, filename):
upload_to = 'mypictures'
ext = filename.split('.')[-1]
filename = '{}.{}'.format(uuid4().hex, ext)
# return the whole path to the file
return os.path.join(upload_to, filename)
# Create your models here.
class Picture(models.Model):
title = models.CharField("标题", max_length=100, blank=True, default='')
image = models.ImageField("图片", upload_to=path_and_rename, blank=True)
date = models.DateField(default=date.today)
如果你喜欢我们的文章,欢迎关注我们的微信公众号【Python与Django大咖之路】。