This commit is contained in:
David 2019-05-28 13:58:00 +08:00
parent ab007d5971
commit bed03c9234
11 changed files with 219 additions and 35 deletions

View File

@ -19,7 +19,7 @@ from django.urls import path, include, re_path
from django.views.generic.base import TemplateView
from django.views.static import serve
from utils.views import UploadFileAPI
from utils.views import UploadFileAPI, QRCodeAPI, DownloadAllQRCodeByRoomAPI
urlpatterns = [
path('', TemplateView.as_view(template_name='index.html')),
@ -28,7 +28,9 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('api/user/', include('user.urls', namespace='user')),
path('api/booking/', include('booking.urls', namespace='booking')),
path('api/file/', UploadFileAPI.as_view())
path('api/file/', UploadFileAPI.as_view()),
path('api/qrcode/', QRCodeAPI.as_view()),
path('api/qrcode/all/', DownloadAllQRCodeByRoomAPI.as_view())
]
if settings.DEBUG:

View File

@ -15,6 +15,7 @@ celery = "*"
eventlet = "*"
psycopg2 = "*"
python-memcached = "*"
qrcode = {extras = ["pil"],version = "*"}
[requires]
python_version = "3.7"

58
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f5e0c38bf5a4c12520573fccc9fd57bd36112da57038e5456845fc259c1fef91"
"sha256": "1e97ca750e0514dd32253773df5c99acd2df1c3ed92d5b615c79a581ca6b9329"
},
"pipfile-spec": 6,
"requires": {
@ -25,10 +25,10 @@
},
"babel": {
"hashes": [
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
"sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab",
"sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"
],
"version": "==2.6.0"
"version": "==2.7.0"
},
"billiard": {
"hashes": [
@ -44,6 +44,14 @@
"index": "pypi",
"version": "==4.3.0"
},
"colorama": {
"hashes": [
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
],
"markers": "platform_system == 'Windows'",
"version": "==0.4.1"
},
"django": {
"hashes": [
"sha256:6fcc3cbd55b16f9a01f37de8bcbe286e0ea22e87096557f1511051780338eaea",
@ -128,6 +136,37 @@
],
"version": "==1.5"
},
"pillow": {
"hashes": [
"sha256:15c056bfa284c30a7f265a41ac4cbbc93bdbfc0dfe0613b9cb8a8581b51a9e55",
"sha256:1a4e06ba4f74494ea0c58c24de2bb752818e9d504474ec95b0aa94f6b0a7e479",
"sha256:1c3c707c76be43c9e99cb7e3d5f1bee1c8e5be8b8a2a5eeee665efbf8ddde91a",
"sha256:1fd0b290203e3b0882d9605d807b03c0f47e3440f97824586c173eca0aadd99d",
"sha256:24114e4a6e1870c5a24b1da8f60d0ba77a0b4027907860188ea82bd3508c80eb",
"sha256:258d886a49b6b058cd7abb0ab4b2b85ce78669a857398e83e8b8e28b317b5abb",
"sha256:33c79b6dd6bc7f65079ab9ca5bebffb5f5d1141c689c9c6a7855776d1b09b7e8",
"sha256:367385fc797b2c31564c427430c7a8630db1a00bd040555dfc1d5c52e39fcd72",
"sha256:3c1884ff078fb8bf5f63d7d86921838b82ed4a7d0c027add773c2f38b3168754",
"sha256:44e5240e8f4f8861d748f2a58b3f04daadab5e22bfec896bf5434745f788f33f",
"sha256:46aa988e15f3ea72dddd81afe3839437b755fffddb5e173886f11460be909dce",
"sha256:74d90d499c9c736d52dd6d9b7221af5665b9c04f1767e35f5dd8694324bd4601",
"sha256:809c0a2ce9032cbcd7b5313f71af4bdc5c8c771cb86eb7559afd954cab82ebb5",
"sha256:85d1ef2cdafd5507c4221d201aaf62fc9276f8b0f71bd3933363e62a33abc734",
"sha256:8c3889c7681af77ecfa4431cd42a2885d093ecb811e81fbe5e203abc07e0995b",
"sha256:9218d81b9fca98d2c47d35d688a0cea0c42fd473159dfd5612dcb0483c63e40b",
"sha256:9aa4f3827992288edd37c9df345783a69ef58bd20cc02e64b36e44bcd157bbf1",
"sha256:9d80f44137a70b6f84c750d11019a3419f409c944526a95219bea0ac31f4dd91",
"sha256:b7ebd36128a2fe93991293f997e44be9286503c7530ace6a55b938b20be288d8",
"sha256:c4c78e2c71c257c136cdd43869fd3d5e34fc2162dc22e4a5406b0ebe86958239",
"sha256:c6a842537f887be1fe115d8abb5daa9bc8cc124e455ff995830cc785624a97af",
"sha256:cf0a2e040fdf5a6d95f4c286c6ef1df6b36c218b528c8a9158ec2452a804b9b8",
"sha256:cfd28aad6fc61f7a5d4ee556a997dc6e5555d9381d1390c00ecaf984d57e4232",
"sha256:dca5660e25932771460d4688ccbb515677caaf8595f3f3240ec16c117deff89a",
"sha256:de7aedc85918c2f887886442e50f52c1b93545606317956d65f342bd81cb4fc3",
"sha256:e6c0bbf8e277b74196e3140c35f9a1ae3eafd818f7f2d3a15819c49135d6c062"
],
"version": "==6.0.0"
},
"psycopg2": {
"hashes": [
"sha256:00cfecb3f3db6eb76dcc763e71777da56d12b6d61db6a2c6ccbbb0bff5421f8f",
@ -160,6 +199,17 @@
],
"version": "==2019.1"
},
"qrcode": {
"extras": [
"pil"
],
"hashes": [
"sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5",
"sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"
],
"index": "pypi",
"version": "==6.1"
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",

View File

@ -32,27 +32,8 @@ export function getCookie (name) {
* @returns {Promise<Response>}
*/
export default function fetchAPI (url, method, data = null, params = null) {
let body = null
// csrf
let headers = {
'Content-Type': 'application/json'
}
if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(method.toUpperCase())) {
headers['X-CSRFToken'] = getCookie('csrftoken')
}
if (data) {
body = JSON.stringify(data)
}
if (params) {
url += '?' + (new URLSearchParams(params)).toString()
}
return new Promise((resolve, reject) => {
fetch(BASE_URL + url, {
credentials: 'include',
headers: headers,
method: method,
body: body
})
fetchAPIBase(url, method, data, params)
.then(res => {
if (res.status === 204) {
return res.text()
@ -75,4 +56,27 @@ export default function fetchAPI (url, method, data = null, params = null) {
notification.error({ message: '错误', description: error, key: 'ERROR' })
})
})
}
export function fetchAPIBase (url, method, data = null, params = null) {
let body = null
// csrf
let headers = {
'Content-Type': 'application/json'
}
if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(method.toUpperCase())) {
headers['X-CSRFToken'] = getCookie('csrftoken')
}
if (data) {
body = JSON.stringify(data)
}
if (params) {
url += '?' + (new URLSearchParams(params)).toString()
}
return fetch(BASE_URL + url, {
credentials: 'include',
headers: headers,
method: method,
body: body
})
}

View File

@ -33,3 +33,17 @@ export function removeLoadingAnimate (id = '', timeout = 1500) {
document.body.removeChild(document.getElementById(id))
}, timeout)
}
/**
* 下载文件
* @param blob
* @param fileName
*/
export function downloadFile (blob, fileName) {
let aLink = document.createElement('a')
let evt = document.createEvent('HTMLEvents')
evt.initEvent('click', true, true) //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
aLink.download = fileName
aLink.href = URL.createObjectURL(blob)
aLink.click()
}

View File

@ -30,7 +30,7 @@
编辑
</a-button>
<a-button @click="$router.push({name: 'seat', params: {id: record.id.toString()}})">座位管理</a-button>
<a-button @click="handleDownload">二维码</a-button>
<a-button @click="handleDownload(record)">二维码</a-button>
</a-button-group>
</template>
</a-table>
@ -69,8 +69,11 @@
width: '30%'
}
]
import api from '../../api/room'
import PageLayout from '../../components/page/PageLayout'
import { fetchAPIBase } from '../../utils/fetch'
import { downloadFile } from '../../utils/util'
export default {
name: 'Room',
@ -101,8 +104,22 @@
this.state.loading = false
})
},
handleDownload () {
handleDownload (record) {
const qrcode = {
type: 'room',
name: record.name,
id: record.id
}
fetchAPIBase('qrcode/', 'get', null, qrcode)
.then(res => {
return res.blob()
})
.then(data => {
downloadFile(data, `房间-${record.name}.png`)
})
.catch(error => {
this.$notification({ message: '下载失败', description: error, key: 'ERROR' })
})
}
}
}

View File

@ -45,7 +45,7 @@
icon="cloud-download"
style="width: 100%;"
@click="handleDownloadAll">
下载全部
全部二维码
</a-button>
</a-col>
</a-row>
@ -59,7 +59,7 @@
:pagination="false">
<template slot="operation" slot-scope="text, record, index">
<a-button-group>
<a-button @click="handleDownload">二维码</a-button>
<a-button @click="handleDownload(record)">二维码</a-button>
</a-button-group>
</template>
</a-table>
@ -95,6 +95,8 @@
]
import PageLayout from '../../components/page/PageLayout'
import api from '../../api/room'
import { fetchAPIBase } from '../../utils/fetch'
import { downloadFile } from '../../utils/util'
export default {
name: 'Seat',
@ -164,11 +166,31 @@
this.$notification.error({ message: '错误', description: '暂无座位', key: 'ERROR' })
}
},
handleDownload () {
alert('handleDownload')
handleDownload (record) {
const qrcode = {
type: 'seat',
name: record.name,
id: record.id
}
fetchAPIBase('qrcode/', 'get', null, qrcode)
.then(res => {
return res.blob()
})
.then(data => {
downloadFile(data, `座位-${record.name}.png`)
})
},
handleDownloadAll () {
alert('handleDownloadAll')
fetchAPIBase('qrcode/all/', 'get', null, { room: this.room.id })
.then(res => {
return res.blob()
})
.then(data => {
downloadFile(data, `房间-${this.room.name}.zip`)
})
.catch(error => {
this.$notification({ message: '下载失败', description: error, key: 'ERROR' })
})
}
}
}

View File

@ -1,8 +1,9 @@
amqp==2.4.2
anyjson==0.3.3
Babel==2.6.0
Babel==2.7.0
billiard==3.6.0.0
celery==4.3.0
colorama==0.4.1
Django==2.2.1
django-debug-toolbar==1.11
django-filter==2.1.0
@ -13,9 +14,11 @@ flower==0.9.3
greenlet==0.4.15
kombu==4.5.0
monotonic==1.5
Pillow==6.0.0
psycopg2==2.8.2
python-memcached==1.59
pytz==2019.1
qrcode==6.1
six==1.12.0
sqlparse==0.3.0
tornado==5.1.1

Binary file not shown.

0
utils/tests.py Normal file
View File

View File

@ -1,8 +1,18 @@
import os
import time
import zipfile
from io import BytesIO
import qrcode
from PIL import Image, ImageDraw, ImageFont
from django.conf import settings
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404
from rest_framework import permissions
from rest_framework import views
from rest_framework.response import Response
from booking.models import Room
from utils.models import UploadFile
@ -21,3 +31,64 @@ class UploadFileAPI(views.APIView):
'id': f.id,
'url': f.get_url()
})
class QRCodeAPI(views.APIView):
permission_classes = (permissions.IsAdminUser,)
def get(self, request):
data = {}
for key in request.query_params:
data[key] = request.query_params[key]
stream = create_qrcode(data)
return HttpResponse(stream.getvalue(), content_type="image/png")
class DownloadAllQRCodeByRoomAPI(views.APIView):
permission_classes = (permissions.IsAdminUser,)
def get(self, request):
room_id = request.query_params.get('room')
room = get_object_or_404(Room, id=room_id)
seat_qrcode_stream_list = []
seat_name_list = []
for seat in room.seat_set.all():
data = {
'type': 'seat',
'name': seat.name,
'id': seat.id
}
seat_qrcode_stream_list.append(create_qrcode(data))
seat_name_list.append(seat.name)
zip = BytesIO()
with zipfile.ZipFile(zip, 'w', zipfile.ZIP_DEFLATED) as zf:
for index, file in enumerate(seat_qrcode_stream_list):
zf.writestr(f'座位-{seat_name_list[index]}.png', file.getvalue())
return HttpResponse(zip.getvalue(), content_type="application/zip")
def _create_qrcode(data):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=4,
)
qr.add_data(data)
qr.make(fit=True)
qrcode_img = qr.make_image()
return qrcode_img
def create_qrcode(data):
img = _create_qrcode(data).convert('RGBA')
tagged_img = Image.new(img.mode, (img.size[0], img.size[1] + 40), (255, 255, 255))
tagged_img.paste(img, (0, 0))
draw = ImageDraw.Draw(tagged_img)
font = ImageFont.truetype(os.path.join(settings.BASE_DIR, 'utils', 'font', 'SourceHanSansCN.otf'), 40)
draw.text((40, img.size[1] - 20), f'{data["type"].upper()} - {data["name"]}', font=font, fill=(0, 0, 0))
stream = BytesIO()
tagged_img.save(stream, 'png')
return stream