功能需求说明:用户提交图片网址,然后由程序将该图片下载并保存到指定的位置。
注:提前安装图片支持模块Pillow(pip install Pillow)
建立数据模型,models.py:
from django.db import models
from django.contrib.auth.models import User
from slugify import slugify
class Image(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='images')
title = models.CharField(max_length=300)
url = models.URLField() # 用来保存网络图片的URL,URLField继承了CharField,默认最大长度是200
slug = models.SlugField(max_length=500,blank=True) # SlugField(),如果没有指定 max_length ,Django 会默认字段长度为50。
description = models.TextField(blank=True)
created = models.DateField(auto_now_add=True,db_index=True) # db_index=True 用数据库的此字段作为索引
image = models.ImageField(upload_to='images/%Y/%m/%D')
# ImageField,Django 会自动检测所提交的是否是图片,不需要用户检验方法
def __str__(self):
return self.title
def save(self,*args,**kwargs): # 重写父类save()方法,slug通常被用作网址的一部分
self.slug = slugify(self.title)
super(Image,self).save(*args,**kwargs)
编写表单类,forms.py:
from django import forms
from django.core.files.base import ContentFile
from slugify import slugify
from urllib import request
from .models import Image
class ImageForm(forms.ModelForm):
class Meta:
model = Image
fields = ['title','url','description']
def clean_url(self):
url = self.cleaned_data['url']
valid_extensions = ['jpg','jpeg','png','webp'] # 规定图片的拓展名
extension = url.rsplit('.',1)[1].lower() # 从得到的图片网址中分解出其拓展名
if extension not in valid_extensions:
raise forms.ValidationError('The given Url does not match valid image extension.')
return url # 别忘了return!!!
def save(self,force_insert=False,force_update=False,commit=True): # 重写save()方法
image = super(ImageForm,self).save(commit=False) # commit=False 表示实例虽然被建立,但并没有保存数据
image_url = self.cleaned_data['url']
image_name = '{0}.{1}'.format(slugify(image.title),image_url.rsplit('.',1)[1].lower()) # 给图片文件命名
response = request.urlopen(image_url) # request是urllib的一部分,request.urlopen(image_url),以GET方式访问该图片地址
image.image.save(image_name,ContentFile(response.read()),save=False) # 将上面的语句返回的结果保存到本地,并按照约定的名称给图片文件命名
if commit:
image.save()
return image # 别忘了return!!!
以上代码解析:
clean_<fieldname> 处理某个字段值。
clean_url(),规定图片拓展名范围,如果不在此范围内,就报错。
request.urlopen(image_url):
这里的request 不是视图函数中的参数request,而是Python标准库Urllib中的一部分。request.urlopen(image_url)的作用是以GET方式访问该图片地址,模拟用户把网址输入到浏览器的地址栏,之后在浏览器中返回相应的内容,这里同样返回一个Request对象。通过该对象得到所访问URL的数据(图片的ASCII字符串),通过response.read()可以得到此数据。
ContentFile:
ContentFile是File类的子类,接收字符串为参数。
image.image.save():
第一个image是实例对象,第二个image是属性/字段
官方样式说明:
FieldFile.save(name, content, save=True)
Note that the content argument should be an instance of django.core.files.File, not Python’s built-in file object. You can construct a File from an existing Python file object like this:from django.core.files import File
f = open('/path/to/hello.world')
myfile = File(f)
或者
from django.core.files.base import ContentFile
myfile = ContentFile("hello world")
编写视图函数,views.py:
两个函数,一个是接收并处理前端提交的数据,一个用来展示图片
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
from .models import Image
from .forms import ImageForm
# 处理前端表单提交的图片URL及相关信息
@login_required(login_url='/account/login/')
@csrf_exempt
@require_POST
def upload_image(request):
form = ImageForm(data=request.POST)
if form.is_valid():
try:
new_item = form.save(commit=False)
new_item.user = request.user
new_item.save()
return JsonResponse({'status':'1'}) # 返回JSON数据。这里可以改用字符串【return HttpResponse('1')】
except:
return JsonResponse({'status':'0'})
# 展示图片
@login_required(login_url='/account/login/')
def list_image(request):
images = Image.objects.filter(user=request.user)
return render(request,'image/list_images.html',locals())
创建模板文件,list_images.html:
{% extends "article/base.html" %}
{% load static %}
{% block title %}images{% endblock %}
{% block content %}
<div>
<button type="button" class="btn btn-primary btn-lg btn-block" onclick="addImage()">添加图片</button>
<div style="margin-top:10px;">
<table class="table table-hover">
<tr>
<td>序号</td>
<td>标题</td>
<td>图片</td>
<td>操作</td>
</tr>
{% for image in images %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ image.title }}</td>
<td>{{image.image}}</td>
<td><a name="delete" href="javascript:" onclick="del_image(this, {{ image.id }})"><span class="glyphicon glyphicon-trash" style="margin-left:20px;"></span></a></td>
</tr>
{% empty %}
<p>还没有图片,请点击上面的按钮添加图片</p>
{% endfor %}
</table>
</div>
</div>
<script src="{% static 'js/jquery.js' %}"></script>
<script src="{% static 'js/layer.js' %}"></script>
<script type="text/javascript">
function addImage(){
var index=layer.open({
type: 1,
skin: 'layui-layer-demo',
closeBtn: 0,
shift: 2,
shadeClose: true,
title: "Add Image",
area: ['600px', '440px'],
content: "<div style='padding:20px'><p>请新增扩展名是.jpg或.png的网上照片地址</p><form><div class='form-group'><label for='phototitle' class='col-sm-2 control-label'>标题</label><div class='col-sm-10'><input id='phototitle' type='text' class='form-control' style='margin-bottom:5px'></div></div><div class='form-group'><label for='photourl' class='col-sm-2 control-label'>地址</label><div class='col-sm-10'><input id='photourl' style='margin-bottom:5px' type='text' class='form-control'></div></div><div class='form-group'><label for='description' class='col-sm-2 control-label'>描述</label><div class='col-sm-10'><textarea class='form-control' style='margin-bottom:5px' row='2' id='photodescription'></textarea></div></div><div class='form-group'><div class='col-sm-offset-2 col-sm-10'><input id='newphoto' type='button' class='btn btn-default' value='Add It'></div></div></form></div>",
success: function(){
$("#newphoto").on('click', function(){
var title = $("#phototitle").val();
var url = $("#photourl").val();
var description = $("#photodescription").val();
var photo = {"title":title, "url":url, "description":description};
$.ajax({
url: '{% url "image:upload_image" %}',
type: "POST",
data: photo,
success: function(e){
var status = e['status']
if(status =="1"){
layer.close(index);
window.location.reload();
} else {
layer.msg("图片无法获取,请更换图片");
}
},
});
});
},
});
}
</script>
{% endblock %}
layer.js是弹出层插件
实现删除功能:
在views.py中添加删除函数
# 删除图片
@login_required(login_url='/account/login/')
@csrf_exempt
@require_POST
def del_image(request):
image_id = request.POST['image_id']
try:
image = Image.objects.get(id=image_id)
image.delete()
return JsonResponse({'status':'1'})
except:
return JsonResponse({'status':'2'})
在list_images.html中添加JS代码
function del_image(the, image_id){
var image_title = $(the).parents("tr").children("td").eq(1).text();
layer.open({
type: 1,
skin: "layui-layer-rim",
area: ["400px", "200px"],
title: "删除图片",
content: '<div class="text-center" style="margin-top:20px"><p>是否确定删除《'+image_title+'》</p></div>',
btn:['确定', '取消'],
yes: function(){
$.ajax({
url: '{% url "image:del_image" %}',
type:"POST",
data: {"image_id":image_id},
success: function(e){
var status = e['status']
if(status=="1"){
parent.location.reload();
layer.msg("has been deleted.");
}else{
layer.msg("删除失败");
}
},
})
},
});
}
注:这种删除,只是从本项目的数据库中删除了,并没有从磁盘上删除。
另外,在模板文件list_images.html中,<td>{{image.image}}</td>不显示图片,而是图片路径地址,如:images/2018/10/10/18/18/chai-quan.jpeg
要显示图片的话,要用image.image.url,把上面的代码换成:
<td><img src="{{ image.image.url }}" width="100px" width="100px"></td>
前提是已经配置好了media和url
# settings.py
MEDIA_URL = '/media/' # 设置URL映射中的路径
MEDIA_ROOT = os.path.join(BASE_DIR,'media/') # 声明相对项目根目录的文件保存地址
# views.py
from django.contrib import admin
from django.urls import path,include
from django.conf import settings
from django.conf.urls.static import static
# 为每个上传的静态文件配置URL路径
urlpatterns = [
path('admin/', admin.site.urls),
path('image/',include('image.urls',namespace='image')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
注:代码更改后,展示的图片虽然已经进行了长宽的设置,但仅仅是在显示的时候对图片尺寸进行了更改,实际上仍要读取原图。
缩略图:
使用应用 sorl-thumbnail
官网文档:https://sorl-thumbnail.readthedocs.io/en/latest/
pip install sorl-thumbnail
在settings.py中注册应用sorl.thumbnail
INSTALLED_APPS = [
# 其他应用
'sorl.thumbnail', # 缩略图
]
# 注册后,别忘了迁移数据!!!
修改模板文件list_images.html
# 在文件第一行导入
{% load thumbnail %}
# 修改原来用来显示图片的代码
{% thumbnail image.image "100x100" crop="center" as im %}
<td><img src="{{im.url}}" width="{{ im.width }}" height="{{ im.height }}"></img></td>
{% endthumbnail %}