做者:Hubery 时间:2018.10.31html
接上文:接上文:Django2 Web 实战02-用户注册登陆退出python
视频是一种可视化媒介,所以视频数据库至少应该存储图像。让用户上传文件是个很大的隐患,所以接下来会讨论这俩话题:文件上传,安全隐患。数据库
咱们会检查文件上传的安全隐患。能够看下Django帮咱们作了什么,以及什么地方咱们应该作出谨慎的决策。django
这里,咱们会建立一个model,展现和管理要上传到网站上的文件;而后,建立一个form和视图来验证和处理上传过程。浏览器
开始着手文件上传以前,咱们须要知道,文件上传取决于一系列的设置,且这些设置在开发环境和生产环境上是不一样的。这些设置会影响文件的存储方式和访问方式。 Django有两套文件配置:STATIC_* 和MEDIA_*。 Static
文件是咱们项目的一部分,好比(CSS,JS)。 Media
文件是用户上传到咱们系统中的文件。Media文件不该被信任,切不能执行。 咱们将会在settings.py
文件中设置这两个地方:安全
MEDIA_URL = '/uploaded'
MEDIA_ROOT = os.path.join(BASE_DIR, '../media_root')
复制代码
MEDIA_URL
, 是用来给上传的文件服务的URL。 开发环境
中,这个值可有可无,一样不会与咱们视图中的URL冲突。 生产环境
中,上传的文件应该给一个与咱们工程中任何app不一样的域URL,同时还不能是子域。 用户的浏览器被欺骗执行它从同一域(或子域)中请求来的文件,由于咱们的app将信任该与咱们用户cookie(包括session ID)相同的文件。 全部浏览器的默认策略是:同源策略(Same Origin Policy)。 MEDIA_ROOT
是Django保存代码目录的路径。 咱们应该确保该目录不在咱们的工程代码目录下,这样就不会意外的将该目录加入版本控制范围,或者意外的授予该目录文件一些特定的权限,如执行。 在生产环境中,还有其余的配置项须要配置,如限制请求body等,这些会在后续的部分讨论。bash
接下来,建立media_root目录: 命令行至:与咱们的项目最外层目录平级
cookie
mkdir media_root
ls
复制代码
MovieImage模型用一个新的字段ImageField来存储文件,同时也会验证该文件是不是图片。尽管ImageField会验证该字段,但仅仅靠阻止那些制造恶意文件的用户是不够的(但会帮助意外点击.zip文件的用户,而不是.png的用户)。 Django用Pillow
库来作验证,因此先添加Pillow库到环境中:session
pip install Pillow
复制代码
默认在命令行中直接pip install Pillow,安装的是最新版本; 另外提供一种更优雅的命令行安装方式:app
touch requirements.dev.txt //建立文件
vi requirements.dev.txt // 编辑文件
// 输入版本号 Pillow<4.4.0 而后保存
pip install -r requirements.dev.txt // 执行py库安装
复制代码
接下来开始建立model: core/models.py
def movie_directory_path_with_uuid(instance, filename):
return '{}/{}'.format(instance.movie_id, uuid4())
class MovieImage(models.Model):
image = models.ImageField(
upload_to=movie_directory_path_with_uuid)
uploaded = models.DateTimeField(
auto_now_add=True)
movie = models.ForeignKey(
'Movie', on_delete=models.CASCADE)
user = models.ForeignKey(
settings.AUTH_PASSWORD_VALIDATORS,
on_delete=models.CASCADE)
复制代码
ImageField
是FileField
的一个特殊字段,用Pillow
来确认一个文件是不是图片。ImageField
和FileField
使用Django的文件存储API
来工做(提供了一种读取文件的方式),同时能够进行文件的读写。 Django自带了FileSystemStorage
,实现了存储API将文件数据存储到本地文件系统上。这对开发来讲足够了,但后续咱们会考虑替代方案。
咱们用ImageField
的upload_to
参数来指定一个方法,用来生成上传文件的名字。咱们不但愿用户能够在咱们的系统中指定文件的名字,由于他们可能会滥用一些用户信任的名字,从而使咱们难堪。鉴于此,咱们使用一个函数将指定的movie的全部图片存储在同一目录中,同时用uuid4
为每一个文件生成一个通用
的名字(这也避免了名字冲突
和处理文件之间的相互覆盖
问题)。
咱们同时会记录是谁上传的文件,这样若是咱们发现一个坏的文件,至关于提供了一种如何找到其余坏文件的线索。
模型建立完,更新数据库:
python manage.py makemigrations core
复制代码
有了模型,就能够建立其余部分,如表单和视图。
MovieImageForm和以前的VoteForm类似,它会隐藏和禁用模型所需的movie和user字段,这很难取得客户的信任。
编辑core/forms.py
# 添加文件上传form
class MovieImageForm(forms.ModelForm):
movie = forms.ModelChoiceField(
widget=forms.HiddenInput,
queryset=Movie.objects.all(),
disabled=True,
)
user = forms.ModelChoiceField(
widget=forms.HiddenInput,
queryset=get_user_model().objects.all(),
disabled=True,
)
class Meta:
model = MovieImage
fields = ('image', 'user', 'movie')
复制代码
表单ModelForm中,咱们没有重写MovieImage的image字段,由于ModelForm会自动提供一正确的文件选择框:<input type="file">。
如今咱们在视图MovieDetail中使用这个表单, core/views.py:
# movie详情 视图
class MovieDetail(DetailView):
queryset = Movie.objects.all_with_related_persons_and_score()
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
# 配置图片上传表单
ctx['image_form'] = self.movie_image_form()
# 其余 略
# 添加图片上传表单
def movie_image_form(self):
if self.request.user.is_authenticated:
return MovieImageForm()
return None
复制代码
这里的上传代码比较简单,只能上传新图片,没有其余操做,一只提供一个空表单。然而经过这种方式咱们不能显示错误信息。实践中,丢失error信息不是很好的作法。
咱们须要对movie_detail.html模版进行两次更新。
编辑core/templates/core/movie_detail.html
{% extends 'base.html' %}
{% block title %}
{{ object.title }} - {{ block.super }}
{% endblock %}
{% block main %}
<h1>{{ object }}</h1>
<p class="lead">
{{ object.plot }}
</p>
{# 展现电影图片列表 #}
<div class="col">
<h1>{{ object }}</h1>
<p class="lead"> {{ object.plot }}</p>
</div>
<ul>
{% for i in object.movieimage_set.all %}
<li class="list-inline-item">
<img src="{{ i.image.url }}">
</li>
{% endfor %}
</ul>
<p>由 {{ object.director }} 执导。</p>
{% endblock %}
{% block sidebar %}
{# 电影排名部分 #}
<div>
这个电影排名:
<span class="badge badge-primary">
{{ object.get_rating_display }}
</span>
</div>
<div>
<h2>
该片得分:{{ object.score|default_if_none:"TBD-暂无得分" }}
</h2>
</div>
{# 文件上传部分 #}
{% if image_form %}
<div>
<h2>上传新图片</h2>
<form method="post"
enctype="multipart/form-data"
action="{% url 'core:MovieImageUpload' movie_id=object.id %}">
{% csrf_token %}
{{ image_form.as_p }}
<p>
<button class="but btn-primary">上传</button>
</p>
</form>
</div>
{% endif %}
{# 投票部分 #}
{% if vote_form %}
<form method="post" action="{{ vote_form_url }}">
{% csrf_token %}
{{ vote_form.as_p }}
<button class="btn btn-primary">投票</button>
</form>
{% else %}
<p> 先登陆,再给此电影投票</p>
{% endif %}
{% endblock %}
复制代码
更新movie_detail.html的main和sidebar部分。 main block
中,用image
字段的url
属性,返回MEDIA_URL
中设置的URL,再与计算的名字相拼接,而后咱们能够经过tag找到正确的图片。 sidebar block
中,form tag中必定要引入enctype属性,以即可以让上传的文件与请求的属性相关联。
模版升级完成,能够开始建立保存上传文件的视图了:MovieImageUpload。
编辑core/views.py文件
# 建立图片上传视图
class MovieImageUpload(LoginRequiredMixin, CreateView):
form_class = MovieImageForm
def get_initial(self):
initial = super().get_initial()
initial['user'] = self.request.user.id
initial['movie'] = self.kwargs['movie_id']
return initial
def render_to_response(self, context, **response_kwargs):
movie_id = self.kwargs['movie_id']
movie_detail_url = reverse(
'core:MovieDetail',
kwargs={'pk': movie_id})
return redirect(to=movie_detail_url)
def get_success_url(self):
movie_id = self.kwargs['movie_id']
movie_detail_url = reverse(
'core:MovieDetail', kwargs={'pk': movie_id})
return movie_detail_url
复制代码
视图再一次作了验证和保存模型的全部工做。咱们从请求的user属性中获取user.id属性,从URL中获取movie ID,当MovieImageForm的user和movie字段不可用时(忽略请求body体中的参数值),将user和movie ID看成初始参数传给form。 Django的ImageField会对文件更名和存储。
将文件上传视图MovieImageUpload关联到URLConf中。 编辑core/urls.py
from django.conf.urls import url
from django.urls import path
from core import views
app_name = 'core'
urlpatterns = [
# 省略其余路径
# 配置
path('movie/<int:movie_id>/image/upload',
views.MovieImageUpload.as_view(),
name='MovieImageUpload'),
]
复制代码
像往常同样,咱们添加一个path()函数,确保传入一个movie_id参数。 如今Django就知道如何找到咱们新增的文件上传视图,只是它还不知道如何对外提供这个上传的文件。 在开发环境中,为了对外提供该上传的文件,更新下urls.py文件: MyMovie/urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
import core.urls
import user.urls
MEDIA_FILE_PATHS = static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include(user.urls, namespace='user')),
path('', include(core.urls, namespace='core')),
] + MEDIA_FILE_PATHS
复制代码
Django提供了static()
函数,返回一个包含单路径对象的列表,该对象将以字符串MEDIA_URL
开头的任何请求路由到document_root
中的文件。 开发环境中,这给咱们提供了一种上传图片文件的方式。这种方式不适合生产环境,若是settings.DEBUG
是False
,static()
函数将返回一个空列表。
天星技术团QQ:557247785
。