Django2 Web 实战03-文件上传

栏目: Python · 发布时间: 6年前

内容简介:作者:Hubery 时间:2018.10.31接上文:接上文:视频是一种可视化媒介,因此视频数据库至少应该存储图像。让用户上传文件是个很大的隐患,因此接下来会讨论这俩话题:文件上传,安全隐患。

作者:Hubery 时间:2018.10.31

接上文:接上文: Django2 Web 实战02-用户注册登录退出

视频是一种可视化媒介,因此视频数据库至少应该存储图像。让用户上传文件是个很大的隐患,因此接下来会讨论这俩话题:文件上传,安全隐患。

  • 新增一个文件上传函数,让用户给movie上传图片
  • 检查OWASP列举的前10项安全隐患

我们会检查文件上传的安全隐患。可以看下Django帮我们做了什么,以及什么地方我们应该做出谨慎的决策。

1. 文件上传

这里,我们会创建一个model,展示和管理要上传到网站上的文件;然后,创建一个form和视图来验证和处理上传过程。

1.1 准备文件上传配置项

开始着手文件上传之前,我们需要知道,文件上传取决于一系列的设置,且这些设置在开发环境和生产环境上是不同的。这些设置会影响文件的存储方式和访问方式。 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等,这些会在后续的部分讨论。

接下来,创建media_root目录: 命令行至: 与我们的项目最外层目录平级

mkdir media_root
ls 
复制代码
Django2 Web 实战03-文件上传

1.2 创建MovieImage模型

MovieImage模型用一个新的字段ImageField来存储文件,同时也会验证该文件是否是图片。尽管ImageField会验证该字段,但仅仅靠阻止那些制造恶意文件的用户是不够的(但会帮助意外点击.zip文件的用户,而不是.png的用户)。 Django用 Pillow 库来做验证,所以先添加Pillow库到环境中:

pip install Pillow
复制代码

默认在命令行中直接pip install Pillow,安装的是最新版本; 另外提供一种更优雅的命令行安装方式:

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)
复制代码

ImageFieldFileField 的一个特殊字段,用 Pillow 来确认一个文件是否是图片。 ImageFieldFileField 使用Django的 文件存储API 来工作(提供了一种读取文件的方式),同时可以进行文件的读写。 Django自带了 FileSystemStorage ,实现了存储API将文件数据存储到本地文件系统上。这对开发来说足够了,但后续我们会考虑替代方案。

我们用 ImageFieldupload_to 参数来指定一个方法,用来生成上传文件的名字。我们不希望用户可以在我们的系统中指定文件的名字,因为他们可能会滥用一些用户信任的名字,从而使我们难堪。鉴于此,我们使用一个函数将指定的movie的所有图片存储在同一目录中,同时用 uuid4 为每个文件生成一个 通用 的名字(这也避免了 名字冲突 和处理 文件之间的相互覆盖 问题)。

我们同时会记录是谁上传的文件,这样如果我们发现一个坏的文件,相当于提供了一种如何找到其他坏文件的线索。

模型创建完,更新数据库:

python manage.py makemigrations core
复制代码

有了模型,就可以创建其他部分,如表单和视图。

1.3 创建和使用MovieImageForm

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信息不是很好的做法。

1.4 更新模版movie_detail.html显示和上传图片

我们需要对movie_detail.html模版进行两次更新。

  • 需要更新main模版的block新增一个图片列表。
  • 需要更新sidebar模版的block包含我们新建的上传表单。

编辑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。

1.5 创建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
复制代码
Django2 Web 实战03-文件上传

视图再一次做了验证和保存模型的所有工作。我们从请求的user属性中获取user.id属性,从URL中获取movie ID,当MovieImageForm的user和movie字段不可用时(忽略请求body体中的参数值),将user和movie ID当作初始参数传给form。 Django的ImageField会对文件改名和存储。

1.6 将请求关联到视图和文件上

将文件上传视图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.DEBUGFalsestatic() 函数将返回一个空列表。

天星技术团QQ: 557247785

Django2 Web 实战03-文件上传

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

设计之下

设计之下

搜狐新闻客户端UED团队 / 电子工业出版社 / 2014-1-1 / CNY 69.00

形而上者谓之道,形而下者谓之器。匠者,器也。处身平凡的匠人不断追求向上的设计之道。本书没有华丽的辞藻和长篇大论的理论,作者是搜狐一线的设计团队,写作过程中他们尽力还原真实的工作场景,并总结出了一些实用的经验和方法。 《设计之下》共三部分,全面讲解了用户体验设计的流程和方法。第一部分为“交互设计”,阐述了从项目启动、解析需求到原型设计的过程,并且总结了交互设计的要点:大局观、操作流程简捷、形式......一起来看看 《设计之下》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具