Django图片系统
1666 浏览 5 years, 10 months
1 图片上传
版权声明: 转载请注明出处 http://www.codingsoho.com/本文以头像为例,来讲解如何在django中添加图片
模型定义
首先定义一个简单的model,只包含图片信息,图片字段用到了ImageField, 具体定义可参考 https://docs.djangoproject.com/en/1.8/ref/forms/fields/#django.forms.ImageField
模型定义如下
class Avatar(models.Model):
image = models.ImageField(_('Image'), blank=False, null=False, upload_to=upload_avatar)
class Meta:
verbose_name = _("avatar")
verbose_name_plural = _("avatar")
其中,upload_to
指定图片上传后存放的位置,参数为instance
和filename
,你可以根据需要上传到指定位置,这儿为了简化用了一个固定路径。
def upload_avatar(instance, filename):
return 'upload/avatar/' + filename
后台上传
我们先完成一个简单的操作,直接在后台admin进行图片上传
图片的存储涉及到两个存储地址和访问地址,我们在setting里面指定了两个变量MEDIA_URL
和MEDIA_ROOT
,分别是访问相对根路径和存储相对根路径
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "static_in_env", "media_root")
我们上传一个图像,名为Tom-Hanks.jpg
, 从admin后台可以看到,当前对象的上传路径为upload/avatar/Tom-Hanks.jpg
其访问链接 http://127.0.0.1:8000/media/upload/avatar/Tom-Hanks.jpg
本地存放路径为 Project Folder\static_in_env\media_root\upload\avatarTom-Hanks.jpg
有两个地方要注意
1.调试模式下,要在网址内访问静态文件,需要在urls.py添加下面两句,否则会提示url找不到
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
2.如果同一路径保存相同文件名时,不会覆盖,会自动生成一个新的文件名(在文件名后面添加一串随机数)
以上面图片为例,如果同名文件再次保存,那么会产生新的文件 upload/avatar/Tom-Hanks_Jzb8Phm.jpg
, 后面这一串码是随机的
前台上传
本章内容可参考 https://docs.djangoproject.com/en/1.8/topics/http/file-uploads/#handling-uploaded-files-with-a-model
Handling uploaded files with a model¶
If you’re saving a file on a Model with a FileField, using a ModelForm makes this process much easier. The file object will be saved to the location specified by the upload_to argument of the corresponding FileField when calling form.save():
我们需要在前端定义一个表单,然后通过这个表单完成上传,示例代码如下
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import ModelFormWithFileField
def upload_file(request):
if request.method == 'POST':
form = ModelFormWithFileField(request.POST, request.FILES)
if form.is_valid():
# file is saved
form.save()
return HttpResponseRedirect('/success/url/')
else:
form = ModelFormWithFileField()
return render(request, 'upload.html', {'form': form})
接下来,我们分下面几步来完成头像图片的前台上传。
1.定义表单 ModelForm
这个表单专门针对avatar这个model,元类里的class即为Avatar
class AvatarForm(forms.ModelForm):
excludes = []
class Meta:
model = Avatar
2.添加模板
模板文件avatar_upload.html
在表单中加入enctype="multipart/form-data;
如果不加,得不到图片信息,只有路径。
FILES
是个字典,它包含每个FileField
的键 (或者ImageField
,FileField
的子类)。这样的话就可以用request.FILES['File']
来存放表单中的这些数据了。
注意: request.FILES只有在请求方法为POST
,并且发送请求的<form>拥有enctype="multipart/form-data
属性时,才会包含数据。否则request.FILES为空
{% block content %}
<form method='POST' enctype="multipart/form-data" action=''>{% csrf_token %}
{{ form|crispy }}
<input class = 'btn btn-primary' type='submit' value='Submit' />
</form>
{% endblock %}
3.渲染和处理表单
参考前面的代码,我们使用下面的function base view来处理
def avatar_upload(request):
if request.method == 'POST':
form = AvatarForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponseRedirect('/')
else:
form = AvatarForm()
return render(request, 'avatar_upload.html', {'form': form})
或者我们也可以用django的generic view库来处理
CreateView
class AvatarCreateView(CreateView):
template_name = 'avatar_upload.html'
form_class = AvatarForm
success_url = "/"
以上代码就足以完成图片上传了。
图片的上传处理在CreateView里,我们也可以自己处理图片上传功能,重写函数来实现自己定制化的功能。
class AvatarCreateView(CreateView):
template_name = 'avatar_upload.html'
form_class = AvatarForm
success_url = "/"
def get_context_data(self, *args, **kwargs):
context = super(AvatarCreateView, self).get_context_data(*args, **kwargs)
context["image_form"] = AvatarForm()
return context
def post(self, request, *args, **kwargs):
imageForm = self.form_class(request.POST, request.FILES)
if imageForm.is_valid():
image = imageForm.save(commit=False)
# add your code here
image.save()
return HttpResponseRedirect('/')
else:
print imageForm.errors
return super(AvatarCreateView, self).post(request, *args, **kwargs)
甚至,我们可以换个图片处理的方式,不调用django自带的图片存储接口
这个在需要调用第三方存储容器时会用到,比如SAE,这儿不做详细展开
def get_file_path(filename):
if filename:
photoname = os.path.join("upload", "avatar", filename)
photodir = os.path.join(settings.MEDIA_ROOT, "upload", "avatar")
if not os.path.exists(photodir):
os.makedirs(photodir)
photopath = os.path.join(settings.MEDIA_ROOT, photoname)
return photoname, photopath
return None, None
创建视图处理如下:
class AvatarCreateView(CreateView):
template_name = 'avatar_upload.html'
form_class = AvatarForm
success_url = "/"
def get_context_data(self, *args, **kwargs):
context = super(AvatarCreateView, self).get_context_data(*args, **kwargs)
context["image_form"] = AvatarForm()
return context
def post(self, request, *args, **kwargs):
filename=request.FILES['image']
print (filename, 0)
photoname, photopath = get_file_path(filename.name)
if photopath:
img=Image.open(filename)
img.save(photopath)
Avatar.objects.create(image = photoname)
return HttpResponseRedirect('/')
return super(AvatarCreateView, self).post(request, *args, **kwargs)
UpdateView
上面主要讲的是创建一个新的图片,接下来讲讲怎么去更新一个图片,这儿我们会使用UpdateView
跟创建视图相比,因为对象已经存在,需要在Form初始化时传递相应的参数data
。
本例中,data = object.image
class AvatarUpdateView(UpdateView):
template_name = 'avatar_upload.html'
form_class = AvatarForm
# success_url = "/"
model = Avatar
def get_context_data(self, *args, **kwargs):
context = super(AvatarUpdateView, self).get_context_data(*args, **kwargs)
context["image_form"] = AvatarForm(
instance=self.get_object(),
initial={"image":self.get_object().image},
data={"image":self.get_object().image}
)
return context
def get_success_url(self, *args, **kwargs):
return self.object.get_absolute_url()
编辑界面如下
Demo项目操作路径:http://demo.codingsoho.com/image/
4. 优化widget
就基本功能而已,这个模板已经够了,但是不够user friendly,我们希望直观的看到图片的显示,而不是只是一个url。 要实现这个目标有很多方法,可以修改template内容或者修改widget的render。
这儿介绍的是后者,这个方法更具备灵活性和扩展性。在模板里,<img class= 'img-responsive' src="{{object.get_image_url}}"/>
会把图片显示出来,我们会把它写进render函数。
默认的文件上传组件是ClearableFileInput
,我们继承这个类,并添加自己的实现。
注:django 1.8 和 django 1.11在ClearableFileInput
的实现上有很大的差别,整体上django 1.11的实现更先进一点,所以本文只介绍基于后者的实现。
它主要是通过内部模板来实现组件的渲染。所以我们只需要修改这个模板就行了。
class ImageFileInput(ClearableFileInput):
if 1.11 > float(django.get_version()): # version to be confirmed
template_with_initial = ('<p class="file-upload">%s</p>'
% ClearableFileInput.template_with_initial)
template_with_clear = ('<span class="clearable-file-input">%s</span>'
% ClearableFileInput.template_with_clear)
else:
template_name = 'widgets/clearable_file_input.html'
#
# [https://stackoverflow.com/questions/27071648/django-imagefield-widget-to-add-thumbnail-with-clearable-checkbox](https://stackoverflow.com/questions/27071648/django-imagefield-widget-to-add-thumbnail-with-clearable-checkbox)
def render(self, name, value, attrs=None):
if 1.11 > float(django.get_version()):
substitutions = {
'initial_text': self.initial_text,
'input_text': self.input_text,
'clear_template': '',
'clear_checkbox_label': self.clear_checkbox_label,
}
template = '%(input)s'
substitutions['input'] = super(ClearableFileInput, self).render(name, value, attrs)
if self.is_initial(value):
template = self.template_with_initial
substitutions.update(self.get_template_substitution_values(value))
###
substitutions['initial'] = (
'<img src="%s" alt="%s" style="max-width: 200px; max-height: 200px; border-radius: 5px;" /><br/>' % (
escape(value.url), escape(force_unicode(value))
)
)
###
if not self.is_required:
checkbox_name = self.clear_checkbox_name(name)
checkbox_id = self.clear_checkbox_id(checkbox_name)
substitutions['clear_checkbox_name'] = conditional_escape(checkbox_name)
substitutions['clear_checkbox_id'] = conditional_escape(checkbox_id)
substitutions['clear'] = CheckboxInput().render(checkbox_name, False, attrs={'id': checkbox_id})
substitutions['clear_template'] = self.template_with_clear % substitutions
return mark_safe(template % substitutions)
else:
return super(ImageFileInput, self).render(name, value, attrs)
增加新的模板widgets/clearable_file_input.html,实现如下.
修改比较简单,也就是在a里面用图片替换了文字。
{% if is_initial %}
{{ initial_text }}: <a href="{{ widget.value.url }}"><img src="{{widget.value.url}}" alt="{{widget.value}}" style="max-width: 200px; max-height: 200px; border-radius: 5px;" /></a><br/>
{% if not widget.required %}
<input type="checkbox" name="{{ checkbox_name }}" id="{{ checkbox_id }}" />
<label for="{{ checkbox_id }}">{{ clear_checkbox_label }}</label>
{% endif %}<br />
{{ input_text }}:
{% endif %}
<input type="{{ widget.type }}" name="{{ widget.name }}"
{% include "django/forms/widgets/attrs.html" %} />
最后,在form里替换这个widget为ImageFileInput
,这样新的模板会用这个widget来渲染
class AvatarForm(forms.ModelForm):
class Meta:
model = Avatar
exclude = []
widgets = {
'image': ImageFileInput(),
}
效果如下:
参考文档
- Django and AJAX image uploads | Source Code
- jQuery File Upload Demo | wiki | Source Code
https://docs.djangoproject.com/en/1.2/topics/http/file-uploads/
- python image模块
- Python创建目录文件夹
- [豆瓣]浅论Django文件上传
- Django Ajax 文件上传及进度显示
- Ajax 和 jQuery 实现进度条+上传文件到Django
- ajax异步上传图片 ajax 异步上传带进度条视频并提取缩略图
- Django image upload
- Django jQuery File Upload django基于jQuery File Upload的实现
- jQuery File Upload