mirror of
https://github.com/GreaterWMS/GreaterWMS.git
synced 2026-06-19 07:36:00 +00:00
V 2.1.3
Former-commit-id: 710132ebaf418af1ef540e2cda73537b6a38a032 [formerly 815cbd4f0709b5d061392b4ab0caab90a7e25e97] [formerly 93affb91a67ea41c7cc39ee6eafa091629549b58 [formerly a489e2a50e18173a7c2862140f0f0093dcf381c5]] [formerly eb7c696e46d0de6cd07607f1270536f350d99501 [formerly a395cdf8535e8ee8e7d133ac4e1d34c5568a47de] [formerly 1f2edad23658f9919b457ea62610956e7764dbc0 [formerly 4b612ae899e10849e74d87bb65d4354001fe60b4]]] [formerly f0f9d2bc4eb1b1c23df4fd48c51f60cbe6646c84 [formerly 43a998c7e2cc1a6c293cc297d1eb7819dca16934]] [formerly 9bb0f2224b4bc7eb0c31f98e71d34dbaeddc02ca [formerly d1415112ccb6daebee059aa5a83887feb594eca9] [formerly 9bb0f2224b4bc7eb0c31f98e71d34dbaeddc02ca [formerly d1415112ccb6daebee059aa5a83887feb594eca9] [formerly 9d2ff6e436769c52c51fe92373a2837cce7639de [formerly c6ba81a118e6ec0d884196e4404a9ffc15922e21]]] [formerly f0f9d2bc4eb1b1c23df4fd48c51f60cbe6646c84 [formerly 43a998c7e2cc1a6c293cc297d1eb7819dca16934] [formerly f0f9d2bc4eb1b1c23df4fd48c51f60cbe6646c84 [formerly 43a998c7e2cc1a6c293cc297d1eb7819dca16934] [formerly 882094cc7f2e1482667e8a162c97d2418052914f [formerly a2484f1e7d214480fc545f6eb375296b80003bf1]]]]] [formerly 0aacfa5ec62c00b7879353d274af2477f2d1057c [formerly ef38218f0ccb7394e14e713e7216214cf1276c3f] [formerly 0aacfa5ec62c00b7879353d274af2477f2d1057c [formerly ef38218f0ccb7394e14e713e7216214cf1276c3f] [formerly 6eaa3a6c3b6405c4ecf33f2298ebe9ad942a588f [formerly 5b54b721c3c117c4fe69297d30daba41fb9e5295]]] [formerly 14de90134656d66ac6ffdb92e106500657feedd9 [formerly ef58d026e05235fb22e5057a7f7c6ec47a52f4b6]] [formerly 4d66cfe753f254ca67419d7bdb57c171a5cea3f5 [formerly 115b821539b9069a867afc973cca7171533c2914] [formerly 4d66cfe753f254ca67419d7bdb57c171a5cea3f5 [formerly 115b821539b9069a867afc973cca7171533c2914] [formerly 49504cb2a4cfdfa6f06be81a34a7bbbadc822291 [formerly 09e0c730aed4f4549c097f1de85e6fef9c45a9a1]]] [formerly 14de90134656d66ac6ffdb92e106500657feedd9 [formerly ef58d026e05235fb22e5057a7f7c6ec47a52f4b6] [formerly 14de90134656d66ac6ffdb92e106500657feedd9 [formerly ef58d026e05235fb22e5057a7f7c6ec47a52f4b6] [formerly e37c42034316ec2587580987e1c0533630839d4c [formerly 7447825f516a6d00d531cd5a2715ba45a50b0a9b [formerly f30653e98acb7e741c669cdb234d90d9aac5f2cf] [formerly 3f491793fc7811a3a587bb9e0bf47ed0d3071cf2 [formerly f3c5597e2368ee488e490c9e4fc808ddccf6cca3]] [formerly 81cadd6db031084e9644d0f8a843a683237d3263 [formerly d9eb4719e24b30f734cc473039211bff269db0cb] [formerly 44ee6df8132dc0f55567d8a15346d9e69d0861fa]] [formerly 44ee6df8132dc0f55567d8a15346d9e69d0861fa [formerly 81d098dc80297196e75aa7a1b50e1a438af282a5]] [formerly c8f3cdf03f472b9bc8669173060b33c02c5d8978 [formerly 44ee6df8132dc0f55567d8a15346d9e69d0861fa [formerly 81d098dc80297196e75aa7a1b50e1a438af282a5] [formerly c8f3cdf03f472b9bc8669173060b33c02c5d8978 [formerly 11aa70bc956f1713927a815ef063e34ce51eba2f [formerly fa7d83e17f92e4dfc5f473a3aa087fd3b27b7ab9] [formerly c8f3cdf03f472b9bc8669173060b33c02c5d8978 [formerlyb5e22373b2]]]]]]]]]]]] Former-commit-id: 195193d1f4397e6b5ff7f62d011fe8a966878f2e [formerly 05356a5c180ad26aded4d3dc35f952e499162c3e] [formerly 53616636866618ab1613ea39dc3e6b1a91e419b2 [formerly ee71d5853fd2cb8a797ba0285b3fcf782a894571]] [formerly 2269a482910ff8166f43517b21c0a4dfc244a892 [formerly 68d81bdf94fea8ac841cbf5d3f5c212499338165] [formerly d915de7428cbaefbf77c8a0bc18d5192d906295c [formerly fff98ea75fc9b92af4b29989be988f24263ccf6d]]] [formerly 8046eb3ff01df83b76a739d0e0acbb4a2969490f [formerly ec6eee9a8f8230c3170b9a601377edb199f41a3a]] [formerly 4f09faec44bee906c26e0a0149f5c01f137e7342 [formerly 44040e2e836d45b525d2c95902024f1cf83dff01] [formerly 4f09faec44bee906c26e0a0149f5c01f137e7342 [formerly 44040e2e836d45b525d2c95902024f1cf83dff01] [formerly 389a0275bcf031cdfe4d228c9081da06c41443ad [formerly f9659235d6f31d603d63622b06fb96932d9aaa1b]]] [formerly 8046eb3ff01df83b76a739d0e0acbb4a2969490f [formerly ec6eee9a8f8230c3170b9a601377edb199f41a3a] [formerly 8046eb3ff01df83b76a739d0e0acbb4a2969490f [formerly ec6eee9a8f8230c3170b9a601377edb199f41a3a] [formerly 3cc35de39c466500c6412ce81bad2b5dd9c874ab [formerly 6eb491686be74f7e0bdc97866be772259ca31b5c]]]]] Former-commit-id: 0299acfe8ebebf061aeb88f8dd8c5fb6dbc3fbd6 [formerly 02193e0e2092fa0cca39c92a4c65a4c4a2436d9f] [formerly 0299acfe8ebebf061aeb88f8dd8c5fb6dbc3fbd6 [formerly 02193e0e2092fa0cca39c92a4c65a4c4a2436d9f] [formerly 9fc8a024de977c7650912150582e0ec12c181b17 [formerly a30f4efc5ae5951cc2ad91106b2f62d5add47c61]]] [formerly 9a351e27a8954996584c86d69b170e1fd0629c7e [formerly 409b8b0a168c7e891f12e1677ced43866f334924] [formerly 7467168a44b436c3a3fea6d7b93d0adc8a6ce49c [formerly 0f6f03018b29257f048557a20bba63fe97ad0f65]] [formerly 7467168a44b436c3a3fea6d7b93d0adc8a6ce49c [formerly 0f6f03018b29257f048557a20bba63fe97ad0f65] [formerly 29d3047970f8c94bc6df6e9623aff8e7b67db092 [formerly 21cf72db4456f82fc322db1b201add1634f89cda]]]] Former-commit-id: 9a351e27a8954996584c86d69b170e1fd0629c7e [formerly 409b8b0a168c7e891f12e1677ced43866f334924] [formerly 9a351e27a8954996584c86d69b170e1fd0629c7e [formerly 409b8b0a168c7e891f12e1677ced43866f334924] [formerly 7467168a44b436c3a3fea6d7b93d0adc8a6ce49c [formerly 0f6f03018b29257f048557a20bba63fe97ad0f65]]] Former-commit-id: 103391802bc7fd77e58325a67a0d57d3ef040c07 [formerly b79267c743d1cbc2ff9bb668e19f9ccf486d1a91] [formerly 582f764db5fa461236e9b7586d8aefd7674c7fcf [formerly 07c55548bb1cd250093f3d86c6624f59e6b5c145]] Former-commit-id: 0861effa93535c25ef6fd11c18152d23926b1f2a [formerly adada0022bce98c7672dcfbe45f64e77930d6f18] Former-commit-id: 757fe374464916e059512d687702e8758a619871 Former-commit-id: ef125af06a88fa46995e1d7117b10b36673a4597 [formerly d374629df51c3fb9f17055db47c126d901474e46] Former-commit-id: 8b3b07f7c7fcb90cc377cc4420cf77dd074911cf Former-commit-id:2bf47b8be0[formerly 9cb477b490fc0cd80bc5d33c2ef16a69a301212e] [formerly 7e6e1613e16b642ed06b14154ffe12865b75d7ef [formerly 64469b896f1961e255615c08905fa4b389e90570]] [formerly 541cb693f9cb21240e1a04407a3597ba5bc1ffe6] [formerly c26ebab1fe2f58fd3627a97d026875957349eb24 [formerly bf85a742c75aba86b8571ecb3f14712406b21270 [formerly 0a37f8c2c23041a8fb5b635ed6e2c7003e711741] [formerly ec033efbef86026d93383b797f3ceeb42e0ec924 [formerly 9ca528bd6bf7aaf2271df25eb2d9274395c22c44]]]] Former-commit-id: b85fdda276646f7cfc602ef08c44b0ab7f32db12 [formerly fe52d269b63215da57c6f9729cd48a9a8b922127 [formerly e67dd0308b172480e347aa908f35caeb3dad604d] [formerly b3ad684c1097da55a194c521cda9975278ac7d4b [formerly 57a4ea7ba88c5a8b1d4eebf88277fde9d5310bc1]]] Former-commit-id: 3625614f3bc56556781e425bfe885d81756c38cd Former-commit-id: 9f07ab8c5b13e6c114b8b3feaf4e502c62cab35f [formerly 6d30bced7f6a1e7de68e364d51dd829fe912ac73] Former-commit-id: d144c1f2d0f7f2048e28040554771db0079f3ead
This commit is contained in:
@@ -0,0 +1 @@
|
||||
*.vue linguist-language=python
|
||||
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
__pycache__/
|
||||
authorization.txt
|
||||
authorization.json
|
||||
db.sqlite3
|
||||
.idea/
|
||||
delete.py
|
||||
*.xlsx
|
||||
@@ -0,0 +1,40 @@
|
||||
~~~shell
|
||||
如何配置Cordova环境,请参考
|
||||
http://www.quasarchs.com/quasar-cli/developing-cordova-apps/preparation
|
||||
|
||||
# 进入templates目录,创建src-cordova
|
||||
quasar mode add cordova
|
||||
# 进入src-cordova目录
|
||||
cd src-cordova
|
||||
# 部署安卓客户端
|
||||
cordova platform add android
|
||||
输入app名称为org.greaterwms.app
|
||||
# 返回templates目录,先启动一次项目来创建gradle文件夹
|
||||
quasar d -m cordova -T android
|
||||
Ctrl + c退出
|
||||
# 修改gradle版本下载distributionUrl,文件在tempates/src-cordova/platforms/android/gradle/wrapper/gradle-wrapper.properties
|
||||
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-4.10.3-all.zip
|
||||
# 进入src-cordova目录来安装所需要的插件
|
||||
cd src-cordova
|
||||
# 按顺序安装插件
|
||||
cordova plugin add cordova-plugin-device
|
||||
cordova plugin add cordova-plugin-battery-status
|
||||
cordova plugin add cordova-plugin-camera
|
||||
cordova plugin add com-darryncampbell-cordova-plugin-intent
|
||||
cordova plugin add https://gitee.com/Singosgu/cordova-plugin-ubx.git
|
||||
# 修改适配问题的文件,templates\src-cordova\platforms\android\app\src\main\java\org\apache\cordova\camera\FileProvider.java
|
||||
第21行的android.support.v4.content.FileProvider,修改为androidx.core.content.FileProvider
|
||||
# 修改适配问题的文件,templates\src-cordova\platforms\android\app\src\main\java\org\apache\cordova\camera\CameraLauncher.java
|
||||
第42行的android.support.v4.content.FileProvider,修改为androidx.core.content.FileProvider
|
||||
# 回到templates,再次启动项目
|
||||
quasar d -m cordova -T android
|
||||
|
||||
现在只支持Zebra扫描枪,Zebra扫描枪需要打开广播
|
||||
Zebra扫描枪配置请看:
|
||||
https://github.com/Singosgu/GreaterWMS/blob/master/Zebra-Profile.pdf
|
||||
|
||||
# 打包apk
|
||||
quasar build -m android
|
||||
|
||||
接下来在dist/cordova/android/apk/release下面可以找到一个未签名的apk,关于怎么签名,自行百度
|
||||
~~~
|
||||
@@ -0,0 +1,71 @@
|
||||
~~~shell
|
||||
su - // 进入管理员账号
|
||||
yum update
|
||||
yum upgrade
|
||||
yum -y install gcc-c++ // 安装依赖
|
||||
yum install zlib-devel xz-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel libffi-devel gcc make // 安装python依赖
|
||||
yum install nodejs // 安装nodejs
|
||||
npm install n -g // 安装n
|
||||
/usr/local/bin/n 14.18.3 // 升级nodejs
|
||||
npm install npm -g // 升级npm
|
||||
npm install yarn -g // 安装yarn
|
||||
npm install @quasar/cli -g // 安装quasar
|
||||
npm install cordova -g // 安装cordova
|
||||
quasar -v // 检查 Quasar 版本
|
||||
python3 // 查看python版本
|
||||
cd /usr/src // 进入src
|
||||
wget https://www.python.org/ftp/python/3.9.5/Python-3.9.5.tgz // 下载python3.9.5
|
||||
tar -zxvf Python-3.9.5.tgz // 解压
|
||||
cd Python-3.9.5/ // 进入目录
|
||||
./configure --enable-optimizations // 编译
|
||||
make altinstall // 安装
|
||||
mv /usr/bin/python3 /usr/bin/python3.bak // 备份
|
||||
ln -s /usr/local/bin/python3.9 /usr/bin/python3 // 建立软连接
|
||||
mv /usr/bin/pip3 /usr/bin/pip3.bak // 备份
|
||||
ln -s /usr/local/bin/pip3.9 /usr/bin/pip3 // 建立软连接
|
||||
python3 // 查看python版本
|
||||
pip3 list // 查看pip3 是否安装成功
|
||||
yum install git // 安装git
|
||||
git clone https://github.com/Singosgu/GreaterWMS.git // 下载 GreaterWMS 从 github
|
||||
chmod -R 755 GreaterWMS // 提权 GreaterWMS 文件夹
|
||||
cd GreaterWMS // 进入GreaterWMS文件夹
|
||||
pip3 install -r requirements.txt
|
||||
// 有些时候,你安装这些库会出问题,是因为python3版本的问题,不用担心,pip3 install 出错的库就可以了.
|
||||
/usr/local/bin/daphne -p 8008 greaterwms.asgi:application
|
||||
// 现在打开浏览器,输入"127.0.0.1:8008",你会看到500错误,恭喜你,你已经可以正常部署接下来的事情了
|
||||
Ctrl + C // 回到GreaterWMS文件夹
|
||||
python3 manage.py makemigrations // 数据库生成
|
||||
python3 manage.py migrate // 数据库迁移
|
||||
/usr/local/bin/daphne -p 8008 greaterwms.asgi:application
|
||||
// 现在打开浏览器,输入"127.0.0.1:8008",你会看到项目已经运行了
|
||||
// 输入 "127.0.0.1:8008/myip", 你会得到你的内网IP,一定记住它
|
||||
Ctrl + C // 回到GreaterWMS文件夹
|
||||
cd templates //进入 templates 文件夹
|
||||
/usr/local/bin/yarn install // 等待Yarn安装完成,其实你也可以npm install ,就是会慢一点
|
||||
yarn config set registry https://registry.npm.taobao.org/ //更改yarn为国内源
|
||||
/usr/local/bin/quasar d // 使用quasar命令启动前端页面
|
||||
// 前端会向 "127.0.0.1:8008"发请求, 在这里我们只是看下项目是不是可以运行
|
||||
Ctrl + C // 退回到templates文件夹
|
||||
|
||||
从2.0.19版本以后,优化了请求地址修改方式,直接修改templates/dist/spa/statics/baseurl.js,中的baseurl和wsurl,就可以成功更改前端请求地址,不再需要做下面的quasar build打包工作。
|
||||
|
||||
如果需要修改前端内容,则还需要修改templates/public/statics/baseurl.js中的baseurl和wsurl,然后重新使用quasar build进行打包
|
||||
|
||||
cd src/boot // 进入在src/boot文件夹
|
||||
vim axios_request.js // 我们开始更改请求地址
|
||||
// 更改 "127.0.0.1" 成你的内网IP, baseurl 是http请求地址 , ws 是 websocket请求地址
|
||||
按下 Esc 然后输入 ":wq" 去保存修改
|
||||
// 现在,你已经知道怎么部署和修改请求地址了
|
||||
/usr/local/bin/quasar build // 需要对修改进行重新打包
|
||||
cd .. // 回到GreaterWMS文件夹
|
||||
/usr/local/bin/daphne -b 0.0.0.0 -p 8008 greaterwms.asgi:application
|
||||
// 现在,打开浏览器,输入 "你的内网IP:8008",你可以看到项目已经运行了
|
||||
|
||||
谢天谢地!!!
|
||||
|
||||
顺便说一句
|
||||
1. 你知道了怎么修改请求地址
|
||||
2. 你也可以按你的喜好,去更改Port
|
||||
3. 你更可以使用Nginx或者Apache,把项目发布到互联网上
|
||||
~~~
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
~~~shell
|
||||
su - // Enter root
|
||||
yum update
|
||||
yum upgrade
|
||||
yum -y install gcc-c++
|
||||
yum install zlib-devel xz-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel libffi-devel gcc make
|
||||
yum install nodejs
|
||||
npm install n -g
|
||||
/usr/local/bin/n 14.18.3 // update nodejs
|
||||
npm install npm -g
|
||||
npm install yarn -g
|
||||
npm install @quasar/cli -g
|
||||
npm install cordova -g
|
||||
cd /usr/src
|
||||
wget https://www.python.org/ftp/python/3.9.5/Python-3.9.5.tgz // download python3.9.5
|
||||
tar -zxvf Python-3.9.5.tgz
|
||||
cd Python-3.9.5/
|
||||
./configure --enable-optimizations
|
||||
make altinstall
|
||||
mv /usr/bin/python3 /usr/bin/python3.bak // backup python3
|
||||
ln -s /usr/local/bin/python3.9 /usr/bin/python3
|
||||
mv /usr/bin/pip3 /usr/bin/pip3.bak // backup pip3
|
||||
ln -s /usr/local/bin/pip3.9 /usr/bin/pip3
|
||||
yum install git // install git
|
||||
git clone https://github.com/Singosgu/GreaterWMS.git // clone GreaterWMS from github
|
||||
chmod -R 755 GreaterWMS
|
||||
cd GreaterWMS
|
||||
pip3 install -r requirements.txt
|
||||
python3 manage.py makemigrations
|
||||
python3 manage.py migrate
|
||||
/usr/local/bin/daphne -p 8008 greaterwms.asgi:application
|
||||
// open Chrom "127.0.0.1:8008/myip", you will get your internal lan ip, remember it
|
||||
Ctrl + C // back GreaterWMS folder
|
||||
cd templates // enter templates folder
|
||||
/usr/local/bin/yarn install
|
||||
/usr/local/bin/quasar d // it will start web site
|
||||
Ctrl + C // back templates folder
|
||||
cd src/boot
|
||||
vim axios_request.js // change request baseurl
|
||||
// change "127.0.0.1" to your internal IP, baseurl is for http, ws is for websocket
|
||||
// save it
|
||||
/usr/local/bin/quasar build // build your web site
|
||||
cd .. // back to GreaterWMS folder
|
||||
/usr/local/bin/daphne -b 0.0.0.0 -p 8008 greaterwms.asgi:application
|
||||
// now,you can use "internal IP:8008" to use greaterwms
|
||||
@@ -0,0 +1 @@
|
||||
22a265356ea69c3d30d96cfaaa591988adcc37b1
|
||||
@@ -0,0 +1 @@
|
||||
e1c14c564fee6800193b329912b295539a0f39ce
|
||||
@@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
https://www.56yhz.com
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1 @@
|
||||
3ea6e343625852897cca8991854acf08d3f46700
|
||||
@@ -0,0 +1,337 @@
|
||||
# GreaterWMS--Open Source Warehouse Management System
|
||||
|
||||
<p><div align=center><img width="150" height="150" src="https://github.com/Singosgu/GreaterWMS/blob/master/static/img/logo.png?raw=true?raw=true"/></div></p>
|
||||
|
||||
---
|
||||
|
||||
[中文文档](https://github.com/Singosgu/GreaterWMS/blob/master/README_zh_Hans.md)
|
||||
|
||||
## Production Introduce:
|
||||
|
||||
Fully open source warehouse management system follows Apache License 2.0 and front-back stage decoupling method. API uses restful protocol to facilitate for add-on functions development. The html & js code is constructed with quasar(base on Vue.js v2.6.0+). According to API, it can support business models such as multi warehouse, wave shipment, combined picking and milk-run and so on.
|
||||
|
||||
- Software Copyright Procedures: 10136559
|
||||
- GitHub Link: [GitHub](https://github.com/Singosgu/GreaterWMS)
|
||||
- Gitee Link: [Gitee](https://gitee.com/Singosgu/GreaterWMS)
|
||||
- Video Tutorials: [YouTube](https://www.youtube.com/channel/UCPW1wciGMIEh7CYOdLnsloA)(All Tutorials Will Upload To Here)
|
||||
- Home Page: [DEMO](https://www.56yhz.com/)(Register can initial the demo data)
|
||||
- Mail:mail@56yhz.com
|
||||
- CIMO-ADMIN(vue-quasar-manage): [GitHub](https://github.com/972784674t/vue-quasar-manage) | [Gitee](https://gitee.com/incimo/vue-quasar-manage)
|
||||
|
||||
---
|
||||
|
||||
## Our Original Intention:
|
||||
|
||||
I have 15 years old experience focus on supply chain . I find that in this professional field . No freedom customize software can support our business deeply . Any software are closed-source and hardly to customize or dynamic with our suppliers & customers . So I design GreaterWMS , in order to give business highest freedom way to support trade development .
|
||||
|
||||
- Our Vision: If you work in a non IT industry and you love your industry, please using technology to change it.
|
||||
|
||||
---
|
||||
|
||||
## Development Environment:
|
||||
|
||||
- Python Version 3.9.5 +
|
||||
|
||||
- Django Version 3.1.12 +(This version of Django only supports asynchronous real-time communication)
|
||||
|
||||
- Django-rest-framework Version 3.12.2 + (Highest versions of Django-rest-framework are more compatible with Django3)
|
||||
|
||||
- Django-silk Version 4.1.0 (If you are deploying production, please turn off silk, which is only used for debugging API interface speed, which may leak users' information)
|
||||
|
||||
- Quasar Version 1.7.2 + (You can view the official website of quasar to edit the webside code of greater WMS: [Quasar](https://quasar.dev/))
|
||||
|
||||
- Vue Version 2.6.0 +(Try not to use vue3, because the development environment does not use vue3, I don't know what will happened)
|
||||
- API,Follow RESTful
|
||||
|
||||
---
|
||||
|
||||
## Build Command:
|
||||
|
||||
- Git Clone:
|
||||
|
||||
~~~shell
|
||||
git clone https://github.com/Singosgu/GreaterWMS.git
|
||||
~~~
|
||||
|
||||
- Install Python Library:
|
||||
|
||||
~~~python
|
||||
pip install -r requirements.txt
|
||||
~~~
|
||||
|
||||
Atention: `Installation requires Twisted library, this library sometimes cannot be installed, you need to download it and install it locally`
|
||||
|
||||
- Download Link:[TWISTED](https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted)
|
||||
|
||||
~~~python
|
||||
pip install Twisted{Version Name Which You Download}
|
||||
~~~
|
||||
|
||||
Atention: `Local installation needs to pay attention to the path`
|
||||
|
||||
- Makemigrations the Database:
|
||||
|
||||
~~~python
|
||||
python manage.py makemigrations
|
||||
~~~
|
||||
|
||||
- Migrate Database:
|
||||
|
||||
~~~python
|
||||
python manage.py migrate
|
||||
~~~
|
||||
|
||||
Create a database. Django uses sqlite3 as the default database. If you need other database, please configure DATABASE in greaterwms/settings.py
|
||||
|
||||
### Run Development Server:
|
||||
|
||||
- Dev Run:
|
||||
|
||||
~~~python
|
||||
daphne -p 8008 greaterwms.asgi:application
|
||||
~~~
|
||||
|
||||
### Run Production Server:
|
||||
|
||||
- supervisor Daemon:
|
||||
|
||||
~~~shell
|
||||
pip install supervisor
|
||||
~~~
|
||||
|
||||
Use supervisor to guard the Django process, and then use Nginx as a reverse proxy. As for supervisors, there are many tutorials, so I won’t explain them here.
|
||||
|
||||
- Nginx Support:
|
||||
|
||||
It is recommended to use Nginx for deployment. You need to specify the WebSocket link when deploying. If you do not specify it, the real-time communication function will report an error.
|
||||
|
||||
Also need to modify ws_url in templates/dist/spa/statics/baseurl.js
|
||||
|
||||
~~~shell
|
||||
## Example changes before
|
||||
const baseurl = 'http://127.0.0.1:8008/'
|
||||
const wsurl = 'ws://127.0.0.1:8008/'
|
||||
|
||||
## Example changes after
|
||||
const baseurl = 'https://Your Production Server/'
|
||||
const wsurl = 'wss://Your Production Server/websocket/'
|
||||
~~~
|
||||
|
||||
If the server has SSL enabled, please use https and wss, if SSL is not enabled, use http and ws
|
||||
|
||||
The front-end code needs to be rebuilt after modification
|
||||
|
||||
---
|
||||
|
||||
## Development Guide:
|
||||
|
||||
### Baseurl
|
||||
|
||||
- It is the basic URL for initiating the request. If it is for local debugging, the default is http://127.0.0.1:8008/. If it is deployed on the server, you need to change it to your website access URL
|
||||
|
||||
- The modification method is to modify axios_request.js. Attention: `The modification of websocket has been mentioned `
|
||||
|
||||
### Django-Silk
|
||||
|
||||
- Django-Silk is a debugging tool during development. It can count the response speed of each interface. If you need to deploy to a production environment, please delete the Django-silk related configuration, because there is a risk of leaking user information, or directly modify the Django-silk library , So that users can only see their request data
|
||||
|
||||
### Database Storage
|
||||
|
||||
- Data migration and other issues are considered during database design, so only the user_id in users and the user_id that comes with Django are foreign keys, and all the other fields do not use foreign keys, which is convenient for data backup and database migration.
|
||||
- The database is a 4-stage design
|
||||
|
||||
1. Verify data user ownership
|
||||
2. Verify data security
|
||||
3. Verify that the data can be stored in the database
|
||||
4. Save it in the database and return Response
|
||||
|
||||
### About Data Request
|
||||
|
||||
- The token value needs to be added to all request headers. This value is the unique identifier OPENID of the user’s data
|
||||
- All data transmission needs to set content-type to application/json
|
||||
|
||||
### OPENID
|
||||
|
||||
- OPENID is the unique identifier of registered user data. When the administrator registers directly, there will be developer=1 as the administrator ID.
|
||||
- You can do custom secondary development based on the developer label
|
||||
|
||||
### APPID
|
||||
|
||||
- APPID is the unique identifier of the user data group
|
||||
- If you need multi company operation or multi warehouse operation, you can make unified link through appid to realize multi company and multi warehouse operation
|
||||
|
||||
### User Jurisdiction
|
||||
|
||||
- There are not too many restrictions on user authorization. Please limit the secondary development according to your own business needs
|
||||
|
||||
---
|
||||
|
||||
## Business Process:
|
||||
|
||||
### Administrator
|
||||
|
||||
- Click Register, you can register as an administrator account, so as to realize the initialization program settings
|
||||
- After registration, you will get two IDs and one developer label. openid is the unique ID of the user data group. All data under this openid is bound through openid. Appid is the unique ID of the user group data. Multi company and multi warehouse functions are realized through appid. The developer ID is a Boolean value, and true represents an administrator account
|
||||
- There are two way for user login:
|
||||
1. Login directly with openid and staff name
|
||||
2. Administrator login with account and password
|
||||
- After login, the web will localstorage login information
|
||||
- you can view the openid of user data group by view my openid
|
||||
- If multi company and multi warehouse operation is required, pay attention to change openid
|
||||
- More administrator authrization, please develop by yourself
|
||||
|
||||
### Staff
|
||||
|
||||
- After registering as an administrator, create a new staff first
|
||||
- Staff has two fields, staff_ Name (for employee login), staff_ Type (employee type to control employee permissions)
|
||||
- The system does not have any restrictions on employee permissions. If you need employee permissions, please modify them according to the enterprise business model Templates
|
||||
- Click Edit to modify the employee information
|
||||
- Click Delete to delete the staff information. The system will set is_delete to true
|
||||
- Click Contact:
|
||||
1. You can directly chat with employees in real time, but you can't chat with yourself
|
||||
2. You can create a new notebook employee, which is actually used as a notebook
|
||||
3. In the personal center, you can view recent contacts
|
||||
4. The message flag will remind you how many unread messages you have
|
||||
|
||||
### Driver
|
||||
|
||||
- Driver management is only used in the shipping process
|
||||
- You need to know which driver picked up the goods
|
||||
|
||||
### Warehouse Set
|
||||
|
||||
- Warehouse
|
||||
1. You can create only one warehouse. Now you can create multiple warehouses, but only the first one will work
|
||||
2. If multi warehouse processing is needed, secondary development can be carried out through appid, or an administrator account can be created directly
|
||||
3. The city of the warehouse must be filled in, which is used to calculate the freight
|
||||
- Bin_Property
|
||||
1. The bin property determines the property of the goods in the warehouse
|
||||
2. Four property: damage, holding, inspection, normal
|
||||
3. In the beta version, property can be modified and deleted, but the release version can not
|
||||
4. All shipping process will only match the goods in the normal bin
|
||||
5. When goods are received and moved to another bin, the inventory quantity will be directly modified according to the bin property, and the inventory quantity of the warehouse will not be negative
|
||||
- Bin_Size
|
||||
1. The size of bin is to help the operator to check whether the goods can be put into the bin
|
||||
2. The current version does not check the dimensions of bin, and automatic inspection will be added in the future
|
||||
- Bin_Set
|
||||
1. Bin setting is necessary. Generally, bin setting is horizontal, vertical and horizontal, such as A010101, i.e. A horizontal, 01 vertical, 01 horizontal and 01 vertical
|
||||
2. The setting of bin needs to set the bin property and size. The property is very important, which determines whether the goods in this bin are normal goods
|
||||
|
||||
### Basic Info
|
||||
|
||||
- Company
|
||||
1. The creation of basic company information can only create one company. Now you can create multiple companies, but only the first one will work
|
||||
2. If multi company processing is needed, secondary development can be carried out through appid, or an administrator account can be created directly
|
||||
3. The city of the company must be filled in, which is used to show on the receipt-proof
|
||||
- Supplier
|
||||
1. Basic information of suppliers
|
||||
2. The city of the supplier must be filled in, which is used to display on the receipt-proof, and also to calculate the freight automatically
|
||||
- Customer
|
||||
1. Basic information of customers
|
||||
2. The customer's city must be filled in, which is used to display on the invoice, and also to automatically calculate the freight
|
||||
|
||||
### Godds
|
||||
|
||||
- Unit
|
||||
1. Goods units, the system will initialize to create some, but you can add and modify
|
||||
- Class
|
||||
1. Goods Class, you can add and modify
|
||||
- Color
|
||||
1. Goods color, the system will initialize to create some, but you can add and modify
|
||||
- Brand
|
||||
1. Goods brand, you can add and modify
|
||||
- Shape
|
||||
1. Goods Shape, the system will initialize to create some, but you can add and modify
|
||||
- Specs
|
||||
1. Goods specs, you can add and modify
|
||||
- Origin
|
||||
1. Goods Origin, where initial product goods, you can add and modify
|
||||
- Goods List
|
||||
1. Goods list
|
||||
|
||||
### Capital
|
||||
|
||||
- Capital
|
||||
1. The creation of fixed assets, not too much expansion, just record the use
|
||||
2. Can be statistical pallets accounts
|
||||
|
||||
### Stock Management
|
||||
|
||||
- Stock List
|
||||
1. Total inventory data of goods
|
||||
2. Onhand_ Stock quantity on hand
|
||||
3. Can order, which can be used to determine the inventory quantity of an order. Some goods have been ordered, but they can't be ordered any more even though they have existing inventory
|
||||
4. Ordered stock, the quantity of goods be ordered
|
||||
5. ASN stock has issued the arrival notice, but has not confirmed the quantity of goods in the arrival notice
|
||||
6. DN stock, has been ordered, but the order quantity has not been confirmed
|
||||
7. Pre Load,expected quantity of goods delivered
|
||||
8. Pre Sort,the quantity of goods that have arrived, unloaded and waiting to be sorted
|
||||
9. Sorted Stock, The quantity of goods waiting to be put on the bin after sorting
|
||||
10. Pick Stock, The picking list is generated from the delivery document, and the quantity of goods waiting to be picked
|
||||
11. Picked Stock, The quantity of goods that have been picked up and waiting to be handed over to the driver
|
||||
12. Back Order Stock, Order quantity in arrears
|
||||
- Bin Stock
|
||||
1. Total Stock, all inventory quantities of the product in this bin
|
||||
2. Pick Stock, the quantity of goods to be picked in this bin
|
||||
3. Picked stock, the quantity of goods picked in this bin
|
||||
4. Move to Bin: after moving, the inventory quantity will be updated directly according to the bin property. If all goods has been moved out from this bin, the bin will be updated to empty
|
||||
- Empty Bin
|
||||
1. Empty bin List
|
||||
- Occupied Bin
|
||||
1. Occupied bin list
|
||||
|
||||
### Inbound
|
||||
|
||||
- ASN
|
||||
1. ASN status = 1, the ASN arrival notice is created, and status 1 is the only status that can delete and modify the ASN information. It will be displayed in pre delivery, that is, there is an arrival notice, but it has not arrived. Click confirm delivery to confirm that the goods have arrived, and ASN status will be updated to 2. At this time, the ASN information can no longer be modified
|
||||
2. ASN status = 2, it is developed to queue up for the arrival of drivers. If we have many drivers arriving, it can be made into a queuing system. At the same time, it can also let the purchase and sales see the arrival information, reduce unnecessary email and telephone communication. Click finish loading to confirm that the goods have been unloaded, and Asn status will be updated to 3, The goods information will appear in sorting, and the ASN status indicates that the goods have been unloaded to the warehouse and are waiting for sorting
|
||||
3. ASN status = 3, goods sorting is a necessary process. Without goods sorting, goods cannot be put on shelves. The principle of putting on shelves is to arrange the goods and put them on the corresponding warehouse location. Click confirm sorted, and Asn status will be updated to 4, that is, confirm the sorting and wait for loading
|
||||
4. At this time, when you move the sorted page, the goods details that need to be put on the shelves will appear. Click move to bin to finish the listing. Of course, the system will automatically update the inventory quantity information of goods according to the location attributes after the listing
|
||||
|
||||
### Outbound
|
||||
|
||||
- DN
|
||||
1. DN status = 1, when the DN shipping order is created, the order status can still be modified, and the inventory quantity in the system will not change. Click confirm order, and the DN status will be updated to 2, that is, the order has been confirmed and cannot be changed. At the same time, the inventory quantity in the system will be automatically updated, such as can order quantity and ordered quantity
|
||||
2. DN Status = 2, This is the process when an order is confirmed and waiting to generate a picking list. You can click order release of a single order to generate a picking list of an order. You can also click release all order to generate a picking list of all orders. If all orders are released, the inventory will be matched according to the time sequence. When the inventory is insufficient, back will be generated In this process, the DN order number will change. For example, multiple orders of a customer will be unified into one order for picking. If the customer's order cannot be satisfied, the unsatisfied part will be generated as a shortage order. If the shortage order is not met by the matched inventory, no new order will be generated. Dn The status will be updated to 3, that is, in the process of waiting for picking, the status will be 2 when both the confirmed order and the shortage order are in the same status
|
||||
3. DN Status = 3,Direct picking, this function will appear in beta5 update
|
||||
4. DN Status = 4, Delivery handover, this function will appear in beta6 update
|
||||
5. DN Status = 5, Customer receiving, this function will appear in beta7 update
|
||||
6. DN Status = 6, When the reconciliation is finished and the order is closed, this function will appear in the beta7 update
|
||||
|
||||
### Reject Order
|
||||
|
||||
- RO
|
||||
This feature will add in the release version
|
||||
|
||||
### Screen Shot
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/inbound.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/outbound.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/stock.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/finace.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/goods.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/baseinfo.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/warehouse.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/staff.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/driver.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/api.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/chat.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img src="https://github.com/Singosgu/picfile/blob/master/CN/app1.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img src="https://github.com/Singosgu/picfile/blob/master/CN/app2.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img src="https://github.com/Singosgu/picfile/blob/master/CN/app3.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img src="https://github.com/Singosgu/picfile/blob/master/CN/app4.png?raw=true"/></div></p>
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
# 聚商汇WMS--开源仓库管理系统
|
||||
|
||||
<p><div align=center><img width="150" height="150" src="https://github.com/Singosgu/GreaterWMS/blob/master/static/img/logo.png?raw=true?raw=true"/></div></p>
|
||||
|
||||
---
|
||||
|
||||
## 项目介绍:
|
||||
|
||||
完全开源仓储管理软件,遵循Apache License 2.0协议,前后端分离,且完全开源,API使用restful协议,方便二次开发,前端代码使用quasar进行构建,后端使用Python Django3.1,利用API,可以支持多仓,波次发货,合并拣货,Milk-Run等业务模型。
|
||||
|
||||
- 软件著作权编号:2018SR517685
|
||||
- GitHub地址:[GitHub](https://github.com/Singosgu/GreaterWMS)
|
||||
- Gitee地址:[Gitee](https://gitee.com/Singosgu/GreaterWMS)
|
||||
- 视频教程:[B站](https://space.bilibili.com/407321291/channel/detail?cid=152043)(所有的教程都会更新在这里)
|
||||
- Demo地址:[DEMO](https://www.56yhz.com/)(注册会获得初始化Demo数据)
|
||||
- 技术交流群:
|
||||
- 邮箱:mail@56yhz.com
|
||||
- CIMO-ADMIN(vue-quasar-manage): [GitHub](https://github.com/972784674t/vue-quasar-manage) | [Gitee](https://gitee.com/incimo/vue-quasar-manage)
|
||||
|
||||
---
|
||||
|
||||
## 项目初衷:
|
||||
|
||||
我在供应链行业工作了15年,发现在我们这个专业的领域,没有一款高自由度、高自定义化的软件,来深度支持我们企业的业务。大多数软件都是闭源的,而且很难去做二次开发,即使开发,周期也是非常长,开发失败的案例也是比比皆是。由于企业选择了一款软件后,其二次开发也会被开发公司绑定,至于二次开发费用,只能说呵呵。所以,我设计了这个聚商汇WMS,为的是做到一款高自由度,高自定义开发的仓库管理软件,来深度支持企业的业务。
|
||||
|
||||
- 愿景:如果你从事着非IT行业的工作,而你又热爱你的行业,那就用科技去改变他。
|
||||
|
||||
---
|
||||
|
||||
## 生命周期
|
||||
|
||||
- V 1.0.0 -- 2019年7月 ~ 2020年12月(由于1.0.0版本的二次开发设计较为复杂,故2.0重新编写)
|
||||
- V 2.0.0 -- 2020年12月 ~ 2021年3月(重新编写业务逻辑,原生自带API开发文档,加入实时通信,方便企业用户互相沟通)
|
||||
- V 2.1.0 -- 2021年3月 ~ 2021年6月(加入了客户与企业之间的实时互动,增进企业与客户之间的业务联系,实现VMI)
|
||||
- V 2.2.0 -- 2020年6月 ~ 2021年9月(加入了供应商与企业之间的实时互动,增进企业与供应商之间的业务联系,实现Milk-Run和看板拉动)
|
||||
- V 2.3.0 -- 2021年9月 ~ 2021年12月(库存管理雏形,初步加入神经网络,深度学习库存变化)
|
||||
- V 3.0.0 -- 2021年12月 ~ 2022年3月(完全植入神经网络,让上下游企业可以以最低的成本运营整体的业务)
|
||||
- V 3.1.0 -- 2022年3月 ~ 2022年6月(区域仓库业务布局,通过深度学习,实现多仓运营,成本最低化)
|
||||
|
||||
---
|
||||
|
||||
## 开发环境:
|
||||
|
||||
- Python 版本为 V 3.8.0 +
|
||||
|
||||
- Django 版本为 V 3.1.0 +(该版本Django才原生支持异步实时通信)
|
||||
|
||||
- Django-rest-framework 版本为 V 3.12.2 + (更高版本的Django-rest-Framework对Django3的兼容比较好)
|
||||
|
||||
- Django-silk 版本为 V 4.1.0 (如果是部署上线,请关闭silk,silk仅为调试API接口速度用,有可能会泄露用户信息)
|
||||
|
||||
- Quasar 版本为 V1.7.2 + (可以查看Quasar官网,来编辑GreaterWMS前端代码:[Quasar官网](http://www.quasarchs.com/))
|
||||
|
||||
- Vue 版本为 V 2.6.0 +(尽量不要使用Vue3,因为开发环境没有使用Vue3,不知道会出现什么问题)
|
||||
- API,遵循 RESTful 架构
|
||||
|
||||
---
|
||||
|
||||
## 构建命令:
|
||||
|
||||
- 下载代码:
|
||||
|
||||
~~~shell
|
||||
git clone https://github.com/Singosgu/GreaterWMS.git
|
||||
~~~
|
||||
|
||||
- 安装Python库:
|
||||
|
||||
~~~python
|
||||
pip install -r requirements.txt
|
||||
~~~
|
||||
|
||||
注意:`安装需要Twisted库,这个库有时候会安装不上,需要下载下来本地安装`
|
||||
|
||||
- 下载地址:[TWISTED](https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted)
|
||||
|
||||
~~~python
|
||||
pip install Twisted{你下载下来的版本名称}
|
||||
~~~
|
||||
|
||||
注意:`本地安装需要注意路径`
|
||||
|
||||
- 初始化数据库:
|
||||
|
||||
~~~python
|
||||
python manage.py makemigrations
|
||||
~~~
|
||||
|
||||
- 迁移数据库:
|
||||
|
||||
~~~python
|
||||
python manage.py migrate
|
||||
~~~
|
||||
|
||||
创建数据库,Django默认使用sqlite3作为数据库,如果需要mysql数据库,请在greaterwms/settings.py里面配置DATABASE
|
||||
|
||||
### 开发服务器运行:
|
||||
|
||||
- 开发运行:
|
||||
|
||||
~~~python
|
||||
daphne -p 8008 greaterwms.asgi:application
|
||||
~~~
|
||||
|
||||
### 生产服务器运行:
|
||||
|
||||
- supervisor守护进程:
|
||||
|
||||
~~~shell
|
||||
pip install supervisor
|
||||
~~~
|
||||
|
||||
使用supervisor来守护Django进程,再使用Nginx做反向代理,至于superevisor的教程有很多,这里不做讲解
|
||||
|
||||
- Nginx支持:
|
||||
|
||||
推荐使用Nginx进行部署,部署的时候需要指定WebSocket链接,如果不指定,实时通信功能将报错
|
||||
|
||||
另需要修改从2.0.19版本以后,优化了请求地址修改方式,直接修改templates/dist/spa/statics/baseurl.js,中的baseurl和wsurl,就可以成功更改前端请求地址,不再需要做下面的quasar build打包工作。
|
||||
|
||||
如果需要修改前端内容,则还需要修改templates/public/statics/baseurl.js中的baseurl和wsurl,然后重新使用quasar build进行打包里的ws_url
|
||||
|
||||
~~~shell
|
||||
## 示例更改前
|
||||
const baseurl = 'http://127.0.0.1:8008/'
|
||||
const wsurl = 'ws://127.0.0.1:8008/'
|
||||
|
||||
## 示例更改后
|
||||
const baseurl = 'https://你的域名/'
|
||||
const wsurl = 'wss://你的域名/websocket/'
|
||||
~~~
|
||||
|
||||
如果服务器启用了SSL,请使用https和wss,如果没有启用SSL,则使用http和ws
|
||||
|
||||
修改后需要重新build前端代码
|
||||
|
||||
---
|
||||
|
||||
## 开发扩展:
|
||||
|
||||
因为使用的前后端分离的设计,所以可以通过API,开发更多的软件应用
|
||||
|
||||
### 物流智能AGV
|
||||
|
||||
- AGV的项目也已经开源,由于场地受限,仅实现智能发货,定点回库,使用的循迹感应器,超声波避障感应器,红外避障感应器,所有的指令通过网络传输,AGV绑定MAC地址和IP地址,保证了安全性,前提是,你需要有一个树莓派。
|
||||
|
||||
### 进销存
|
||||
|
||||
- 可以直接当一个进销存系统使用,简化仓库库位设置等操作即可。
|
||||
|
||||
### APP和小程序
|
||||
|
||||
- Quasar原生可以直接打包成IOS APP和Android APP
|
||||
- 小程序的开发可以通过API开做二次开发,但小程序不支持put请求,所以需要自己再写一个请求接口。
|
||||
|
||||
- API的组合可以达到100万种,这样我们可以根据查询请求,来获得实时报表和数据监控
|
||||
|
||||
### 供应链管理系统
|
||||
|
||||
- 产品的数量,创建时间,最后使用时间是各方面统计的,所以可以方便采购计划和调拨计划进行库存的分析
|
||||
- V 2.3.0及其以后的版本,将自带深度学习分析,所以可以直接使用分析结果作为供应链管理系统工具使用
|
||||
|
||||
### 多仓管理
|
||||
|
||||
- OPENID为用户的数据唯一标识,数据组统一标识为APPID,所以很方便可以实现多仓管理
|
||||
|
||||
### 波次拣货,发货
|
||||
|
||||
- 可以设置固定时间向服务器发出请求,从而达到波次拣货的功能
|
||||
|
||||
- 也可以直接使用任务工作,通过API查询分析结果来实现,推荐使用[APScheduler](https://pypi.org/project/APScheduler/)
|
||||
|
||||
~~~python
|
||||
pip install apscheduler
|
||||
~~~
|
||||
|
||||
### Milk-Run
|
||||
|
||||
- V 2.2.0及其以上版本,将原生支持此功能
|
||||
- 如果现在就需要这个业务,可以根据API调用库存消耗,来实现此功能
|
||||
|
||||
### VMI
|
||||
|
||||
- V 2.1.0及其以上版本,将原生支持此功能
|
||||
- 如果现在就需要这个业务,可以根据API调用库存消耗,来实现此功能
|
||||
|
||||
### 拣货路线优化
|
||||
|
||||
- 现在的拣货路线是按照库位排序
|
||||
- V 2.3.0以后版本将原生支持此功能
|
||||
- 如果现在需要这个业务,可以根据每天的拣货明细,调用API来实现此功能
|
||||
|
||||
---
|
||||
|
||||
## 开发指南:
|
||||
|
||||
### baseurl
|
||||
|
||||
- 是发起请求的基本网址,如果是本地调试,则默认为http://127.0.0.1:8008/ ,如果部署在服务器,则需要将其改为你的网站访问url
|
||||
|
||||
- 修改方式为,修改axios_request.js,注意`websocket的修改之前已经提到了`
|
||||
|
||||
### Django-silk
|
||||
|
||||
- django-silk为开发时的调试工具,可以统计每个接口的响应速度,如果需要部署到生产环境,请删除Django-silk相关配置,因为会有泄露用户信息的风险,或者直接修改Django-silk库,让用户只能看到自己的请求数据
|
||||
|
||||
### 数据库存储
|
||||
|
||||
- 数据库设计时考虑到数据迁移等问题,所以只有users里面的user_id和Django自带的user_id做了外键,其余所有字段全部没有使用外键,方便数据备份和数据库迁移
|
||||
- 数据库是4段式设计
|
||||
1. 验证数据用户归属
|
||||
2. 验证数据安全性
|
||||
3. 验证数据是否可以存入数据库
|
||||
4. 存入数据库,并返回Response
|
||||
|
||||
### 关于数据传输
|
||||
|
||||
- 需要在所有的请求头headers里面加入token值,这个值就是用户的数据唯一标识OPENID
|
||||
- 所有的数据传输需要设定content-type为application/json
|
||||
|
||||
### OPENID
|
||||
|
||||
- OPENID是注册用户数据的唯一标识,当管理员直接注册时,会有developer=1这个管理员标识。
|
||||
- 你可以根据developer标识来做自定义二次开发
|
||||
|
||||
### APPID
|
||||
|
||||
- APPID是用户数据组唯一标识
|
||||
- 如果需要多公司运营,或者多仓运营,可以通过APPID做统一链接,来实现多公司,多仓操作
|
||||
|
||||
### 用户权限
|
||||
|
||||
- 未对用户权限做过多限制,请根据自身的业务需要,做二次开发限制
|
||||
|
||||
---
|
||||
|
||||
## 业务流程:
|
||||
|
||||
### 管理员
|
||||
|
||||
- 点击注册,可以注册成为管理员账号,从而实现初始化程序设置
|
||||
- 注册后会得到2个ID和1个开发者标识,OPENID是用户数据组唯一标识,通过OPENID绑定此OPENID下所有的数据,APPID是用户组数据唯一标识,通过APPID来实现多公司,多仓库功能,Developer标识是个布尔值,True代表这是个管理员账号
|
||||
- 用户登入分2种:
|
||||
1. 使用OPENID和员工名称直接登入
|
||||
2. 管理员使用账号和密码登入
|
||||
- 登入后前端会存储登入信息
|
||||
- 可以通过查看我的OPENID来查看用户数据组的OPENID
|
||||
- 如果需要多公司,多仓库操作,注意需要更改OPENID
|
||||
- 更多管理员权限,请自行开发
|
||||
|
||||
### 员工管理
|
||||
- 注册管理员后,新建一个员工
|
||||
- 员工有2个字段,Staff_name(用于员工登入),Staff_type(员工类型来控制员工的权限)
|
||||
- 系统没有对员工权限做任何限制,如果需要员工权限,请根据企业业务模型,自行修改Templates
|
||||
- 点击Edit,可以修改员工信息
|
||||
- 点击Delete,可以删除员工信息,系统后台会将Is_delete调成True
|
||||
- 点击Contact:
|
||||
1. 可以直接和员工实时聊天,但是不可以和自己聊天
|
||||
2. 可以新建一个备忘录员工,这样做其实是当成备忘录使用
|
||||
3. 在个人中心,可以查看最近的联系人
|
||||
4. Message标识会提醒你现在有多少未读消息
|
||||
|
||||
### 司机管理
|
||||
- 司机管理只会在发货流程中用到
|
||||
- 你需要知道货物是哪个司机提货取走的
|
||||
|
||||
### 仓库设置
|
||||
- Warehouse
|
||||
1. 仓库的创建只可以创建一个仓库,现在可以创建多个,但是只有第一个会起作用
|
||||
2. 如果需要多仓处理,可以通过APPID进行二次开发,也可以直接重新创建一个管理员账号
|
||||
3. 仓库的城市一定要填写,这是用来计算运费的
|
||||
- Bin_Property
|
||||
1. 库位属性决定了仓库中货物属于什么属性的货物
|
||||
2. 4种属性:破损(Damage),锁定(Holding),质检(Inspection),正常(Normal)
|
||||
3. Beta版中,属性可以修改和删除,正式版将无法删除和修改
|
||||
4. 所有的发货,都只会匹配Normal库位的货物
|
||||
5. 收货上架和移库,都会根据库位属性,直接修改库存数量,仓库的库存数量不会出现负数
|
||||
- Bin_Size
|
||||
1. 库位的尺寸是帮助操作人员查看货物是否可以放入库位
|
||||
2. 现行的版本没有对上架和移库尺寸做检查,将来会加入自动检查
|
||||
- Bin_Set
|
||||
1. 库位设置是必须的,通常库位设置是横纵横纵,比如A010101,即A横01纵01横01纵
|
||||
2. 库位的设置需要设置库位属性和尺寸,属性很重要,他决定了此库位的货物是否为正常货物
|
||||
|
||||
### 基础设置
|
||||
- Company
|
||||
1. 公司基本信息的创建只可以创建一个公司,现在可以创建多个,但是只有第一个会起作用
|
||||
2. 如果需要多公司处理,可以通过APPID进行二次开发,也可以直接重新创建一个管理员账号
|
||||
3. 公司的城市一定要填写,这是用来显示在收发货单上的
|
||||
- Supplier
|
||||
1. 供应商的基础信息
|
||||
2. 供应商的城市一定要填写,这是用来显示在收货单上的,并且也是要自动计算运费的
|
||||
- Customer
|
||||
1. 客户的基础信息
|
||||
2. 客户的城市一定要填写,这是用来显示在发货单上的,并且也是要自动计算运费的
|
||||
|
||||
### 商品管理
|
||||
- Unit
|
||||
1. 商品的单位,系统会初始化创建一些,但可以自己添加和修改
|
||||
- Class
|
||||
1. 商品的类型,可以自己添加和修改
|
||||
- Color
|
||||
1. 商品的颜色,系统会初始化创建一些,但可以自己添加和修改
|
||||
- Brand
|
||||
1. 商品的品牌,可以自己添加和修改
|
||||
- Shape
|
||||
1. 商品的形状,系统会初始化创建一些,但可以自己添加和修改
|
||||
- Specs
|
||||
1. 商品的规格,可以自己添加和修改
|
||||
- Origin
|
||||
1. 商品的产地,可以自己添加和修改
|
||||
- Goods List
|
||||
1. 商品的列表
|
||||
|
||||
### 固定资产
|
||||
- Capital
|
||||
1. 固定资产创建,没有做过多拓展,只是记录使用
|
||||
2. 可以统计托盘账目等
|
||||
|
||||
### 库存管理
|
||||
- Stock List
|
||||
1. 在库的货物总的库存数据量
|
||||
2. Onhand_stock现有的库存数量
|
||||
3. Can Order,可以用于下单发货的库存数量,因为有些货物已经被下了订单,虽然有现有库存,但是不可以再被订货
|
||||
4. Ordered Stock,已经被下单的货物数量
|
||||
5. ASN Stock,已经下了到货通知书,但还没有确认到货通知书的货物数量
|
||||
6. DN Stock,已被下单,但是还没有确认订单数量
|
||||
7. Pre Load,预计到货货物数量
|
||||
8. Pre Sort,已经到货,卸货完成,等待分拣的货物数量
|
||||
9. Sorted Stock,货物分拣完成,等待上架的货物数量
|
||||
10. Pick Stock,发货单生成了拣货单,等待拣货的货物数量
|
||||
11. Picked Stock,已经拣货完成,等待和司机交接的货物数量
|
||||
12. Back Order Stock,欠货订单数量
|
||||
- Bin Stock
|
||||
1. Total Stock,这个库位该产品的所有库存数量
|
||||
2. Pick Stock,这个库位需要拣货的数量
|
||||
3. Picked Stock,这个库位拣货完成的数量
|
||||
4. Move To Bin, 移库,移库后,会根据库位属性,直接更新库存数量,如果库位全部移空,则该库位会更新为空库位
|
||||
- Empty Bin
|
||||
1. 空库位明细
|
||||
- Occupied Bin
|
||||
1. 非空库位明细
|
||||
|
||||
### 收货管理
|
||||
- ASN到货通知书状态
|
||||
1. ASN Status = 1, ASN到货通知书创建完成,状态1是唯一可以删除和修改ASN信息的状态,他会显示在Pre Delivery中,即有了到货通知书,但是还没有到货,点击Confirm Delivery,即确认货物已经到达,ASN Status更新到2,此时已经无法再修改ASN信息
|
||||
2. ASN Status = 2, 拓展开发为司机到货排队,如果我们有很多司机到货,这可以做成一个排队系统,同时也可以让采购和销售看到到货信息,减少不必要的邮件和电话沟通,点击Finish Loading,即确认货物已经卸货完成,ASN Status更新到3,货物信息会出现在Sorting,此时的ASN状态表示,货物已卸到仓库,等待分拣
|
||||
3. ASN Status = 3, 货物分拣是必须的一个流程,没有货物分拣,货物是无法上架的,上架的原则就是货物整理好,摆放到相对应的库位上,点击Confirm Sorted,ASN Status更新到4,即确认分拣完成,等待上架
|
||||
4. 此时移动Sorted页面,会出现需要上架的货物明细,点击Move To Bin,上架完成,当然,系统会根据上架后的库位属性,自动更新商品库存数量信息
|
||||
### 发货管理
|
||||
- DN发货单状态
|
||||
1. DN Status = 1, DN发货单创建完成,此时订单还是可以修改状态,且系统中的库存数量不会发生任何改变,点击Confirm Order,DN Status更新到2,即订单已经被确认,且无法更改,同时系统中的货物库存数量会自动更新,比如Can Order数量和Ordered数量
|
||||
2. DN Status = 2, 这是订单被确认等待生成拣货单的过程,你可以点击单条订单Order Release来生成一个订单的拣货单,你也可以点击Release All Order,来将所有订单生成拣货单,如果是所有订单Release,那么会根据时间的先后进行库存匹配,库存不足时,会生成Back Order,即欠货订单,在这个过程中,DN单号是会发生改变的,如一家客户的多张订单,会被统一到一张订单中进行拣货,如客户订单无法满足,会将未满足部分生成欠货订单,欠货订单如果仍未得到匹配库存满足,将不再生成新的订单,DN Status会更新到3,即等待拣货的过程,已确认的订单和欠货订单都时Status为2的状态
|
||||
3. DN Status = 3, 直接拣货,此功能会出现在Beta5更新
|
||||
4. DN Status = 4, 发货交接,此功能会出现在Beta6更新
|
||||
5. DN Status = 5, 客户签收,此功能会出现在Beta7更新
|
||||
6. DN Status = 6, 对账结束,订单关闭,此功能会出现在Beta7更新
|
||||
### 退货管理
|
||||
- RO退货订单
|
||||
此功能将会出现在正式版中
|
||||
### 运费管理
|
||||
- Transportation Fee
|
||||
API已经完成,前端暂未更新入口,如果想要使用,可以直接调用Payment下的Transportation Fee API进行使用,运费自动计算模块已经做进收发货流程中
|
||||
|
||||
### 界面截图
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/inbound.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/outbound.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/stock.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/finace.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/goods.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/baseinfo.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/warehouse.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/staff.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/driver.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/api.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img width="100%" height="100%" src="https://github.com/Singosgu/picfile/blob/master/CN/chat.png?raw=true"/></div></p>
|
||||
<p><div align=center><img src="https://github.com/Singosgu/picfile/blob/master/CN/app1.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img src="https://github.com/Singosgu/picfile/blob/master/CN/app2.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img src="https://github.com/Singosgu/picfile/blob/master/CN/app3.png?raw=true"/></div></p>
|
||||
|
||||
<p><div align=center><img src="https://github.com/Singosgu/picfile/blob/master/CN/app4.png?raw=true"/></div></p>
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
7617270580ae099de75afba1ee0dfaf912f22a6d
|
||||
@@ -0,0 +1,65 @@
|
||||
~~~shell
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
sudo apt install vim-gtk // 安装 vim
|
||||
cd ~ // 到达Home目录
|
||||
vi .bashrc // 把 "alias vi=vim" 加进 bashrc
|
||||
source .bashrc // 刷新生效bashrc
|
||||
sudo apt install git // 安装 git
|
||||
sudo git clone https://github.com/Singosgu/GreaterWMS.git // 下载 GreaterWMS 从 github
|
||||
sudo apt install nodejs // 安装 nodejs
|
||||
sudo apt install npm // 安装 npm
|
||||
sudo npm install n -g // 安装 n
|
||||
sudo n stable // 下载 nodejs 的稳定版本
|
||||
// 你需要确定你的nodejs是12或者14版本,Quasar只支持12或者14版本
|
||||
// 这步完成以后,你需要重新启动你的Terminal,要不然升级不生效
|
||||
sudo npm install npm -g // 升级 NPM 到最新版本
|
||||
sudo npm install yarn -g // 安装 yarn
|
||||
sudo npm install -g @quasar/cli // 安装 quasar/cli
|
||||
quasar -v // 检查 Quasar 版本
|
||||
python3 // 确定你的python版本是3.8以上版本,原则上3.6也是可以的,但是安装库会有些问题
|
||||
pip3 list // 确定你是否安装有 pip3
|
||||
sudo apt install python3-pip // 如果你没有pip3 ,就安装一下
|
||||
pip3 list // 检查下是否安装成功
|
||||
sudo chmod -R 755 GreaterWMS // 提权 GreaterWMS 文件夹
|
||||
cd GreaterWMS // 进入GreaterWMS文件夹
|
||||
sudo pip3 install -r requirements.txt
|
||||
// 有些时候,你安装这些库会出问题,是因为python3版本的问题,不用担心,sudo pip3 install 出错的库就可以了.
|
||||
sudo daphne -p 8008 greaterwms.asgi:application
|
||||
// 现在打开浏览器,输入"127.0.0.1:8008",你会看到500错误,恭喜你,你已经可以正常部署接下来的事情了
|
||||
Ctrl + C // 回到GreaterWMS文件夹
|
||||
sudo python3 manage.py makemigrations // 数据库生成
|
||||
sudo python3 manage.py migrate // 数据库迁移
|
||||
sudo daphne -p 8008 greaterwms.asgi:application
|
||||
// 现在打开浏览器,输入"127.0.0.1:8008",你会看到项目已经运行了
|
||||
// 输入 "127.0.0.1:8008/myip", 你会得到你的内网IP,一定记住它
|
||||
Ctrl + C // 回到GreaterWMS文件夹
|
||||
cd templates //进入 templates 文件夹
|
||||
|
||||
从2.0.19版本以后,优化了请求地址修改方式,直接修改templates/dist/spa/statics/baseurl.js,中的baseurl和wsurl,就可以成功更改前端请求地址,不再需要做下面的quasar build打包工作。
|
||||
|
||||
如果需要修改前端内容,则还需要修改templates/public/statics/baseurl.js中的baseurl和wsurl,然后重新使用quasar build进行打包
|
||||
|
||||
sudo yarn config set registry https://registry.npm.taobao.org/ //更改yarn为国内源
|
||||
sudo yarn install // 等待Yarn安装完成,其实你也可以sudo npm install ,就是会慢一点
|
||||
sudo quasar d // 使用quasar命令启动前端页面
|
||||
// 前端会向 "127.0.0.1:8008"发请求, 在这里我们只是看下项目是不是可以运行
|
||||
Ctrl + C // 退回到templates文件夹
|
||||
cd src/boot // 进入在src/boot文件夹
|
||||
sudo vim axios_request.js // 我们开始更改请求地址
|
||||
// 更改 "127.0.0.1" 成你的内网IP, baseurl 是http请求地址 , ws 是 websocket请求地址
|
||||
按下 Esc 然后输入 ":wq" 去保存修改
|
||||
// 现在,你已经知道怎么部署和修改请求地址了
|
||||
sudo quasar build // 需要对修改进行重新打包
|
||||
cd .. // 回到GreaterWMS文件夹
|
||||
sudo daphne -b 0.0.0.0 -p 8008 greaterwms.asgi:application
|
||||
// 现在,打开浏览器,输入 "你的内网IP:8008",你可以看到项目已经运行了
|
||||
|
||||
谢天谢地!!!
|
||||
|
||||
顺便说一句
|
||||
1. 你知道了怎么修改请求地址
|
||||
2. 你也可以按你的喜好,去更改Port
|
||||
3. 你更可以使用Nginx或者Apache,把项目发布到互联网上
|
||||
~~~
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
~~~shell
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
sudo apt install vim-gtk // install vim
|
||||
cd ~ // Go to home follder
|
||||
vi .bashrc // add "alias vi=vim" in bashrc
|
||||
source .bashrc // refresh bashrc
|
||||
sudo apt install git // install git
|
||||
sudo git clone https://github.com/Singosgu/GreaterWMS.git // Download GreaterWMS from github
|
||||
sudo apt install nodejs // install nodejs
|
||||
sudo apt install npm // install npm
|
||||
sudo npm install n -g // update n
|
||||
sudo n stable // Download nodejs to stable version
|
||||
// You must confirm your nodejs version is 14 or 12 , cause Quasar just support 12 or 14
|
||||
// after this step , you should re-open your terminal
|
||||
sudo npm install npm -g // update npm to latestest
|
||||
sudo npm install yarn -g // install yarn
|
||||
sudo npm install -g @quasar/cli // install quasar/cli
|
||||
quasar -v // check quasar version
|
||||
python3 // To check your python version is 3.8 above
|
||||
pip3 list // To check whether you have pip3
|
||||
sudo apt install python3-pip // if you don't have pip3 , install it
|
||||
pip3 list // check again
|
||||
sudo chmod -R 755 GreaterWMS // approve GreaterWMS chmod
|
||||
cd GreaterWMS
|
||||
sudo pip3 install -r requirements.txt
|
||||
// Some times , you can not install some lib . Cause your python3 version . Don't worry, sudo pip3 install it is ok.
|
||||
sudo daphne -p 8008 greaterwms.asgi:application
|
||||
// Now, Open the brower and enter "127.0.0.1:8008", If you see 500 error . Congratuation, you success.
|
||||
Ctrl + C // out to terminal
|
||||
sudo python3 manage.py makemigrations // database create
|
||||
sudo python3 manage.py migrate // database create
|
||||
sudo daphne -p 8008 greaterwms.asgi:application
|
||||
// Now, Open the brower and enter "127.0.0.1:8008". You can see our project run
|
||||
// Now , Enter "127.0.0.1:8008/myip", you will get Intranet ip, recorde it
|
||||
Ctrl + C // out to terminal
|
||||
cd templates // Go to templates follder
|
||||
sudo yarn install // Waiting for yarn install
|
||||
sudo quasar d // run quasar web
|
||||
// quasar web will send request to "127.0.0.1:8008", this step just to check whether it can be run
|
||||
Ctrl + C // out to terminal
|
||||
cd src/boot // go to boot follder
|
||||
sudo vim axios_request.js // go to this file to change the request link
|
||||
// change "127.0.0.1" to your Intranet ip , baseurl is for http , ws is for websocket
|
||||
Esc then enter ":wq" to save the change
|
||||
// till now you know how to build it and change the request
|
||||
sudo quasar build // Re-Build the Quasar Web
|
||||
cd .. // back to greaterwms follder
|
||||
sudo daphne -b 0.0.0.0 -p 8008 greaterwms.asgi:application
|
||||
// Now, Open the brower and enter "your Intranet ip:8008". You can see our project run
|
||||
|
||||
Congratuation!!!
|
||||
|
||||
By The Way
|
||||
1. You Know how to change the request
|
||||
2. You also can change the port which you like
|
||||
3. You can use Nginx or Apache to run the project on internet
|
||||
~~~
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
d845ab52a97e28ec0282b25020c1284479f920f3
|
||||
@@ -0,0 +1,105 @@
|
||||
~~~shell
|
||||
# 下载python3.9.2(版本以自己电脑系统为主,我们以64位为例)
|
||||
https://www.python.org/ftp/python/3.9.2/python-3.9.2-amd64.exe
|
||||
# 右键,以管理员运行exe文件,安装python3.9.2
|
||||
# 注意一定要勾选上Add Python3.9 To PATH,然后点选Install Now
|
||||
# 下载sqlite3(版本以自己电脑系统为主,我们以64位为例)
|
||||
https://www.sqlite.org/2021/sqlite-dll-win64-x64-3350500.zip
|
||||
# 解压zip文件,将解压出来的文件,覆盖python路径dll中的文件,地址为
|
||||
~ C:\Users\{你的用户名}\AppData\Local\Programs\Python\Python39\DLLs
|
||||
# 下载Node.JS14.16.1(版本以自己电脑系统为主,我们以64位为例)
|
||||
https://nodejs.org/dist/v14.16.1/node-v14.16.1-x64.msi
|
||||
# 安装Node.JS的时候,一定不要勾选Automatically,一直下一步,知道安装完成
|
||||
# 下载Git(版本以自己电脑系统为主,我们以64位为例,需要下载setup版本)
|
||||
https://git-scm.com/download/win
|
||||
# 右键,以管理员运行exe文件,然后一直下一步就可以了
|
||||
# 选择好你要把GreaterWMS摆在哪个目录中,右键,选择Git Bash Here
|
||||
# 下载 GreaterWMS 从 github,由于网络原因,会很慢,多试几次
|
||||
git clone https://github.com/Singosgu/GreaterWMS.git
|
||||
# 左下角搜索栏,输入cmd
|
||||
# 右键,以管理员运行cmd
|
||||
# 查看Python版本
|
||||
python -V
|
||||
# 查看pip有没有装好
|
||||
pip list
|
||||
# 升级pip到最新版本
|
||||
pip install --upgrade pip
|
||||
# 进入GreaterWMS摆放目录,演示时摆在downlowad里面的,所以我们进去目录
|
||||
~ cd C:\Users\{你的用户名}\Downloads\GreaterWMS\
|
||||
# pip安装python依赖库
|
||||
pip install -r requirements.txt
|
||||
# Twisted可能安装不上,需要下载下来手动安装
|
||||
https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
|
||||
# 下载自己适合的版本,比如:我的演示视频是Python3.9.2,Win10版本是64位的
|
||||
# 所以我就要下载Twisted-20.3.0-cp39-cp39-win_amd64.whl
|
||||
# 将下载下来的Twisted摆在GreaterWMS根目录,手动安装
|
||||
pip install Twisted-20.3.0-cp39-cp39-win_amd64.whl
|
||||
# 再次运行安装requirements.txt
|
||||
pip install -r requirements.txt
|
||||
# 启动GreaterWMS
|
||||
daphne -p 8008 greaterwms.asgi:application
|
||||
# 这时候打开浏览器,输入127.0.0.1:8008
|
||||
# 如果看到500报错,就说明之前的Python依赖已经全部安装完成了
|
||||
# 回到CMD界面,按住Ctrl+C来退出项目启动
|
||||
# 生成数据库迁移文件
|
||||
python manage.py makemigrations
|
||||
# 生成数据库
|
||||
python manage.py migrate
|
||||
# 再次启动项目
|
||||
daphne -p 8008 greaterwms.asgi:application
|
||||
# 这时候打开浏览器,输入127.0.0.1:8008
|
||||
# 查看局域网IP,浏览器输入127.0.0.1:8008/myip
|
||||
# 保存或者记住这个IP地址
|
||||
# 一定注意,windows每次启动获得的内网IP是不同的,要么你路由器设置固定内网IP给这台电脑,要么你就不要关电脑
|
||||
# 回到CMD界面,按住Ctrl+C来退出项目启动
|
||||
# 进入templates目录
|
||||
cd templates
|
||||
|
||||
从2.0.19版本以后,优化了请求地址修改方式,直接修改templates/dist/spa/statics/baseurl.js,中的baseurl和wsurl,就可以成功更改前端请求地址,不再需要做下面的quasar build打包工作。
|
||||
|
||||
如果需要修改前端内容,则还需要修改templates/public/statics/baseurl.js中的baseurl和wsurl,然后重新使用quasar build进行打包
|
||||
|
||||
# 升级下npm
|
||||
npm install -g npm
|
||||
# 切换npm源为国内源
|
||||
npm config set registry https://registry.npm.taobao.org
|
||||
# 安装Yarn
|
||||
npm install -g yarn
|
||||
# 更改yarn为国内源
|
||||
yarn config set registry https://registry.npm.taobao.org/
|
||||
# 安装quasar环境
|
||||
npm install -g @quasar/cli
|
||||
# 安装windows构建工具
|
||||
#注意:如果安装不上请下载 Visual Studio 安装C++环境
|
||||
npm install -g windows-build-tools
|
||||
# 安装core-js依赖
|
||||
npm install -g core-js
|
||||
# 查看全局依赖是否安装完成
|
||||
npm list -g --depth=0
|
||||
# 安装项目依赖
|
||||
yarn install
|
||||
# 这个过程会有点慢,有时候会很快,是因为网络原因
|
||||
# 如果发生报错,那是因为网络原因无法安装,多试几次就可以了,直到没有报错
|
||||
# 进入发请求的文件,修改请求地址
|
||||
~ 记事本编辑 GreaterWMS/templates/src/boot/axios_request
|
||||
# 将127.0.0.1更改为你刚才查看到的内网IP
|
||||
const baseurl = 'http://127.0.0.1:8008/'
|
||||
const wsurl = 'ws://127.0.0.1:8008/'
|
||||
# 保存退出
|
||||
# templates目录下重新编译前端
|
||||
quasar build
|
||||
# 回到GreaterWMS根目录
|
||||
cd ..
|
||||
# 启动项目加入-b 0.0.0.0参数
|
||||
daphne -b 0.0.0.0 -p 8008 greaterwms.asgi:application
|
||||
# 接下来就可以使用你的浏览器,访问{ http://内网IP:8008 }来查看该项目了
|
||||
# 局域网上的电脑也可以通过这个IP来访问项目
|
||||
|
||||
谢天谢地!!!
|
||||
|
||||
顺便说一句
|
||||
1. 你知道了怎么修改请求地址
|
||||
2. 你也可以按你的喜好,去更改Port
|
||||
3. 你更可以使用Nginx或者Apache,把项目发布到互联网上
|
||||
~~~
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
63e42d89d6750efd58197b6b61d732d9f309ffd2
|
||||
@@ -0,0 +1 @@
|
||||
8b42c0ea8087964023ae7aa09fae24b32cc62b14
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.contrib import admin
|
||||
from . models import AsnListModel, AsnDetailModel
|
||||
|
||||
admin.site.register(AsnListModel)
|
||||
admin.site.register(AsnDetailModel)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AsnConfig(AppConfig):
|
||||
name = 'asn'
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
from rest_framework_csv.renderers import CSVStreamingRenderer
|
||||
|
||||
def list_file_headers():
|
||||
return [
|
||||
'asn_code',
|
||||
'asn_status',
|
||||
'total_weight',
|
||||
'total_volume',
|
||||
'total_cost',
|
||||
'supplier',
|
||||
'creater',
|
||||
'create_time',
|
||||
'update_time'
|
||||
]
|
||||
|
||||
def list_cn_data_header():
|
||||
return dict([
|
||||
('asn_code', u'ASN单号'),
|
||||
('asn_status', u'ASN状态'),
|
||||
('total_weight', u'总重量'),
|
||||
('total_volume', u'总体积'),
|
||||
('total_cost', u'总成本'),
|
||||
('supplier', u'供应商'),
|
||||
('creater', u'创建人'),
|
||||
('create_time', u'创建时间'),
|
||||
('update_time', u'更新时间')
|
||||
])
|
||||
|
||||
def list_en_data_header():
|
||||
return dict([
|
||||
('asn_code', u'ASN Code'),
|
||||
('asn_status', u'ASN Status'),
|
||||
('total_weight', u'Total Weight'),
|
||||
('total_volume', u'Total Volume'),
|
||||
('total_cost', u'Total Cost'),
|
||||
('supplier', u'Supplier'),
|
||||
('creater', u'Creater'),
|
||||
('create_time', u'Create Time'),
|
||||
('update_time', u'Update Time')
|
||||
])
|
||||
|
||||
def detail_file_headers():
|
||||
return [
|
||||
'asn_code',
|
||||
'asn_status',
|
||||
'supplier',
|
||||
'goods_code',
|
||||
'goods_qty',
|
||||
'goods_actual_qty',
|
||||
'sorted_qty',
|
||||
'goods_shortage_qty',
|
||||
'goods_more_qty',
|
||||
'goods_damage_qty',
|
||||
'goods_weight',
|
||||
'goods_volume',
|
||||
'goods_cost',
|
||||
'creater',
|
||||
'create_time',
|
||||
'update_time'
|
||||
]
|
||||
|
||||
def detail_cn_data_header():
|
||||
return dict([
|
||||
('asn_code', u'ASN单号'),
|
||||
('asn_status', u'ASN状态'),
|
||||
('supplier', u'供应商'),
|
||||
('goods_code', u'商品编码'),
|
||||
('goods_qty', u'订单数量'),
|
||||
('goods_actual_qty', u'实际到货数量'),
|
||||
('sorted_qty', u'已分拣数量'),
|
||||
('goods_shortage_qty', u'少到货数量'),
|
||||
('goods_more_qty', u'多到货数量'),
|
||||
('goods_damage_qty', u'破损数量'),
|
||||
('goods_weight', u'商品重量'),
|
||||
('goods_volume', u'商品体积'),
|
||||
('goods_cost', u'商品成本'),
|
||||
('creater', u'创建人'),
|
||||
('create_time', u'创建时间'),
|
||||
('update_time', u'更新时间')
|
||||
])
|
||||
|
||||
def detail_en_data_header():
|
||||
return dict([
|
||||
('asn_code', u'ASN Code'),
|
||||
('asn_status', u'ASN Status'),
|
||||
('supplier', u'Supplier'),
|
||||
('goods_code', u'Goods Code'),
|
||||
('goods_qty', u'Goods Qty'),
|
||||
('goods_actual_qty', u'Goods Actual Qty'),
|
||||
('sorted_qty', u'Sorted Qty'),
|
||||
('goods_shortage_qty', u'Goods Shortage Qty'),
|
||||
('goods_more_qty', u'Goods More Qty'),
|
||||
('goods_damage_qty', u'Goods Damage Qty'),
|
||||
('goods_weight', u'Goods Weight'),
|
||||
('goods_volume', u'Goods Volume'),
|
||||
('goods_cost', u'Goods Cost'),
|
||||
('creater', u'Creater'),
|
||||
('create_time', u'Create Time'),
|
||||
('update_time', u'Update Time')
|
||||
])
|
||||
|
||||
class FileListRenderCN(CSVStreamingRenderer):
|
||||
header = list_file_headers()
|
||||
labels = list_cn_data_header()
|
||||
|
||||
class FileListRenderEN(CSVStreamingRenderer):
|
||||
header = list_file_headers()
|
||||
labels = list_en_data_header()
|
||||
|
||||
class FileDetailRenderCN(CSVStreamingRenderer):
|
||||
header = detail_file_headers()
|
||||
labels = detail_cn_data_header()
|
||||
|
||||
class FileDetailRenderEN(CSVStreamingRenderer):
|
||||
header = detail_file_headers()
|
||||
labels = detail_en_data_header()
|
||||
@@ -0,0 +1,43 @@
|
||||
from django_filters import FilterSet
|
||||
from .models import AsnListModel, AsnDetailModel
|
||||
|
||||
class AsnListFilter(FilterSet):
|
||||
class Meta:
|
||||
model = AsnListModel
|
||||
fields = {
|
||||
"id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"asn_code": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"asn_status": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"total_weight": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"total_volume": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"total_cost": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"supplier": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"creater": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['exact', 'iexact', 'year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['exact', 'iexact', 'year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
|
||||
class AsnDetailFilter(FilterSet):
|
||||
class Meta:
|
||||
model = AsnDetailModel
|
||||
fields = {
|
||||
"id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"asn_code": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"asn_status": ['exact', 'iexact'],
|
||||
"supplier": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"goods_code": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"goods_qty": ['exact', 'iexact', 'gt', 'lt', 'gte', 'lte'],
|
||||
"goods_actual_qty": ['exact', 'iexact', 'gt', 'lt', 'gte', 'lte'],
|
||||
"sorted_qty": ['exact', 'iexact', 'gt', 'lt', 'gte', 'lte'],
|
||||
"goods_shortage_qty": ['exact', 'iexact', 'gt', 'lt', 'gte', 'lte'],
|
||||
"goods_more_qty": ['exact', 'iexact', 'gt', 'lt', 'gte', 'lte'],
|
||||
"goods_damage_qty": ['exact', 'iexact', 'gt', 'lt', 'gte', 'lte'],
|
||||
"goods_weight": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"goods_volume": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"goods_cost": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"creater": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
from django.db import models
|
||||
|
||||
class AsnListModel(models.Model):
|
||||
asn_code = models.CharField(max_length=255, verbose_name="ASN Code")
|
||||
asn_status = models.BigIntegerField(default=1, verbose_name="ASN Status")
|
||||
total_weight = models.FloatField(default=0, verbose_name="Total Weight")
|
||||
total_volume = models.FloatField(default=0, verbose_name="Total Volume")
|
||||
total_cost = models.FloatField(default=0, verbose_name="Total Cost")
|
||||
supplier = models.CharField(max_length=255, verbose_name="ASN Supplier")
|
||||
creater = models.CharField(max_length=255, verbose_name="Who Created")
|
||||
bar_code = models.CharField(max_length=255, verbose_name="Bar Code")
|
||||
openid = models.CharField(max_length=255, verbose_name="Openid")
|
||||
transportation_fee = models.JSONField(default=dict, verbose_name="Transportation Fee")
|
||||
is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
|
||||
update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
|
||||
|
||||
class Meta:
|
||||
db_table = 'asnlist'
|
||||
verbose_name = 'data id'
|
||||
verbose_name_plural = "data id"
|
||||
ordering = ['-id']
|
||||
|
||||
def __str__(self):
|
||||
return self.pk
|
||||
|
||||
class AsnDetailModel(models.Model):
|
||||
asn_code = models.CharField(max_length=255, verbose_name="ASN Code")
|
||||
asn_status = models.BigIntegerField(default=1, verbose_name="ASN Status")
|
||||
supplier = models.CharField(max_length=255, verbose_name="ASN Supplier")
|
||||
goods_code = models.CharField(max_length=255, verbose_name="Goods Code")
|
||||
goods_qty = models.BigIntegerField(default=0, verbose_name="Goods QTY")
|
||||
goods_actual_qty = models.BigIntegerField(default=0, verbose_name="Goods Actual QTY")
|
||||
sorted_qty = models.BigIntegerField(default=0, verbose_name="Sorted QTY")
|
||||
goods_shortage_qty = models.BigIntegerField(default=0, verbose_name="Goods Shortage QTY")
|
||||
goods_more_qty = models.BigIntegerField(default=0, verbose_name="Goods More QTY")
|
||||
goods_damage_qty = models.BigIntegerField(default=0, verbose_name="Goods damage QTY")
|
||||
goods_weight = models.FloatField(default=0, verbose_name="Goods Weight")
|
||||
goods_volume = models.FloatField(default=0, verbose_name="Goods Volume")
|
||||
goods_cost = models.FloatField(default=0, verbose_name="Goods Cost")
|
||||
creater = models.CharField(max_length=255, verbose_name="Who Created")
|
||||
openid = models.CharField(max_length=255, verbose_name="Openid")
|
||||
is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
|
||||
update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
|
||||
|
||||
class Meta:
|
||||
db_table = 'asndetail'
|
||||
verbose_name = 'data id'
|
||||
verbose_name_plural = "data id"
|
||||
ordering = ['-id']
|
||||
|
||||
def __str__(self):
|
||||
return self.pk
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.utils.urls import replace_query_param, remove_query_param
|
||||
|
||||
from supplier.models import ListModel as supplier
|
||||
|
||||
class MyPageNumberPaginationASNList(PageNumberPagination):
|
||||
page_size = 30
|
||||
page_size_query_param = "max_page"
|
||||
max_page_size = 1000
|
||||
page_query_param = 'page'
|
||||
|
||||
def get_previous_link(self):
|
||||
if not self.page.has_previous():
|
||||
return None
|
||||
url = self.request.build_absolute_uri()
|
||||
page_number = self.page.previous_page_number()
|
||||
ssl_check = str(self.request.META.get('HTTP_ORIGIN')).split(':')[0]
|
||||
url_combine = str(url).split(':')
|
||||
if len(str(url).split(':')) == 2:
|
||||
url = ssl_check + ':' + url_combine[1]
|
||||
if page_number == 1:
|
||||
return remove_query_param(url, self.page_query_param)
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
elif len(str(url).split(':')) == 3:
|
||||
url = ssl_check + ':' + url_combine[1] + ':' + url_combine[2]
|
||||
if page_number == 1:
|
||||
return remove_query_param(url, self.page_query_param)
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
else:
|
||||
raise APIException({"detail": "Wrong API Url"})
|
||||
|
||||
def get_next_link(self):
|
||||
if not self.page.has_next():
|
||||
return None
|
||||
url = self.request.build_absolute_uri()
|
||||
page_number = self.page.next_page_number()
|
||||
ssl_check = str(self.request.META.get('HTTP_ORIGIN')).split(':')[0]
|
||||
url_combine = str(url).split(':')
|
||||
if len(str(url).split(':')) == 2:
|
||||
url = ssl_check + ':' + url_combine[1]
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
elif len(str(url).split(':')) == 3:
|
||||
url = ssl_check + ':' + url_combine[1] + ':' + url_combine[2]
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
else:
|
||||
raise APIException({"detail": "Wrong API Url"})
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
supplier_list_data = supplier.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
supplier_list = []
|
||||
for i in range(len(supplier_list_data)):
|
||||
supplier_list.append(supplier_list_data[i].supplier_name)
|
||||
return Response(OrderedDict([
|
||||
('supplier_list', supplier_list),
|
||||
('count', self.page.paginator.count),
|
||||
('next', self.get_next_link()),
|
||||
('previous', self.get_previous_link()),
|
||||
('results', data)
|
||||
]))
|
||||
@@ -0,0 +1,156 @@
|
||||
from rest_framework import serializers
|
||||
from .models import AsnListModel, AsnDetailModel
|
||||
from utils import datasolve
|
||||
|
||||
class ASNListGetSerializer(serializers.ModelSerializer):
|
||||
asn_code = serializers.CharField(read_only=True, required=False)
|
||||
asn_status = serializers.IntegerField(read_only=True, required=False)
|
||||
supplier = serializers.CharField(read_only=True, required=False)
|
||||
bar_code = serializers.CharField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
class Meta:
|
||||
model = AsnListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'openid', ]
|
||||
|
||||
class ASNListPostSerializer(serializers.ModelSerializer):
|
||||
openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
|
||||
asn_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.asn_data_validate])
|
||||
supplier = serializers.CharField(read_only=False, required=False)
|
||||
bar_code = serializers.CharField(read_only=False, required=True)
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = AsnListModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class ASNListPartialUpdateSerializer(serializers.ModelSerializer):
|
||||
asn_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.asn_data_validate])
|
||||
|
||||
class Meta:
|
||||
model = AsnListModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class ASNListUpdateSerializer(serializers.ModelSerializer):
|
||||
asn_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.asn_data_validate])
|
||||
|
||||
class Meta:
|
||||
model = AsnListModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class ASNDetailGetSerializer(serializers.ModelSerializer):
|
||||
asn_code = serializers.CharField(read_only=True, required=False)
|
||||
supplier = serializers.CharField(read_only=True, required=False)
|
||||
goods_code = serializers.CharField(read_only=True, required=False)
|
||||
goods_qty = serializers.IntegerField(read_only=True, required=False)
|
||||
goods_actual_qty = serializers.IntegerField(read_only=True, required=False)
|
||||
sorted_qty = serializers.IntegerField(read_only=True, required=False)
|
||||
goods_shortage_qty = serializers.IntegerField(read_only=True, required=False)
|
||||
goods_more_qty = serializers.IntegerField(read_only=True, required=False)
|
||||
goods_damage_qty = serializers.IntegerField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
class Meta:
|
||||
model = AsnDetailModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'openid']
|
||||
|
||||
class ASNDetailPostSerializer(serializers.ModelSerializer):
|
||||
openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
|
||||
asn_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
supplier = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
goods_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
goods_qty = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.qty_0_data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = AsnDetailModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class ASNSortedPostSerializer(serializers.ModelSerializer):
|
||||
openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
|
||||
asn_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
supplier = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
goods_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
goods_qty = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.qty_data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = AsnDetailModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class ASNDetailUpdateSerializer(serializers.ModelSerializer):
|
||||
asn_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
supplier = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
goods_code = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
goods_qty = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.qty_0_data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = AsnDetailModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class ASNDetailPartialUpdateSerializer(serializers.ModelSerializer):
|
||||
asn_code = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
supplier = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
goods_code = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
goods_qty = serializers.IntegerField(read_only=False, required=False, validators=[datasolve.qty_0_data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = AsnDetailModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class MoveToBinSerializer(serializers.ModelSerializer):
|
||||
bin_name = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
qty = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.qty_0_data_validate])
|
||||
class Meta:
|
||||
model = AsnDetailModel
|
||||
ref_name = 'AsnMoveToBin'
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class FileListRenderSerializer(serializers.ModelSerializer):
|
||||
asn_code = serializers.CharField(read_only=False, required=False)
|
||||
asn_status = serializers.IntegerField(read_only=False, required=False)
|
||||
total_weight = serializers.FloatField(read_only=False, required=False)
|
||||
total_volume = serializers.FloatField(read_only=False, required=False)
|
||||
total_cost = serializers.FloatField(read_only=False, required=False)
|
||||
supplier = serializers.CharField(read_only=False, required=False)
|
||||
creater = serializers.CharField(read_only=False, required=False)
|
||||
transportation_fee = serializers.JSONField(read_only=False, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
class Meta:
|
||||
model = AsnListModel
|
||||
ref_name = 'ASNFileListRenderSerializer'
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
|
||||
class FileDetailRenderSerializer(serializers.ModelSerializer):
|
||||
asn_code = serializers.CharField(read_only=False, required=False)
|
||||
asn_status = serializers.IntegerField(read_only=False, required=False)
|
||||
goods_code = serializers.CharField(read_only=False, required=False)
|
||||
goods_qty = serializers.IntegerField(read_only=False, required=False)
|
||||
goods_actual_qty = serializers.IntegerField(read_only=False, required=False)
|
||||
sorted_qty = serializers.IntegerField(read_only=False, required=False)
|
||||
goods_shortage_qty = serializers.IntegerField(read_only=False, required=False)
|
||||
goods_more_qty = serializers.IntegerField(read_only=False, required=False)
|
||||
goods_damage_qty = serializers.IntegerField(read_only=False, required=False)
|
||||
goods_weight = serializers.FloatField(read_only=False, required=False)
|
||||
goods_volume = serializers.FloatField(read_only=False, required=False)
|
||||
goods_cost = serializers.FloatField(read_only=False, required=False)
|
||||
supplier = serializers.CharField(read_only=False, required=False)
|
||||
creater = serializers.CharField(read_only=False, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
class Meta:
|
||||
model = AsnDetailModel
|
||||
ref_name = 'ASNFileDetailRenderSerializer'
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
from django.urls import path, re_path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path(r'list/', views.AsnListViewSet.as_view({"get": "list", "post": "create"}), name="asnlist"),
|
||||
re_path(r'^list/(?P<pk>\d+)/$', views.AsnListViewSet.as_view({
|
||||
'get': 'retrieve',
|
||||
'put': 'update',
|
||||
'patch': 'partial_update',
|
||||
'delete': 'destroy'
|
||||
}), name="asnlist_1"),
|
||||
path(r'detail/', views.AsnDetailViewSet.as_view({"get": "list", "post": "create", 'put': 'update'}), name="asndetail"),
|
||||
re_path(r'^detail/(?P<pk>\d+)/$', views.AsnDetailViewSet.as_view({
|
||||
'get': 'retrieve',
|
||||
}), name="asndetail_1"),
|
||||
re_path(r'^viewprint/(?P<pk>\d+)/$', views.AsnViewPrintViewSet.as_view({
|
||||
'get': 'retrieve',
|
||||
}), name="asnviewprint_1"),
|
||||
re_path(r'^preload/(?P<pk>\d+)/$', views.AsnPreLoadViewSet.as_view({
|
||||
'post': 'create',
|
||||
}), name="preload_1"),
|
||||
re_path(r'^presort/(?P<pk>\d+)/$', views.AsnPreSortViewSet.as_view({
|
||||
'post': 'create',
|
||||
}), name="presort_1"),
|
||||
path(r'sorted/', views.AsnSortedViewSet.as_view({"put": "update"}), name="sorted"),
|
||||
re_path(r'^sorted/(?P<pk>\d+)/$', views.AsnSortedViewSet.as_view({
|
||||
'post': 'create'
|
||||
}), name="sorted_1"),
|
||||
path(r'movetobin/', views.MoveToBinViewSet.as_view({'put': 'update'}), name="movetobin"),
|
||||
re_path(r'^movetobin/(?P<pk>\d+)/$', views.MoveToBinViewSet.as_view({
|
||||
'post': 'create',
|
||||
}), name="movetobin_1"),
|
||||
path(r'filelist/', views.FileListDownloadView.as_view({"get": "list"}), name="asnfilelistdownload"),
|
||||
path(r'filedetail/', views.FileDetailDownloadView.as_view({"get": "list"}), name="asnfiledetaildownload"),
|
||||
]
|
||||
+1188
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from . models import ListModel
|
||||
|
||||
admin.site.register(ListModel)
|
||||
@@ -0,0 +1,38 @@
|
||||
from django.apps import AppConfig
|
||||
from django.db.models.signals import post_migrate
|
||||
|
||||
class BinpropertyConfig(AppConfig):
|
||||
name = 'binproperty'
|
||||
|
||||
def ready(self):
|
||||
post_migrate.connect(do_init_data, sender=self)
|
||||
|
||||
def do_init_data(sender, **kwargs):
|
||||
init_category()
|
||||
|
||||
def init_category():
|
||||
"""
|
||||
:return:None
|
||||
"""
|
||||
try:
|
||||
from .models import ListModel as ls
|
||||
if ls.objects.filter(openid__iexact='init_data').exists():
|
||||
if ls.objects.filter(openid__iexact='init_data').count() != 4:
|
||||
ls.objects.filter(openid__iexact='init_data').delete()
|
||||
init_data = [
|
||||
ls(id=1, openid='init_data', bin_property='Damage', creater='GreaterWMS'),
|
||||
ls(id=2, openid='init_data', bin_property='Inspection', creater='GreaterWMS'),
|
||||
ls(id=3, openid='init_data', bin_property='Normal', creater='GreaterWMS'),
|
||||
ls(id=4, openid='init_data', bin_property='Holding', creater='GreaterWMS')
|
||||
]
|
||||
ls.objects.bulk_create(init_data, batch_size=100)
|
||||
else:
|
||||
init_data = [
|
||||
ls(id=1, openid='init_data', bin_property='Damage', creater='GreaterWMS'),
|
||||
ls(id=2, openid='init_data', bin_property='Inspection', creater='GreaterWMS'),
|
||||
ls(id=3, openid='init_data', bin_property='Normal', creater='GreaterWMS'),
|
||||
ls(id=4, openid='init_data', bin_property='Holding', creater='GreaterWMS')
|
||||
]
|
||||
ls.objects.bulk_create(init_data, batch_size=100)
|
||||
except:
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
from django_filters import FilterSet
|
||||
from .models import ListModel
|
||||
|
||||
class Filter(FilterSet):
|
||||
class Meta:
|
||||
model = ListModel
|
||||
fields = {
|
||||
"id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"bin_property": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"creater": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
class ListModel(models.Model):
|
||||
bin_property = models.CharField(max_length=32, verbose_name="Bin property")
|
||||
creater = models.CharField(max_length=255, verbose_name="Who created")
|
||||
openid = models.CharField(max_length=255, verbose_name="Openid")
|
||||
is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
|
||||
update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
|
||||
|
||||
class Meta:
|
||||
db_table = 'binproperty'
|
||||
verbose_name = 'data id'
|
||||
verbose_name_plural = "data id"
|
||||
ordering = ['bin_property']
|
||||
|
||||
def __str__(self):
|
||||
return self.pk
|
||||
@@ -0,0 +1,14 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ListModel
|
||||
|
||||
class BinpropertyGetSerializer(serializers.ModelSerializer):
|
||||
bin_property = serializers.CharField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
class Meta:
|
||||
model = ListModel
|
||||
ref_name = 'BinpropertyGetSerializer'
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', ]
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path(r'', views.APIViewSet.as_view({"get": "list"}), name="binproperty")
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
from rest_framework import viewsets
|
||||
from .models import ListModel
|
||||
from . import serializers
|
||||
from utils.page import MyPageNumberPagination
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .filter import Filter
|
||||
|
||||
class APIViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
list:
|
||||
Response a data list(all)
|
||||
"""
|
||||
pagination_class = MyPageNumberPagination
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user:
|
||||
return ListModel.objects.filter(is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list']:
|
||||
return serializers.BinpropertyGetSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from . models import ListModel
|
||||
|
||||
admin.site.register(ListModel)
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class BinsetConfig(AppConfig):
|
||||
name = 'binset'
|
||||
@@ -0,0 +1,42 @@
|
||||
from rest_framework_csv.renderers import CSVStreamingRenderer
|
||||
|
||||
def file_headers():
|
||||
return [
|
||||
'bin_name',
|
||||
'bin_size',
|
||||
'bin_property',
|
||||
'empty_label',
|
||||
'creater',
|
||||
'create_time',
|
||||
'update_time'
|
||||
]
|
||||
|
||||
def cn_data_header():
|
||||
return dict([
|
||||
('bin_name', u'库位名称'),
|
||||
('bin_size', u'库位尺寸'),
|
||||
('bin_property', u'库位属性'),
|
||||
('empty_label', u'空库位标识'),
|
||||
('creater', u'创建人'),
|
||||
('create_time', u'创建时间'),
|
||||
('update_time', u'更新时间')
|
||||
])
|
||||
|
||||
def en_data_header():
|
||||
return dict([
|
||||
('bin_name', u'Bin Name'),
|
||||
('bin_size', u'Bin Size'),
|
||||
('bin_property', u'Bin Property'),
|
||||
('empty_label', u'Empty Label'),
|
||||
('creater', u'Creater'),
|
||||
('create_time', u'Create Time'),
|
||||
('update_time', u'Update Time')
|
||||
])
|
||||
|
||||
class FileRenderCN(CSVStreamingRenderer):
|
||||
header = file_headers()
|
||||
labels = cn_data_header()
|
||||
|
||||
class FileRenderEN(CSVStreamingRenderer):
|
||||
header = file_headers()
|
||||
labels = en_data_header()
|
||||
@@ -0,0 +1,17 @@
|
||||
from django_filters import FilterSet
|
||||
from .models import ListModel
|
||||
|
||||
class Filter(FilterSet):
|
||||
class Meta:
|
||||
model = ListModel
|
||||
fields = {
|
||||
"id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range', ],
|
||||
"bin_name": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"bin_size": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"bin_property": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"empty_label": ['exact', 'iexact'],
|
||||
"creater": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
from django.db import models
|
||||
|
||||
class ListModel(models.Model):
|
||||
bin_name = models.CharField(max_length=255, verbose_name="Bin Name")
|
||||
bin_size = models.CharField(max_length=255, verbose_name="Bin Size")
|
||||
bin_property = models.CharField(max_length=11, verbose_name="Bin Property")
|
||||
empty_label = models.BooleanField(default=True, verbose_name="Empty Label")
|
||||
creater = models.CharField(max_length=255, verbose_name="Who Created")
|
||||
bar_code = models.CharField(max_length=255, verbose_name="Bar Code")
|
||||
openid = models.CharField(max_length=255, verbose_name="Openid")
|
||||
is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
|
||||
update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
|
||||
|
||||
class Meta:
|
||||
db_table = 'binset'
|
||||
verbose_name = 'data id'
|
||||
verbose_name_plural = "data id"
|
||||
ordering = ['bin_name']
|
||||
|
||||
def __str__(self):
|
||||
return self.pk
|
||||
@@ -0,0 +1,70 @@
|
||||
from collections import OrderedDict
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.utils.urls import replace_query_param, remove_query_param
|
||||
from binproperty.models import ListModel as binproperty
|
||||
from binsize.models import ListModel as binsize
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
class MyPageNumberPagination(PageNumberPagination):
|
||||
page_size = 30
|
||||
page_size_query_param = "max_page"
|
||||
max_page_size = 1000
|
||||
page_query_param = 'page'
|
||||
|
||||
def get_previous_link(self):
|
||||
if not self.page.has_previous():
|
||||
return None
|
||||
url = self.request.build_absolute_uri()
|
||||
page_number = self.page.previous_page_number()
|
||||
ssl_check = str(self.request.META.get('HTTP_ORIGIN')).split(':')[0]
|
||||
url_combine = str(url).split(':')
|
||||
if len(str(url).split(':')) == 2:
|
||||
url = ssl_check + ':' + url_combine[1]
|
||||
if page_number == 1:
|
||||
return remove_query_param(url, self.page_query_param)
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
elif len(str(url).split(':')) == 3:
|
||||
url = ssl_check + ':' + url_combine[1] + ':' + url_combine[2]
|
||||
if page_number == 1:
|
||||
return remove_query_param(url, self.page_query_param)
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
else:
|
||||
raise APIException({"detail": "Wrong API Url"})
|
||||
|
||||
def get_next_link(self):
|
||||
if not self.page.has_next():
|
||||
return None
|
||||
url = self.request.build_absolute_uri()
|
||||
page_number = self.page.next_page_number()
|
||||
ssl_check = str(self.request.META.get('HTTP_ORIGIN')).split(':')[0]
|
||||
url_combine = str(url).split(':')
|
||||
if len(str(url).split(':')) == 2:
|
||||
url = ssl_check + ':' + url_combine[1]
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
elif len(str(url).split(':')) == 3:
|
||||
url = ssl_check + ':' + url_combine[1] + ':' + url_combine[2]
|
||||
return replace_query_param(url, self.page_query_param, page_number)
|
||||
else:
|
||||
raise APIException({"detail": "Wrong API Url"})
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
bin_property_list_data = binproperty.objects.filter(Q(openid=self.request.auth.openid, is_delete=False) |
|
||||
Q(openid='init_data', is_delete=False))
|
||||
bin_property_list = []
|
||||
for i in range(len(bin_property_list_data)):
|
||||
bin_property_list.append(bin_property_list_data[i].bin_property)
|
||||
bin_size_list_data = binsize.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
bin_size_list = []
|
||||
for i in range(len(bin_size_list_data)):
|
||||
bin_size_list.append(bin_size_list_data[i].bin_size)
|
||||
return Response(OrderedDict([
|
||||
('bin_size_list', bin_size_list),
|
||||
('bin_property_list', bin_property_list),
|
||||
('count', self.page.paginator.count),
|
||||
('next', self.get_next_link()),
|
||||
('previous', self.get_previous_link()),
|
||||
('results', data)
|
||||
]))
|
||||
@@ -0,0 +1,79 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ListModel
|
||||
from utils import datasolve
|
||||
|
||||
class ScannerBinsetTagGetSerializer(serializers.ModelSerializer):
|
||||
bin_name = serializers.CharField(read_only=True, required=False)
|
||||
bin_size = serializers.CharField(read_only=True, required=False)
|
||||
bin_property = serializers.CharField(read_only=True, required=False)
|
||||
empty_label = serializers.BooleanField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=True, required=False)
|
||||
bar_code = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', ]
|
||||
|
||||
|
||||
class BinsetGetSerializer(serializers.ModelSerializer):
|
||||
bin_name = serializers.CharField(read_only=True, required=False)
|
||||
bin_size = serializers.CharField(read_only=True, required=False)
|
||||
bin_property = serializers.CharField(read_only=True, required=False)
|
||||
empty_label = serializers.BooleanField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=True, required=False)
|
||||
bar_code = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', ]
|
||||
|
||||
class BinsetPostSerializer(serializers.ModelSerializer):
|
||||
openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
|
||||
bin_name = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_size = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_property = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bar_code = serializers.CharField(read_only=False, required=True)
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class BinsetUpdateSerializer(serializers.ModelSerializer):
|
||||
bin_name = serializers.CharField(read_only=True, required=False)
|
||||
bin_size = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_property = serializers.CharField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bar_code = serializers.CharField(read_only=False, required=False)
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class BinsetPartialUpdateSerializer(serializers.ModelSerializer):
|
||||
bin_name = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
bin_size = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
bin_property = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class FileRenderSerializer(serializers.ModelSerializer):
|
||||
bin_name = serializers.CharField(read_only=False, required=False)
|
||||
bin_size = serializers.CharField(read_only=False, required=False)
|
||||
bin_property = serializers.CharField(read_only=False, required=False)
|
||||
empty_label = serializers.BooleanField(read_only=False, required=False)
|
||||
creater = serializers.CharField(read_only=False, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
class Meta:
|
||||
model = ListModel
|
||||
ref_name = 'BinSetFileRenderSerializer'
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
@@ -0,0 +1,14 @@
|
||||
from django.urls import path, re_path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path(r'', views.APIViewSet.as_view({"get": "list", "post": "create"}), name="binset"),
|
||||
path(r'file/', views.FileDownloadView.as_view({"get": "list"}), name="binsetfiledownload"),
|
||||
re_path(r'^(?P<pk>\d+)/$', views.APIViewSet.as_view({
|
||||
'get': 'retrieve',
|
||||
'put': 'update',
|
||||
'patch': 'partial_update',
|
||||
'delete': 'destroy'
|
||||
}), name="binset_1"),
|
||||
path(r'scannerbintag/<str:bar_code>/',views.ScannerBinsetTagView.as_view({"get":"retrieve"}))
|
||||
]
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
from rest_framework import viewsets
|
||||
from .models import ListModel
|
||||
from . import serializers
|
||||
from .page import MyPageNumberPagination
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.response import Response
|
||||
from .filter import Filter
|
||||
from rest_framework.exceptions import APIException
|
||||
from django.db.models import Q
|
||||
from binsize.models import ListModel as binsize
|
||||
from scanner.models import ListModel as scanner
|
||||
from binproperty.models import ListModel as binproperty
|
||||
from .serializers import FileRenderSerializer
|
||||
from django.http import StreamingHttpResponse
|
||||
from .files import FileRenderCN, FileRenderEN
|
||||
from rest_framework.settings import api_settings
|
||||
from utils.md5 import Md5
|
||||
from .serializers import ScannerBinsetTagGetSerializer
|
||||
|
||||
class ScannerBinsetTagView(viewsets.ModelViewSet):
|
||||
"""
|
||||
retrieve:
|
||||
Response a data list(get)
|
||||
|
||||
http://127.0.0.1:8008/binset/scannerbintag/3d89ad23d185d5f206d860745c5c4121/
|
||||
"""
|
||||
pagination_class = MyPageNumberPagination
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
lookup_field = 'bar_code'
|
||||
def get_project(self):
|
||||
try:
|
||||
bar_code = self.kwargs['bar_code']
|
||||
return bar_code
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_queryset(self):
|
||||
bar_code = self.get_project()
|
||||
if self.request.user:
|
||||
if bar_code is None:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, bar_code=bar_code, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list', 'retrieve', 'destroy']:
|
||||
return serializers.ScannerBinsetTagGetSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
|
||||
|
||||
|
||||
class APIViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
retrieve:
|
||||
Response a data list(get)
|
||||
|
||||
list:
|
||||
Response a data list(all)
|
||||
|
||||
create:
|
||||
Create a data line(post)
|
||||
|
||||
delete:
|
||||
Delete a data line(delete)
|
||||
|
||||
partial_update:
|
||||
Partial_update a data(patch:partial_update)
|
||||
|
||||
update:
|
||||
Update a data(put:update)
|
||||
"""
|
||||
pagination_class = MyPageNumberPagination
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_project(self):
|
||||
try:
|
||||
id = self.kwargs.get('pk')
|
||||
return id
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_queryset(self):
|
||||
id = self.get_project()
|
||||
if self.request.user:
|
||||
if id is None:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list', 'retrieve', 'destroy']:
|
||||
return serializers.BinsetGetSerializer
|
||||
elif self.action in ['create']:
|
||||
return serializers.BinsetPostSerializer
|
||||
elif self.action in ['update']:
|
||||
return serializers.BinsetUpdateSerializer
|
||||
elif self.action in ['partial_update']:
|
||||
return serializers.BinsetPartialUpdateSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
data = self.request.data
|
||||
data['openid'] = self.request.auth.openid
|
||||
if ListModel.objects.filter(openid=data['openid'], bin_name=data['bin_name'], is_delete=False).exists():
|
||||
raise APIException({"detail": "Data exists"})
|
||||
else:
|
||||
if binsize.objects.filter(openid=data['openid'], bin_size=data['bin_size'], is_delete=False).exists():
|
||||
if binproperty.objects.filter(Q(openid=data['openid'], bin_property=data['bin_property'], is_delete=False) |
|
||||
Q(openid='init_data', bin_property=data['bin_property'], is_delete=False)).exists():
|
||||
data['bar_code'] = Md5.md5(data['bin_name'])
|
||||
serializer = self.get_serializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
scanner.objects.create(openid=self.request.auth.openid, mode="BINSET", code=data['bin_name'],
|
||||
bar_code=data['bar_code'])
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
else:
|
||||
raise APIException({"detail": "Bin property does not exists or it has been changed"})
|
||||
else:
|
||||
raise APIException({"detail": "Bin size does not exists or it has been changed"})
|
||||
|
||||
def update(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot update data which not yours"})
|
||||
else:
|
||||
data = self.request.data
|
||||
if binsize.objects.filter(openid=self.request.auth.openid, bin_size=data['bin_size'], is_delete=False).exists():
|
||||
if binproperty.objects.filter(Q(openid=self.request.auth.openid, bin_property=data['bin_property'], is_delete=False) |
|
||||
Q(openid='init_data', bin_property=data['bin_property'], is_delete=False)).exists():
|
||||
serializer = self.get_serializer(qs, data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
else:
|
||||
raise APIException({"detail": "Bin property does not exists or it has been changed"})
|
||||
else:
|
||||
raise APIException({"detail": "Bin size does not exists or it has been changed"})
|
||||
|
||||
def partial_update(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot partial_update data which not yours"})
|
||||
else:
|
||||
data = self.request.data
|
||||
if binsize.objects.filter(openid=self.request.auth.openid, bin_size=data['bin_size'], is_delete=False).exists():
|
||||
if binproperty.objects.filter(Q(openid=self.request.auth.openid, bin_property=data['bin_property'], is_delete=False) |
|
||||
Q(openid='init_data', bin_property=data['bin_property'], is_delete=False)).exists():
|
||||
serializer = self.get_serializer(qs, data=data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
else:
|
||||
raise APIException({"detail": "Bin property does not exists or it has been changed"})
|
||||
else:
|
||||
raise APIException({"detail": "Bin size does not exists or it has been changed"})
|
||||
|
||||
def destroy(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot delete data which not yours"})
|
||||
else:
|
||||
qs.is_delete = True
|
||||
qs.save()
|
||||
serializer = self.get_serializer(qs, many=False)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
class FileDownloadView(viewsets.ModelViewSet):
|
||||
renderer_classes = (FileRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_project(self):
|
||||
try:
|
||||
id = self.kwargs.get('pk')
|
||||
return id
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_queryset(self):
|
||||
id = self.get_project()
|
||||
if self.request.user:
|
||||
if id is None:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list']:
|
||||
return serializers.FileRenderSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
def get_lang(self, data):
|
||||
lang = self.request.META.get('HTTP_LANGUAGE')
|
||||
if lang:
|
||||
if lang == 'zh-hans':
|
||||
return FileRenderCN().render(data)
|
||||
else:
|
||||
return FileRenderEN().render(data)
|
||||
else:
|
||||
return FileRenderEN().render(data)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
from datetime import datetime
|
||||
dt = datetime.now()
|
||||
data = (
|
||||
FileRenderSerializer(instance).data
|
||||
for instance in self.filter_queryset(self.get_queryset())
|
||||
)
|
||||
renderer = self.get_lang(data)
|
||||
response = StreamingHttpResponse(
|
||||
renderer,
|
||||
content_type="text/csv"
|
||||
)
|
||||
response['Content-Disposition'] = "attachment; filename='binset_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
|
||||
return response
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from . models import ListModel
|
||||
|
||||
admin.site.register(ListModel)
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class BinsizeConfig(AppConfig):
|
||||
name = 'binsize'
|
||||
@@ -0,0 +1,42 @@
|
||||
from rest_framework_csv.renderers import CSVStreamingRenderer
|
||||
|
||||
def file_headers():
|
||||
return [
|
||||
'bin_size',
|
||||
'bin_size_w',
|
||||
'bin_size_d',
|
||||
'bin_size_h',
|
||||
'creater',
|
||||
'create_time',
|
||||
'update_time'
|
||||
]
|
||||
|
||||
def cn_data_header():
|
||||
return dict([
|
||||
('bin_size', u'库位尺寸名称'),
|
||||
('bin_size_w', u'库位尺寸长度'),
|
||||
('bin_size_d', u'库位尺寸宽度'),
|
||||
('bin_size_h', u'库位尺寸高度'),
|
||||
('creater', u'创建人'),
|
||||
('create_time', u'创建时间'),
|
||||
('update_time', u'更新时间'),
|
||||
])
|
||||
|
||||
def en_data_header():
|
||||
return dict([
|
||||
('bin_size', u'Bin Size'),
|
||||
('bin_size_w', u'Bin Wide'),
|
||||
('bin_size_d', u'Bin Depth'),
|
||||
('bin_size_h', u'Bin Height'),
|
||||
('creater', u'Creater'),
|
||||
('create_time', u'Create Time'),
|
||||
('update_time', u'Update Time'),
|
||||
])
|
||||
|
||||
class FileRenderCN(CSVStreamingRenderer):
|
||||
header = file_headers()
|
||||
labels = cn_data_header()
|
||||
|
||||
class FileRenderEN(CSVStreamingRenderer):
|
||||
header = file_headers()
|
||||
labels = en_data_header()
|
||||
@@ -0,0 +1,17 @@
|
||||
from django_filters import FilterSet
|
||||
from .models import ListModel
|
||||
|
||||
class Filter(FilterSet):
|
||||
class Meta:
|
||||
model = ListModel
|
||||
fields = {
|
||||
"id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"bin_size": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"bin_size_w": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"bin_size_d": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"bin_size_h": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"creater": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
from django.db import models
|
||||
|
||||
class ListModel(models.Model):
|
||||
bin_size = models.CharField(max_length=255, verbose_name="Bin Name")
|
||||
bin_size_w = models.FloatField(default=0, verbose_name="Bin Width")
|
||||
bin_size_d = models.FloatField(default=0, verbose_name="Bin Depth")
|
||||
bin_size_h = models.FloatField(default=0, verbose_name="Bin Height")
|
||||
creater = models.CharField(max_length=255, verbose_name="Who created")
|
||||
openid = models.CharField(max_length=255, verbose_name="Openid")
|
||||
is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
|
||||
update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
|
||||
|
||||
class Meta:
|
||||
db_table = 'binsize'
|
||||
verbose_name = 'data id'
|
||||
verbose_name_plural = "data id"
|
||||
ordering = ['-id']
|
||||
|
||||
def __str__(self):
|
||||
return self.pk
|
||||
@@ -0,0 +1,64 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ListModel
|
||||
from utils import datasolve
|
||||
|
||||
class BinsizeGetSerializer(serializers.ModelSerializer):
|
||||
bin_size = serializers.CharField(read_only=True, required=False)
|
||||
bin_size_w = serializers.FloatField(read_only=True, required=False)
|
||||
bin_size_d = serializers.FloatField(read_only=True, required=False)
|
||||
bin_size_h = serializers.FloatField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', ]
|
||||
|
||||
class BinsizePostSerializer(serializers.ModelSerializer):
|
||||
openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
|
||||
bin_size = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_size_w = serializers.FloatField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_size_d = serializers.FloatField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_size_h = serializers.FloatField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class BinsizeUpdateSerializer(serializers.ModelSerializer):
|
||||
bin_size = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_size_w = serializers.FloatField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_size_d = serializers.FloatField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
bin_size_h = serializers.FloatField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class BinsizePartialUpdateSerializer(serializers.ModelSerializer):
|
||||
bin_size = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
bin_size_w = serializers.FloatField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
bin_size_d = serializers.FloatField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
bin_size_h = serializers.FloatField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class FileRenderSerializer(serializers.ModelSerializer):
|
||||
bin_size = serializers.CharField(read_only=False, required=False)
|
||||
bin_size_w = serializers.FloatField(read_only=False, required=False)
|
||||
bin_size_d = serializers.FloatField(read_only=False, required=False)
|
||||
bin_size_h = serializers.FloatField(read_only=False, required=False)
|
||||
creater = serializers.CharField(read_only=False, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
class Meta:
|
||||
model = ListModel
|
||||
ref_name = 'BinSizeFileRenderSerializer'
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
@@ -0,0 +1,13 @@
|
||||
from django.urls import path, re_path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path(r'', views.APIViewSet.as_view({"get": "list", "post": "create"}), name="binsize"),
|
||||
path(r'file/', views.FileDownloadView.as_view({"get": "list"}), name="binsizefiledownload"),
|
||||
re_path(r'^(?P<pk>\d+)/$', views.APIViewSet.as_view({
|
||||
'get': 'retrieve',
|
||||
'put': 'update',
|
||||
'patch': 'partial_update',
|
||||
'delete': 'destroy'
|
||||
}), name="binsize_1")
|
||||
]
|
||||
@@ -0,0 +1,168 @@
|
||||
from rest_framework import viewsets
|
||||
from .models import ListModel
|
||||
from . import serializers
|
||||
from utils.page import MyPageNumberPagination
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.response import Response
|
||||
from .filter import Filter
|
||||
from rest_framework.exceptions import APIException
|
||||
from .serializers import FileRenderSerializer
|
||||
from django.http import StreamingHttpResponse
|
||||
from .files import FileRenderCN, FileRenderEN
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
class APIViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
retrieve:
|
||||
Response a data list(get)
|
||||
|
||||
list:
|
||||
Response a data list(all)
|
||||
|
||||
create:
|
||||
Create a data line(post)
|
||||
|
||||
delete:
|
||||
Delete a data line(delete)
|
||||
|
||||
partial_update:
|
||||
Partial_update a data(patch:partial_update)
|
||||
|
||||
update:
|
||||
Update a data(put:update)
|
||||
"""
|
||||
pagination_class = MyPageNumberPagination
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_project(self):
|
||||
try:
|
||||
id = self.kwargs.get('pk')
|
||||
return id
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_queryset(self):
|
||||
id = self.get_project()
|
||||
if self.request.user:
|
||||
if id is None:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list', 'retrieve', 'destroy']:
|
||||
return serializers.BinsizeGetSerializer
|
||||
elif self.action in ['create']:
|
||||
return serializers.BinsizePostSerializer
|
||||
elif self.action in ['update']:
|
||||
return serializers.BinsizeUpdateSerializer
|
||||
elif self.action in ['partial_update']:
|
||||
return serializers.BinsizePartialUpdateSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
data = self.request.data
|
||||
data['openid'] = self.request.auth.openid
|
||||
if ListModel.objects.filter(openid=data['openid'], bin_size=data['bin_size'], is_delete=False).exists():
|
||||
raise APIException({"detail": "Data exists"})
|
||||
else:
|
||||
serializer = self.get_serializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def update(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot update data which not yours"})
|
||||
else:
|
||||
data = self.request.data
|
||||
serializer = self.get_serializer(qs, data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def partial_update(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot partial_update data which not yours"})
|
||||
else:
|
||||
data = self.request.data
|
||||
serializer = self.get_serializer(qs, data=data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def destroy(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot delete data which not yours"})
|
||||
else:
|
||||
qs.is_delete = True
|
||||
qs.save()
|
||||
serializer = self.get_serializer(qs, many=False)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
class FileDownloadView(viewsets.ModelViewSet):
|
||||
renderer_classes = (FileRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_project(self):
|
||||
try:
|
||||
id = self.kwargs.get('pk')
|
||||
return id
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_queryset(self):
|
||||
id = self.get_project()
|
||||
if self.request.user:
|
||||
if id is None:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list']:
|
||||
return serializers.FileRenderSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
def get_lang(self, data):
|
||||
lang = self.request.META.get('HTTP_LANGUAGE')
|
||||
if lang:
|
||||
if lang == 'zh-hans':
|
||||
return FileRenderCN().render(data)
|
||||
else:
|
||||
return FileRenderEN().render(data)
|
||||
else:
|
||||
return FileRenderEN().render(data)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
from datetime import datetime
|
||||
dt = datetime.now()
|
||||
data = (
|
||||
FileRenderSerializer(instance).data
|
||||
for instance in self.filter_queryset(self.get_queryset())
|
||||
)
|
||||
renderer = self.get_lang(data)
|
||||
response = StreamingHttpResponse(
|
||||
renderer,
|
||||
content_type="text/csv"
|
||||
)
|
||||
response['Content-Disposition'] = "attachment; filename='binsize_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
|
||||
return response
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from . models import ListModel
|
||||
|
||||
admin.site.register(ListModel)
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class CapitalConfig(AppConfig):
|
||||
name = 'capital'
|
||||
@@ -0,0 +1,38 @@
|
||||
from rest_framework_csv.renderers import CSVStreamingRenderer
|
||||
|
||||
def file_headers():
|
||||
return [
|
||||
'capital_name',
|
||||
'capital_qty',
|
||||
'capital_cost',
|
||||
'creater',
|
||||
'create_time',
|
||||
'update_time'
|
||||
]
|
||||
def cn_data_header():
|
||||
return dict([
|
||||
('capital_name', u'资产名称'),
|
||||
('capital_qty', u'资产数量'),
|
||||
('capital_cost', u'资产成本'),
|
||||
('creater', u'创建人'),
|
||||
('create_time', u'创建时间'),
|
||||
('update_time', u'更新时间')
|
||||
])
|
||||
|
||||
def en_data_header():
|
||||
return dict([
|
||||
('capital_name', u'Capital Name'),
|
||||
('capital_qty', u'Capital Qty'),
|
||||
('capital_cost', u'Capital Cost'),
|
||||
('creater', u'Creater'),
|
||||
('create_time', u'Create Time'),
|
||||
('update_time', u'Update Time')
|
||||
])
|
||||
|
||||
class FileRenderCN(CSVStreamingRenderer):
|
||||
header = file_headers()
|
||||
labels = cn_data_header()
|
||||
|
||||
class FileRenderEN(CSVStreamingRenderer):
|
||||
header = file_headers()
|
||||
labels = en_data_header()
|
||||
@@ -0,0 +1,16 @@
|
||||
from django_filters import FilterSet
|
||||
from .models import ListModel
|
||||
|
||||
class Filter(FilterSet):
|
||||
class Meta:
|
||||
model = ListModel
|
||||
fields = {
|
||||
"id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"capital_name": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"capital_qty": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"capital_cost": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"creater": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
from django.db import models
|
||||
|
||||
class ListModel(models.Model):
|
||||
capital_name = models.CharField(max_length=255, verbose_name="Capital Name")
|
||||
capital_qty = models.BigIntegerField(default=0, verbose_name="Capital Qty")
|
||||
capital_cost = models.FloatField(default=0, verbose_name="Capital Cost")
|
||||
creater = models.CharField(max_length=255, verbose_name="Who Created")
|
||||
openid = models.CharField(max_length=255, verbose_name="Openid")
|
||||
is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
|
||||
update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
|
||||
|
||||
class Meta:
|
||||
db_table = 'capital'
|
||||
verbose_name = 'data id'
|
||||
verbose_name_plural = "data id"
|
||||
ordering = ['-id']
|
||||
|
||||
def __str__(self):
|
||||
return self.pk
|
||||
@@ -0,0 +1,59 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ListModel
|
||||
from utils import datasolve
|
||||
|
||||
class CapitalGetSerializer(serializers.ModelSerializer):
|
||||
capital_name = serializers.CharField(read_only=True, required=False)
|
||||
capital_qty = serializers.IntegerField(read_only=True, required=False)
|
||||
capital_cost = serializers.FloatField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'openid', ]
|
||||
|
||||
class CapitalPostSerializer(serializers.ModelSerializer):
|
||||
openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
|
||||
capital_name = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
capital_qty = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
capital_cost = serializers.FloatField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class CapitalUpdateSerializer(serializers.ModelSerializer):
|
||||
capital_name = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
capital_qty = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
capital_cost = serializers.FloatField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class CapitalPartialUpdateSerializer(serializers.ModelSerializer):
|
||||
capital_name = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
capital_qty = serializers.IntegerField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
capital_cost = serializers.FloatField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class FileRenderSerializer(serializers.ModelSerializer):
|
||||
capital_name = serializers.CharField(read_only=False, required=False)
|
||||
capital_qty = serializers.IntegerField(read_only=False, required=False)
|
||||
capital_cost = serializers.FloatField(read_only=False, required=False)
|
||||
creater = serializers.CharField(read_only=False, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
class Meta:
|
||||
model = ListModel
|
||||
ref_name = 'CapitalFileRenderSerializer'
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
@@ -0,0 +1,13 @@
|
||||
from django.urls import path, re_path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path(r'', views.APIViewSet.as_view({"get": "list", "post": "create"}), name="capital"),
|
||||
path(r'file/', views.FileDownloadView.as_view({"get": "list"}), name="capitalfiledownload"),
|
||||
re_path(r'^(?P<pk>\d+)/$', views.APIViewSet.as_view({
|
||||
'get': 'retrieve',
|
||||
'put': 'update',
|
||||
'patch': 'partial_update',
|
||||
'delete': 'destroy'
|
||||
}), name="capital_1")
|
||||
]
|
||||
@@ -0,0 +1,165 @@
|
||||
from rest_framework import viewsets
|
||||
from .models import ListModel
|
||||
from . import serializers
|
||||
from utils.page import MyPageNumberPagination
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.response import Response
|
||||
from .filter import Filter
|
||||
from rest_framework.exceptions import APIException
|
||||
from .serializers import FileRenderSerializer
|
||||
from django.http import StreamingHttpResponse
|
||||
from .files import FileRenderCN, FileRenderEN
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
class APIViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
retrieve:
|
||||
Response a data list(get)
|
||||
|
||||
list:
|
||||
Response a data list(all)
|
||||
|
||||
create:
|
||||
Create a data line(post)
|
||||
|
||||
delete:
|
||||
Delete a data line(delete)
|
||||
|
||||
partial_update:
|
||||
Partial_update a data(patch:partial_update)
|
||||
|
||||
update:
|
||||
Update a data(put:update)
|
||||
"""
|
||||
pagination_class = MyPageNumberPagination
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_project(self):
|
||||
try:
|
||||
id = self.kwargs.get('pk')
|
||||
return id
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_queryset(self):
|
||||
id = self.get_project()
|
||||
if self.request.user:
|
||||
if id is None:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list', 'retrieve', 'destroy']:
|
||||
return serializers.CapitalGetSerializer
|
||||
elif self.action in ['create']:
|
||||
return serializers.CapitalPostSerializer
|
||||
elif self.action in ['update']:
|
||||
return serializers.CapitalUpdateSerializer
|
||||
elif self.action in ['partial_update']:
|
||||
return serializers.CapitalPartialUpdateSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
data = self.request.data
|
||||
data['openid'] = self.request.auth.openid
|
||||
serializer = self.get_serializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def update(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot update data which not yours"})
|
||||
else:
|
||||
data = self.request.data
|
||||
serializer = self.get_serializer(qs, data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def partial_update(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot partial_update data which not yours"})
|
||||
else:
|
||||
data = self.request.data
|
||||
serializer = self.get_serializer(qs, data=data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def destroy(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot delete data which not yours"})
|
||||
else:
|
||||
qs.is_delete = True
|
||||
qs.save()
|
||||
serializer = self.get_serializer(qs, many=False)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
class FileDownloadView(viewsets.ModelViewSet):
|
||||
renderer_classes = (FileRenderCN, ) + tuple(api_settings.DEFAULT_RENDERER_CLASSES)
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_project(self):
|
||||
try:
|
||||
id = self.kwargs.get('pk')
|
||||
return id
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_queryset(self):
|
||||
id = self.get_project()
|
||||
if self.request.user:
|
||||
if id is None:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list']:
|
||||
return serializers.FileRenderSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
def get_lang(self, data):
|
||||
lang = self.request.META.get('HTTP_LANGUAGE')
|
||||
if lang:
|
||||
if lang == 'zh-hans':
|
||||
return FileRenderCN().render(data)
|
||||
else:
|
||||
return FileRenderEN().render(data)
|
||||
else:
|
||||
return FileRenderEN().render(data)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
from datetime import datetime
|
||||
dt = datetime.now()
|
||||
data = (
|
||||
FileRenderSerializer(instance).data
|
||||
for instance in self.filter_queryset(self.get_queryset())
|
||||
)
|
||||
renderer = self.get_lang(data)
|
||||
response = StreamingHttpResponse(
|
||||
renderer,
|
||||
content_type="text/csv"
|
||||
)
|
||||
response['Content-Disposition'] = "attachment; filename='capital_{}.csv'".format(str(dt.strftime('%Y%m%d%H%M%S%f')))
|
||||
return response
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from . models import ListModel
|
||||
|
||||
admin.site.register(ListModel)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ChatConfig(AppConfig):
|
||||
name = 'chat'
|
||||
@@ -0,0 +1,14 @@
|
||||
from django_filters import FilterSet
|
||||
from .models import ListModel
|
||||
|
||||
class Filter(FilterSet):
|
||||
class Meta:
|
||||
model = ListModel
|
||||
fields = {
|
||||
"id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"read": ['exact', 'iexact'],
|
||||
"detail": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
from django.db import models
|
||||
|
||||
class ListModel(models.Model):
|
||||
sender = models.CharField(max_length=100, verbose_name='Sender')
|
||||
receiver = models.CharField(max_length=100, verbose_name='Receiver')
|
||||
read = models.BooleanField(default=False, verbose_name="Readed")
|
||||
detail = models.CharField(max_length=100, verbose_name='Chat text')
|
||||
is_delete = models.BooleanField(default=False, verbose_name='Delete label')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='Create time')
|
||||
update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name='Update time')
|
||||
|
||||
class Meta:
|
||||
db_table = 'chat'
|
||||
verbose_name = 'data id'
|
||||
verbose_name_plural = "data id"
|
||||
ordering = ['-id']
|
||||
|
||||
def __str__(self):
|
||||
return self.pk
|
||||
@@ -0,0 +1,14 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ListModel
|
||||
|
||||
class ChatGetSerializer(serializers.ModelSerializer):
|
||||
sender = serializers.CharField(read_only=True, required=False)
|
||||
receiver = serializers.CharField(read_only=True, required=False)
|
||||
read = serializers.BooleanField(read_only=True, required=False)
|
||||
detail = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', ]
|
||||
@@ -0,0 +1,7 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path(r'', views.ChatViewSet.as_view({"get": "list"}), name="chat"),
|
||||
path(r'read/', views.ReadAPI.as_view({"get": "list"}), name='read')
|
||||
]
|
||||
@@ -0,0 +1,57 @@
|
||||
from django.db.models import Q
|
||||
from rest_framework import viewsets
|
||||
from .models import ListModel
|
||||
from . import serializers
|
||||
from utils.page import MyPageNumberPagination
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .filter import Filter
|
||||
|
||||
class ChatViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
list:
|
||||
Response a data list(all)
|
||||
"""
|
||||
pagination_class = MyPageNumberPagination
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user:
|
||||
sender = str(self.request.GET.get('sender', '')) + '-' + self.request.auth.openid
|
||||
receiver = str(self.request.GET.get('receiver', '')) + '-' + self.request.auth.openid
|
||||
if ListModel.objects.filter(sender=receiver, receiver=sender, read=False).exists():
|
||||
ListModel.objects.filter(sender=receiver, receiver=sender, read=False).update(read=True)
|
||||
return ListModel.objects.filter(Q(sender=sender, receiver=receiver) | Q(sender=receiver, receiver=sender))
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list']:
|
||||
return serializers.ChatGetSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
class ReadAPI(viewsets.ModelViewSet):
|
||||
"""
|
||||
list:
|
||||
Response a data list(all)
|
||||
"""
|
||||
pagination_class = MyPageNumberPagination
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user:
|
||||
sender = str(self.request.GET.get('sender', '')) + '-' + self.request.auth.openid
|
||||
return ListModel.objects.filter(receiver=sender, read=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list']:
|
||||
return serializers.ChatGetSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from . models import ListModel
|
||||
|
||||
admin.site.register(ListModel)
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class CompanyConfig(AppConfig):
|
||||
name = 'company'
|
||||
@@ -0,0 +1,18 @@
|
||||
from django_filters import FilterSet
|
||||
from .models import ListModel
|
||||
|
||||
class Filter(FilterSet):
|
||||
class Meta:
|
||||
model = ListModel
|
||||
fields = {
|
||||
"id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"company_name": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"company_city": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"company_address": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"company_contact": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"company_manager": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"creater": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
from django.db import models
|
||||
|
||||
class ListModel(models.Model):
|
||||
company_name = models.CharField(max_length=255, verbose_name="Company Name")
|
||||
company_city = models.CharField(max_length=255, verbose_name="Company City")
|
||||
company_address = models.CharField(max_length=255, verbose_name="Company Address")
|
||||
company_contact = models.BigIntegerField(default=0, verbose_name="Company Contact")
|
||||
company_manager = models.CharField(max_length=255, verbose_name="Company Manager")
|
||||
creater = models.CharField(max_length=255, verbose_name="Who Created")
|
||||
openid = models.CharField(max_length=255, verbose_name="Openid")
|
||||
is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
|
||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
|
||||
update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
|
||||
|
||||
class Meta:
|
||||
db_table = 'company'
|
||||
verbose_name = 'data id'
|
||||
verbose_name_plural = "data id"
|
||||
ordering = ['company_name']
|
||||
|
||||
def __str__(self):
|
||||
return self.pk
|
||||
@@ -0,0 +1,54 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ListModel
|
||||
from utils import datasolve
|
||||
|
||||
class CompanyGetSerializer(serializers.ModelSerializer):
|
||||
company_name = serializers.CharField(read_only=True, required=False)
|
||||
company_city = serializers.CharField(read_only=True, required=False)
|
||||
company_address = serializers.CharField(read_only=True, required=False)
|
||||
company_contact = serializers.IntegerField(read_only=True, required=False)
|
||||
company_manager = serializers.CharField(read_only=True, required=False)
|
||||
creater = serializers.CharField(read_only=True, required=False)
|
||||
create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
update_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M:%S')
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id']
|
||||
|
||||
class CompanyPostSerializer(serializers.ModelSerializer):
|
||||
openid = serializers.CharField(read_only=False, required=False, validators=[datasolve.openid_validate])
|
||||
company_name = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
company_city = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
company_address = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
company_contact = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
company_manager = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class CompanyUpdateSerializer(serializers.ModelSerializer):
|
||||
company_name = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
company_city = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
company_address = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
company_contact = serializers.IntegerField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
company_manager = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
|
||||
class CompanyPartialUpdateSerializer(serializers.ModelSerializer):
|
||||
company_name = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
company_city = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
company_address = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
company_contact = serializers.IntegerField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
company_manager = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
creater = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate])
|
||||
class Meta:
|
||||
model = ListModel
|
||||
exclude = ['openid', 'is_delete', ]
|
||||
read_only_fields = ['id', 'create_time', 'update_time', ]
|
||||
@@ -0,0 +1,12 @@
|
||||
from django.urls import path, re_path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path(r'', views.APIViewSet.as_view({"get": "list", "post": "create"}), name="company"),
|
||||
re_path(r'^(?P<pk>\d+)/$', views.APIViewSet.as_view({
|
||||
'get': 'retrieve',
|
||||
'put': 'update',
|
||||
'patch': 'partial_update',
|
||||
'delete': 'destroy'
|
||||
}), name="company_1")
|
||||
]
|
||||
@@ -0,0 +1,114 @@
|
||||
from rest_framework import viewsets
|
||||
from .models import ListModel
|
||||
from . import serializers
|
||||
from utils.page import MyPageNumberPagination
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.response import Response
|
||||
from .filter import Filter
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
class APIViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
retrieve:
|
||||
Response a data list(get)
|
||||
|
||||
list:
|
||||
Response a data list(all)
|
||||
|
||||
create:
|
||||
Create a data line(post)
|
||||
|
||||
delete:
|
||||
Delete a data line(delete)
|
||||
|
||||
partial_update:
|
||||
Partial_update a data(patch:partial_update)
|
||||
|
||||
update:
|
||||
Update a data(put:update)
|
||||
"""
|
||||
pagination_class = MyPageNumberPagination
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, ]
|
||||
ordering_fields = ['id', "create_time", "update_time", ]
|
||||
filter_class = Filter
|
||||
|
||||
def get_project(self):
|
||||
try:
|
||||
id = self.kwargs.get('pk')
|
||||
return id
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_queryset(self):
|
||||
id = self.get_project()
|
||||
if self.request.user:
|
||||
if id is None:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.filter(openid=self.request.auth.openid, id=id, is_delete=False)
|
||||
else:
|
||||
return ListModel.objects.none()
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['list', 'retrieve', 'destroy']:
|
||||
return serializers.CompanyGetSerializer
|
||||
elif self.action in ['create']:
|
||||
return serializers.CompanyPostSerializer
|
||||
elif self.action in ['update']:
|
||||
return serializers.CompanyUpdateSerializer
|
||||
elif self.action in ['partial_update']:
|
||||
return serializers.CompanyPartialUpdateSerializer
|
||||
else:
|
||||
return self.http_method_not_allowed(request=self.request)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
data = self.request.data
|
||||
data['openid'] = self.request.auth.openid
|
||||
if ListModel.objects.filter(openid=data['openid'], company_name=data['company_name'], is_delete=False).exists():
|
||||
raise APIException({"detail": "Data exists"})
|
||||
else:
|
||||
if ListModel.objects.filter(openid=data['openid'], is_delete=False).count() >= 1:
|
||||
raise APIException({"detail": "You Just Can Create 1 Company"})
|
||||
else:
|
||||
serializer = self.get_serializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def update(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot update data which not yours"})
|
||||
else:
|
||||
data = self.request.data
|
||||
serializer = self.get_serializer(qs, data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def partial_update(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot partial_update data which not yours"})
|
||||
else:
|
||||
data = self.request.data
|
||||
serializer = self.get_serializer(qs, data=data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
def destroy(self, request, pk):
|
||||
qs = self.get_object()
|
||||
if qs.openid != self.request.auth.openid:
|
||||
raise APIException({"detail": "Cannot delete data which not yours"})
|
||||
else:
|
||||
qs.is_delete = True
|
||||
qs.save()
|
||||
serializer = self.get_serializer(qs, many=False)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=200, headers=headers)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from . models import ListModel
|
||||
|
||||
admin.site.register(ListModel)
|
||||
@@ -0,0 +1,4 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class CustomerConfig(AppConfig):
|
||||
name = 'customer'
|
||||
@@ -0,0 +1,49 @@
|
||||
from rest_framework_csv.renderers import CSVStreamingRenderer
|
||||
|
||||
def file_headers():
|
||||
return [
|
||||
'customer_name',
|
||||
'customer_city',
|
||||
'customer_address',
|
||||
'customer_contact',
|
||||
'customer_manager',
|
||||
'customer_level',
|
||||
'creater',
|
||||
'create_time',
|
||||
'update_time'
|
||||
]
|
||||
|
||||
def cn_data_header():
|
||||
return dict([
|
||||
('customer_name', u'客户名称'),
|
||||
('customer_city', u'客户城市'),
|
||||
('customer_address', u'详细地址'),
|
||||
('customer_contact', u'联系电话'),
|
||||
('customer_manager', u'负责人'),
|
||||
('customer_level', u'客户等级'),
|
||||
('creater', u'创建人'),
|
||||
('create_time', u'创建时间'),
|
||||
('update_time', u'更新时间'),
|
||||
])
|
||||
|
||||
def en_data_header():
|
||||
return dict([
|
||||
('customer_name', u'Customer Name'),
|
||||
('customer_city', u'Customer City'),
|
||||
('customer_address', u'Customer Address'),
|
||||
('customer_contact', u'Customer Contact'),
|
||||
('customer_manager', u'Customer Manager'),
|
||||
('customer_level', u'Customer Level'),
|
||||
('creater', u'Creater'),
|
||||
('create_time', u'Create Time'),
|
||||
('update_time', u'Update Time'),
|
||||
])
|
||||
|
||||
|
||||
class FileRenderCN(CSVStreamingRenderer):
|
||||
header = file_headers()
|
||||
labels = cn_data_header()
|
||||
|
||||
class FileRenderEN(CSVStreamingRenderer):
|
||||
header = file_headers()
|
||||
labels = en_data_header()
|
||||
@@ -0,0 +1,20 @@
|
||||
from django_filters import FilterSet
|
||||
from .models import ListModel
|
||||
|
||||
class Filter(FilterSet):
|
||||
class Meta:
|
||||
model = ListModel
|
||||
fields = {
|
||||
"id": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"customer_name": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"customer_city": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"customer_address": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"customer_contact": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"customer_manager": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"customer_level": ['exact', 'iexact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
|
||||
"creater": ['exact', 'iexact', 'contains', 'icontains'],
|
||||
"is_delete": ['exact', 'iexact'],
|
||||
"create_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range'],
|
||||
"update_time": ['year', 'month', 'day', 'week_day', 'gt', 'gte', 'lt', 'lte', 'range']
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user