valar 1.2.4__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of valar might be problematic. Click here for more details.
- {valar-1.2.4/src/valar.egg-info → valar-1.3.0}/PKG-INFO +24 -13
- {valar-1.2.4 → valar-1.3.0}/README.md +22 -12
- {valar-1.2.4 → valar-1.3.0}/setup.py +2 -2
- {valar-1.2.4 → valar-1.3.0}/src/valar/apps.py +10 -4
- valar-1.3.0/src/valar/auth/Authentication.py +32 -0
- valar-1.3.0/src/valar/auth/Middleware.py +28 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/channels/sender.py +4 -1
- valar-1.3.0/src/valar/classes/valar_response.py +9 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/abstract.py +6 -3
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/defaults/field_keys_default.py +10 -1
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/engine.py +9 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/orm_field.py +4 -6
- valar-1.3.0/src/valar/models/__init__.py +1 -0
- valar-1.3.0/src/valar/models/auth.py +47 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/urls.py +10 -4
- valar-1.3.0/src/valar/views/__init__.py +0 -0
- valar-1.3.0/src/valar/views/auth.py +110 -0
- valar-1.3.0/src/valar/views/password.py +60 -0
- {valar-1.2.4 → valar-1.3.0/src/valar.egg-info}/PKG-INFO +24 -13
- {valar-1.2.4 → valar-1.3.0}/src/valar.egg-info/SOURCES.txt +6 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar.egg-info/requires.txt +1 -0
- valar-1.2.4/src/valar/classes/valar_response.py +0 -8
- valar-1.2.4/src/valar/models/__init__.py +0 -1
- {valar-1.2.4 → valar-1.3.0}/LICENSE +0 -0
- {valar-1.2.4 → valar-1.3.0}/setup.cfg +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/__init__.py +0 -0
- {valar-1.2.4/src/valar/channels → valar-1.3.0/src/valar/auth}/__init__.py +0 -0
- {valar-1.2.4/src/valar/classes → valar-1.3.0/src/valar/channels}/__init__.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/channels/consumer.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/channels/counter.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/channels/executer.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/channels/mapping.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/channels/views.py +0 -0
- {valar-1.2.4/src/valar/classes/app_mixins → valar-1.3.0/src/valar/classes}/__init__.py +0 -0
- {valar-1.2.4/src/valar/dao/defaults → valar-1.3.0/src/valar/classes/app_mixins}/__init__.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/classes/app_mixins/auto_migration_mixin.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/classes/app_mixins/auto_urlpatterns_mixin.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/classes/singleton_meta.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/classes/valar_minio.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/__init__.py +0 -0
- {valar-1.2.4/src/valar/migrations → valar-1.3.0/src/valar/dao/defaults}/__init__.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/defaults/field_values_default.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/defaults/view_defaults.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/frame.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/meta.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/mon_dao.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/mon_field.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/orm_dao.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/dao/query.py +0 -0
- {valar-1.2.4/src/valar/views → valar-1.3.0/src/valar/migrations}/__init__.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/models/core.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/models/frame.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/models/meta.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/models/test.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/views/file.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/views/handler.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/views/meta.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar/views/rest.py +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar.egg-info/dependency_links.txt +0 -0
- {valar-1.2.4 → valar-1.3.0}/src/valar.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: valar
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: valar for morghulis
|
|
5
5
|
Home-page: https://gitee.com/GRIFFIN120/valar_dev
|
|
6
6
|
Author: LYP
|
|
@@ -16,6 +16,7 @@ Requires-Dist: pandas==2.2.3
|
|
|
16
16
|
Requires-Dist: openpyxl==3.1.5
|
|
17
17
|
Requires-Dist: deepmerge~=2.0
|
|
18
18
|
Requires-Dist: minio==7.2.2
|
|
19
|
+
Requires-Dist: PyJWT~=2.10.1
|
|
19
20
|
Dynamic: author
|
|
20
21
|
Dynamic: author-email
|
|
21
22
|
Dynamic: description
|
|
@@ -30,11 +31,20 @@ valar for morghulis
|
|
|
30
31
|
|
|
31
32
|
# 1. installation
|
|
32
33
|
|
|
34
|
+
- you need to install valar in a django project
|
|
35
|
+
|
|
33
36
|
```shell
|
|
34
37
|
pip install valar
|
|
35
38
|
```
|
|
36
39
|
|
|
37
|
-
#
|
|
40
|
+
# 2. root app
|
|
41
|
+
|
|
42
|
+
- you only need 3 files in your root app
|
|
43
|
+
- settings.py
|
|
44
|
+
- asgi.py
|
|
45
|
+
- urls.py
|
|
46
|
+
|
|
47
|
+
## 2.1 settings.py
|
|
38
48
|
|
|
39
49
|
```python
|
|
40
50
|
from pathlib import Path
|
|
@@ -99,7 +109,7 @@ MINIO_ROOT = "https://host:9001"
|
|
|
99
109
|
|
|
100
110
|
```
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
## 2.2 asgi.py
|
|
103
113
|
|
|
104
114
|
```python
|
|
105
115
|
from django.core.asgi import get_asgi_application
|
|
@@ -118,18 +128,19 @@ application = ProtocolTypeRouter({
|
|
|
118
128
|
|
|
119
129
|
```
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
## 2.3 urls.py
|
|
122
132
|
|
|
123
|
-
- no need to
|
|
133
|
+
- no need to provide urls for Valar, Valar will auto set urlpatterns for Morghulis (
|
|
134
|
+
see https://www.npmjs.com/package/morghulis)
|
|
135
|
+
- go to section 4 to see how to register channel handlers (a Vue - Django async communication tool) in urls.
|
|
124
136
|
|
|
125
|
-
#
|
|
137
|
+
# 3. migrate
|
|
126
138
|
|
|
127
|
-
no need to
|
|
128
|
-
see https://www.npmjs.com/package/morghulis)
|
|
139
|
+
- no need to makemigrations and migrate for valar, valar will auto migration
|
|
129
140
|
|
|
130
|
-
#
|
|
141
|
+
# 4. how to register a channel handler for Morghulis async methods
|
|
131
142
|
|
|
132
|
-
##
|
|
143
|
+
## 4.1 create a handler
|
|
133
144
|
|
|
134
145
|
```python
|
|
135
146
|
import time
|
|
@@ -148,7 +159,7 @@ def valar_test_handler(sender: ValarChannelSender):
|
|
|
148
159
|
sender.load(tick)
|
|
149
160
|
```
|
|
150
161
|
|
|
151
|
-
###
|
|
162
|
+
### 4.2 create a dict (e.g. using the name 'channel_mapping') to save your handler
|
|
152
163
|
|
|
153
164
|
- I'd like to put it in the root urls.py, you can put it anywhere
|
|
154
165
|
|
|
@@ -158,13 +169,13 @@ channel_mapping = {
|
|
|
158
169
|
}
|
|
159
170
|
```
|
|
160
171
|
|
|
161
|
-
##
|
|
172
|
+
## 4.3 register the channel_mapping in the settings.py
|
|
162
173
|
|
|
163
174
|
```python
|
|
164
175
|
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
165
176
|
```
|
|
166
177
|
|
|
167
|
-
###
|
|
178
|
+
### 4.4 you can copy the following codes to your urls.py
|
|
168
179
|
|
|
169
180
|
```python
|
|
170
181
|
import json
|
|
@@ -2,11 +2,20 @@ valar for morghulis
|
|
|
2
2
|
|
|
3
3
|
# 1. installation
|
|
4
4
|
|
|
5
|
+
- you need to install valar in a django project
|
|
6
|
+
|
|
5
7
|
```shell
|
|
6
8
|
pip install valar
|
|
7
9
|
```
|
|
8
10
|
|
|
9
|
-
#
|
|
11
|
+
# 2. root app
|
|
12
|
+
|
|
13
|
+
- you only need 3 files in your root app
|
|
14
|
+
- settings.py
|
|
15
|
+
- asgi.py
|
|
16
|
+
- urls.py
|
|
17
|
+
|
|
18
|
+
## 2.1 settings.py
|
|
10
19
|
|
|
11
20
|
```python
|
|
12
21
|
from pathlib import Path
|
|
@@ -71,7 +80,7 @@ MINIO_ROOT = "https://host:9001"
|
|
|
71
80
|
|
|
72
81
|
```
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
## 2.2 asgi.py
|
|
75
84
|
|
|
76
85
|
```python
|
|
77
86
|
from django.core.asgi import get_asgi_application
|
|
@@ -90,18 +99,19 @@ application = ProtocolTypeRouter({
|
|
|
90
99
|
|
|
91
100
|
```
|
|
92
101
|
|
|
93
|
-
|
|
102
|
+
## 2.3 urls.py
|
|
94
103
|
|
|
95
|
-
- no need to
|
|
104
|
+
- no need to provide urls for Valar, Valar will auto set urlpatterns for Morghulis (
|
|
105
|
+
see https://www.npmjs.com/package/morghulis)
|
|
106
|
+
- go to section 4 to see how to register channel handlers (a Vue - Django async communication tool) in urls.
|
|
96
107
|
|
|
97
|
-
#
|
|
108
|
+
# 3. migrate
|
|
98
109
|
|
|
99
|
-
no need to
|
|
100
|
-
see https://www.npmjs.com/package/morghulis)
|
|
110
|
+
- no need to makemigrations and migrate for valar, valar will auto migration
|
|
101
111
|
|
|
102
|
-
#
|
|
112
|
+
# 4. how to register a channel handler for Morghulis async methods
|
|
103
113
|
|
|
104
|
-
##
|
|
114
|
+
## 4.1 create a handler
|
|
105
115
|
|
|
106
116
|
```python
|
|
107
117
|
import time
|
|
@@ -120,7 +130,7 @@ def valar_test_handler(sender: ValarChannelSender):
|
|
|
120
130
|
sender.load(tick)
|
|
121
131
|
```
|
|
122
132
|
|
|
123
|
-
###
|
|
133
|
+
### 4.2 create a dict (e.g. using the name 'channel_mapping') to save your handler
|
|
124
134
|
|
|
125
135
|
- I'd like to put it in the root urls.py, you can put it anywhere
|
|
126
136
|
|
|
@@ -130,13 +140,13 @@ channel_mapping = {
|
|
|
130
140
|
}
|
|
131
141
|
```
|
|
132
142
|
|
|
133
|
-
##
|
|
143
|
+
## 4.3 register the channel_mapping in the settings.py
|
|
134
144
|
|
|
135
145
|
```python
|
|
136
146
|
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
137
147
|
```
|
|
138
148
|
|
|
139
|
-
###
|
|
149
|
+
### 4.4 you can copy the following codes to your urls.py
|
|
140
150
|
|
|
141
151
|
```python
|
|
142
152
|
import json
|
|
@@ -12,11 +12,11 @@ requires = [
|
|
|
12
12
|
'openpyxl==3.1.5',
|
|
13
13
|
'deepmerge~=2.0',
|
|
14
14
|
'minio==7.2.2',
|
|
15
|
+
'PyJWT~=2.10.1'
|
|
15
16
|
]
|
|
16
|
-
|
|
17
17
|
setup(
|
|
18
18
|
name="valar", # 包名
|
|
19
|
-
version="1.
|
|
19
|
+
version="1.3.0", # 版本号
|
|
20
20
|
author="LYP", # 作者
|
|
21
21
|
author_email="liuyinpeng@buaa.edu.cn", # 邮箱
|
|
22
22
|
description="valar for morghulis", # 简短描述
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
2
4
|
from django.apps import AppConfig
|
|
3
5
|
|
|
4
6
|
from .classes.app_mixins.auto_migration_mixin import AutoMigrationMixin
|
|
@@ -11,7 +13,11 @@ class ValarConfig(AutoMigrationMixin, AutoUrlPatternsMixin, AppConfig):
|
|
|
11
13
|
|
|
12
14
|
def ready(self):
|
|
13
15
|
if os.environ.get('RUN_MAIN') == 'true':
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
try:
|
|
17
|
+
from .dao.frame import MetaFrame
|
|
18
|
+
getattr(super(), 'set_url', None)()
|
|
19
|
+
getattr(super(), 'auto_migrate', None)()
|
|
20
|
+
MetaFrame()
|
|
21
|
+
except Exception as e:
|
|
22
|
+
traceback.print_exc()
|
|
23
|
+
print('ERROR', e)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import jwt
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
|
|
4
|
+
from src.valar.classes.valar_response import ValarResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def auth_required(view_func):
|
|
8
|
+
def wrapper(request, *args, **kwargs):
|
|
9
|
+
payload, response = validate(request)
|
|
10
|
+
if payload:
|
|
11
|
+
request.user_id = payload["user_id"]
|
|
12
|
+
return view_func(request, *args, **kwargs)
|
|
13
|
+
else:
|
|
14
|
+
return response
|
|
15
|
+
|
|
16
|
+
return wrapper
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def validate(request):
|
|
20
|
+
auth_header = request.headers.get("Authorization")
|
|
21
|
+
if not auth_header or not auth_header.startswith("Bearer "):
|
|
22
|
+
return None, ValarResponse(False, '请登录系统', 'info', status=401)
|
|
23
|
+
token = auth_header.split(" ")[1]
|
|
24
|
+
if not token:
|
|
25
|
+
return None, ValarResponse(False, status=401)
|
|
26
|
+
try:
|
|
27
|
+
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
|
28
|
+
return payload, ValarResponse(False)
|
|
29
|
+
except jwt.ExpiredSignatureError:
|
|
30
|
+
return None, ValarResponse(False, '状态已过期,请重新登录', 'warning', status=401)
|
|
31
|
+
except jwt.InvalidTokenError:
|
|
32
|
+
return None, ValarResponse(False, '错误状态,请重新登录', 'error', status=401)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from django.http import HttpRequest
|
|
5
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
6
|
+
|
|
7
|
+
from src.valar.auth.Authentication import validate
|
|
8
|
+
from src.valar.classes.valar_response import ValarResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ValarMiddleware(MiddlewareMixin):
|
|
12
|
+
@staticmethod
|
|
13
|
+
def process_response(request: HttpRequest, response: ValarResponse):
|
|
14
|
+
auth = request.headers.get("Auth")
|
|
15
|
+
if auth:
|
|
16
|
+
payload, _ = validate(request)
|
|
17
|
+
if not payload:
|
|
18
|
+
return ValarResponse(False, '无效权限', 'error', status=403)
|
|
19
|
+
if type(response) == ValarResponse:
|
|
20
|
+
valar_message, valar_code = response.valar_message, response.valar_code
|
|
21
|
+
data = {
|
|
22
|
+
'payload': json.loads(response.content),
|
|
23
|
+
'message': valar_message,
|
|
24
|
+
'code': valar_code
|
|
25
|
+
}
|
|
26
|
+
response.content = json.dumps(data, ensure_ascii=False).encode("utf-8")
|
|
27
|
+
response["Content-Type"] = "application/json; charset=utf-8"
|
|
28
|
+
return response
|
|
@@ -7,6 +7,7 @@ from channels.layers import get_channel_layer
|
|
|
7
7
|
from django.http import HttpRequest
|
|
8
8
|
import threading
|
|
9
9
|
from .consumer import VALAR_CHANNEL_GROUP
|
|
10
|
+
from ..auth.Authentication import validate
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class Channel:
|
|
@@ -54,7 +55,9 @@ class ValarChannelSender(Sender):
|
|
|
54
55
|
self.__lock__ = threading.Lock()
|
|
55
56
|
self.__interval__ = interval
|
|
56
57
|
if self.__channel__.auth and not self.uid:
|
|
57
|
-
|
|
58
|
+
payload, _ = validate(request)
|
|
59
|
+
if payload is None:
|
|
60
|
+
raise Exception('Unauthorized!')
|
|
58
61
|
|
|
59
62
|
def _run(self):
|
|
60
63
|
while self.__loading__:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from django.http import JsonResponse
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ValarResponse(JsonResponse):
|
|
5
|
+
def __init__(self, data=True, message='', code='info', status=200):
|
|
6
|
+
self.valar_message = message
|
|
7
|
+
self.valar_code = code
|
|
8
|
+
self.status_code = status
|
|
9
|
+
super(ValarResponse, self).__init__(data, safe=False)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
|
|
3
3
|
from bson import ObjectId
|
|
4
|
+
from django.db.models import QuerySet
|
|
4
5
|
|
|
5
6
|
from ..dao.engine import ValarEngine
|
|
7
|
+
from ..models.core import VModel
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class AbstractField(ABC):
|
|
@@ -12,6 +14,7 @@ class AbstractField(ABC):
|
|
|
12
14
|
label = None
|
|
13
15
|
domain = None
|
|
14
16
|
refer = None
|
|
17
|
+
model_field = None
|
|
15
18
|
|
|
16
19
|
@abstractmethod
|
|
17
20
|
def to_dict(self):
|
|
@@ -51,7 +54,7 @@ class AbstractDao(ABC):
|
|
|
51
54
|
pass
|
|
52
55
|
|
|
53
56
|
@abstractmethod
|
|
54
|
-
def values(self, conditions, props):
|
|
57
|
+
def values(self, conditions, props) -> list:
|
|
55
58
|
pass
|
|
56
59
|
|
|
57
60
|
@abstractmethod
|
|
@@ -59,11 +62,11 @@ class AbstractDao(ABC):
|
|
|
59
62
|
pass
|
|
60
63
|
|
|
61
64
|
@abstractmethod
|
|
62
|
-
def find_one(self, _id):
|
|
65
|
+
def find_one(self, _id) -> VModel:
|
|
63
66
|
pass
|
|
64
67
|
|
|
65
68
|
@abstractmethod
|
|
66
|
-
def find(self, conditions=None, orders=None, size=0, page=1):
|
|
69
|
+
def find(self, conditions=None, orders=None, size=0, page=1) -> (QuerySet, int):
|
|
67
70
|
pass
|
|
68
71
|
|
|
69
72
|
@abstractmethod
|
|
@@ -39,10 +39,19 @@ meta_field_key_defaults = {
|
|
|
39
39
|
]
|
|
40
40
|
),
|
|
41
41
|
},
|
|
42
|
-
'valar.
|
|
42
|
+
'valar.Vala': {
|
|
43
43
|
'simple': ('pick', ['id', 'name', 'text_field', 'boolean_field', 'integer_field', 'float_field']),
|
|
44
44
|
'date': ('pick', ['id', 'name', 'date_field', 'datetime_field', 'time_field']),
|
|
45
45
|
'special': ('pick', ['id', 'name', 'text_field', 'json_field', 'file', 'm2m']),
|
|
46
46
|
'ref': ('pick', ['id', 'name', 'm2o', 'm2m', 'o2o_id']),
|
|
47
47
|
},
|
|
48
|
+
'valar.Account': {
|
|
49
|
+
'auth': ('pick', ['user_id', 'username', 'email', 'is_active', 'is_admin', 'roles']),
|
|
50
|
+
},
|
|
51
|
+
'valar.Role': {
|
|
52
|
+
'auth': ('pick', ['name', 'duty', 'menu', 'account']),
|
|
53
|
+
},
|
|
54
|
+
'valar.Menu': {
|
|
55
|
+
'auth': ('pick', ['icon', 'path', 'name', 'roles', 'is_admin', 'scope']),
|
|
56
|
+
},
|
|
48
57
|
}
|
|
@@ -2,6 +2,8 @@ import pymongo
|
|
|
2
2
|
from django.apps import apps
|
|
3
3
|
from django.conf import settings
|
|
4
4
|
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
from django.core.mail import EmailMessage
|
|
5
7
|
from minio import Minio
|
|
6
8
|
from pymongo.synchronous.collection import Collection
|
|
7
9
|
|
|
@@ -55,6 +57,13 @@ class ValarEngine(metaclass=SingletonMeta):
|
|
|
55
57
|
else:
|
|
56
58
|
self.minio_engine = None
|
|
57
59
|
|
|
60
|
+
self.from_email = settings.EMAIL_HOST_USER
|
|
61
|
+
|
|
62
|
+
def send_email(self, title, content, email):
|
|
63
|
+
e = EmailMessage(title, content, self.from_email, [email])
|
|
64
|
+
e.content_subtype = 'html'
|
|
65
|
+
e.send()
|
|
66
|
+
|
|
58
67
|
def get_orm_model(self, entity) -> VModel:
|
|
59
68
|
return self.orm_engine[entity]
|
|
60
69
|
|
|
@@ -131,12 +131,10 @@ class OrmField(AbstractField):
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def column_width(domain, tool):
|
|
134
|
-
if domain in ['BooleanField', '
|
|
135
|
-
return 80
|
|
136
|
-
elif domain in ['DateField', 'DateTimeField', 'TimeField']:
|
|
134
|
+
if domain in ['BooleanField', 'DateField', 'DateTimeField', 'TimeField']:
|
|
137
135
|
return 120
|
|
138
|
-
elif domain in ['ManyToManyRel', '
|
|
139
|
-
return
|
|
140
|
-
elif domain in ['TextField']:
|
|
136
|
+
elif domain in ['ManyToManyRel', 'ManyToManyField', 'ManyToOneRel']:
|
|
137
|
+
return 120
|
|
138
|
+
elif domain in ['TextField', 'FileField', 'JSONField']:
|
|
141
139
|
return 80 if tool == 'rich' else 0
|
|
142
140
|
return 0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import core, frame, meta, auth, test
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from .core import VModel, VTree
|
|
3
|
+
from django.contrib.auth.hashers import check_password
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Role(VModel):
|
|
7
|
+
duty = models.TextField(null=True, verbose_name='职责描述')
|
|
8
|
+
|
|
9
|
+
class Meta:
|
|
10
|
+
verbose_name = '角色'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Account(VModel):
|
|
14
|
+
"""账户核心信息"""
|
|
15
|
+
username = models.CharField(max_length=255, null=True, unique=True, verbose_name='账号')
|
|
16
|
+
password = models.CharField(max_length=255, verbose_name='密码')
|
|
17
|
+
email = models.CharField(max_length=255, null=True, unique=True, verbose_name='邮箱')
|
|
18
|
+
"""权限"""
|
|
19
|
+
is_admin = models.BooleanField(default=False, verbose_name='超级管理员')
|
|
20
|
+
roles = models.ManyToManyField(Role)
|
|
21
|
+
"""密码找回"""
|
|
22
|
+
is_active = models.BooleanField(default=False, verbose_name='激活状态')
|
|
23
|
+
token = models.CharField(max_length=255, null=True, verbose_name='Token')
|
|
24
|
+
|
|
25
|
+
def is_auth(self, password):
|
|
26
|
+
return password == self.token or check_password(password, self.password)
|
|
27
|
+
|
|
28
|
+
class Meta:
|
|
29
|
+
verbose_name = '账户信息'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Menu(VTree):
|
|
33
|
+
scope = models.CharField(max_length=100, null=True, verbose_name='域')
|
|
34
|
+
path = models.CharField(max_length=255, null=True, verbose_name='地址')
|
|
35
|
+
roles = models.ManyToManyField(Role)
|
|
36
|
+
is_admin = models.BooleanField(null=True, default=False, verbose_name='超管权限')
|
|
37
|
+
|
|
38
|
+
class Meta:
|
|
39
|
+
verbose_name = '菜单'
|
|
40
|
+
unique_together = ('scope', 'path')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AbstractUser(VModel):
|
|
44
|
+
account = models.OneToOneField(Account, null=True, on_delete=models.SET_NULL)
|
|
45
|
+
|
|
46
|
+
class Meta:
|
|
47
|
+
abstract = True
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from django.urls import path
|
|
2
|
-
|
|
3
1
|
from .channels.views import handel_channel
|
|
4
|
-
from .views import rest, meta, file
|
|
2
|
+
from .views import rest, meta, file, auth, password
|
|
3
|
+
from django.urls import path
|
|
5
4
|
|
|
6
5
|
urlpatterns = [
|
|
7
6
|
path('socket/<str:handler>', handel_channel),
|
|
@@ -13,7 +12,6 @@ urlpatterns = [
|
|
|
13
12
|
path('<str:db>/<str:entity>/update', rest.update),
|
|
14
13
|
path('<str:db>/<str:entity>/search', rest.search),
|
|
15
14
|
path('<str:db>/<str:entity>/values', rest.values),
|
|
16
|
-
# path('<str:db>/<str:entity>/tree', rest.tree),
|
|
17
15
|
|
|
18
16
|
path('<str:db>/<str:entity>/meta_view', meta.meta_view),
|
|
19
17
|
path('metas', meta.metas),
|
|
@@ -26,4 +24,12 @@ urlpatterns = [
|
|
|
26
24
|
path('<str:db>/<str:entity>/save_file', file.save_file),
|
|
27
25
|
path('<str:db>/<str:entity>/remove_file', file.remove_file),
|
|
28
26
|
|
|
27
|
+
path('sign_in', auth.sign_in),
|
|
28
|
+
path("user_profile", auth.user_profile),
|
|
29
|
+
path("free_menus", auth.free_menus),
|
|
30
|
+
|
|
31
|
+
path("change_password", password.change_password),
|
|
32
|
+
path("retrieve_password", password.retrieve_password),
|
|
33
|
+
path("send_password", password.send_password),
|
|
34
|
+
|
|
29
35
|
]
|
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import jwt
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.db.models import OneToOneRel
|
|
7
|
+
from django.contrib.auth.hashers import make_password
|
|
8
|
+
|
|
9
|
+
from ..auth.Authentication import auth_required
|
|
10
|
+
from ..classes.valar_response import ValarResponse
|
|
11
|
+
from ..dao import Dao
|
|
12
|
+
from ..models.auth import Account, AbstractUser, Menu
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def sign_in(request):
|
|
16
|
+
body = json.loads(request.body)
|
|
17
|
+
username = body.get("username")
|
|
18
|
+
email = body.get("email")
|
|
19
|
+
password = body.get("password")
|
|
20
|
+
signin = body.get("signin")
|
|
21
|
+
dao = Dao('valar.Account')
|
|
22
|
+
account = dao.search({"username": username}).first()
|
|
23
|
+
if account is not None:
|
|
24
|
+
if signin:
|
|
25
|
+
if not account.is_auth(password):
|
|
26
|
+
return ValarResponse(False, '密码错误', 'warning', status=400)
|
|
27
|
+
else:
|
|
28
|
+
return ValarResponse(False, f"{username}已被占用", 'warning', status=400)
|
|
29
|
+
else:
|
|
30
|
+
if signin:
|
|
31
|
+
return ValarResponse(False, f"{username}不存在", 'warning', status=400)
|
|
32
|
+
else:
|
|
33
|
+
if username == 'admin' and password != settings.SECRET_KEY:
|
|
34
|
+
return ValarResponse(False, "请输入正确的admin密码", 'warning', status=400)
|
|
35
|
+
else:
|
|
36
|
+
account = dao.save_one({
|
|
37
|
+
"username": username,
|
|
38
|
+
"email": email,
|
|
39
|
+
"password": make_password(password),
|
|
40
|
+
"is_admin": username == 'admin'
|
|
41
|
+
})
|
|
42
|
+
return ValarResponse(jwt.encode({
|
|
43
|
+
"user_id": account.id,
|
|
44
|
+
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
|
|
45
|
+
}, settings.SECRET_KEY, algorithm="HS256"))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def free_menus(request):
|
|
49
|
+
body = json.loads(request.body)
|
|
50
|
+
scope = body.get("scope", 'admin')
|
|
51
|
+
menus = Menu.objects.filter(isLeaf=True, scope=scope, is_admin=False, path__isnull=False,
|
|
52
|
+
roles__isnull=True).values('path')
|
|
53
|
+
payload = {
|
|
54
|
+
"permissions": [m['path'] for m in menus]
|
|
55
|
+
}
|
|
56
|
+
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
|
|
57
|
+
return ValarResponse(token)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@auth_required
|
|
61
|
+
def user_profile(request):
|
|
62
|
+
body = json.loads(request.body)
|
|
63
|
+
scope = body.get("scope", 'admin')
|
|
64
|
+
account_id = request.user_id
|
|
65
|
+
account = Account.objects.filter(id=account_id).first()
|
|
66
|
+
if account is None:
|
|
67
|
+
return ValarResponse(False, f"账户已不存在", 'warning', status=401)
|
|
68
|
+
user_admin = account.is_admin
|
|
69
|
+
user_roles = list(account.roles.values('id', 'name'))
|
|
70
|
+
user_role_keys = [r['id'] for r in user_roles]
|
|
71
|
+
permissions = []
|
|
72
|
+
for m in Menu.objects.filter(isLeaf=True, scope=scope):
|
|
73
|
+
_id = m.path
|
|
74
|
+
if _id is None:
|
|
75
|
+
continue
|
|
76
|
+
if user_admin:
|
|
77
|
+
permissions.append(_id)
|
|
78
|
+
else:
|
|
79
|
+
if not m.is_admin:
|
|
80
|
+
menu_role_keys = [r['id'] for r in m.roles.all().values('id')]
|
|
81
|
+
if len(menu_role_keys):
|
|
82
|
+
if bool(set(user_role_keys) & set(menu_role_keys)):
|
|
83
|
+
permissions.append(_id)
|
|
84
|
+
else:
|
|
85
|
+
permissions.append(_id)
|
|
86
|
+
payload = {
|
|
87
|
+
"username": account.username,
|
|
88
|
+
"roles": user_roles,
|
|
89
|
+
"is_admin": user_admin,
|
|
90
|
+
"email": account.email,
|
|
91
|
+
"is_active": account.is_active,
|
|
92
|
+
"temporary": account.token is not None,
|
|
93
|
+
"permissions": permissions
|
|
94
|
+
}
|
|
95
|
+
print(permissions)
|
|
96
|
+
field = (
|
|
97
|
+
next((field for field in account.get_meta().get_fields()
|
|
98
|
+
if type(field) == OneToOneRel
|
|
99
|
+
and issubclass(field.related_model, AbstractUser)), None))
|
|
100
|
+
if field:
|
|
101
|
+
accessor = field.get_accessor_name()
|
|
102
|
+
model = field.related_model
|
|
103
|
+
entity = f'{model._meta.app_label}.{model.__name__}'
|
|
104
|
+
payload.update({'user_entity': entity, 'user_accessor': accessor})
|
|
105
|
+
if hasattr(account, accessor):
|
|
106
|
+
user = getattr(account, accessor)
|
|
107
|
+
name = getattr(user, 'name')
|
|
108
|
+
payload.update({'name': name})
|
|
109
|
+
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
|
|
110
|
+
return ValarResponse(token)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import secrets
|
|
3
|
+
from django.contrib.auth.hashers import check_password, make_password
|
|
4
|
+
|
|
5
|
+
from ..auth.Authentication import auth_required
|
|
6
|
+
from ..classes.valar_response import ValarResponse
|
|
7
|
+
|
|
8
|
+
from ..dao.engine import ValarEngine
|
|
9
|
+
from ..models.auth import Account, AbstractUser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def send_password(request):
|
|
13
|
+
body = json.loads(request.body)
|
|
14
|
+
account_id = body.get('account_id')
|
|
15
|
+
account = Account.objects.filter(id=account_id).first()
|
|
16
|
+
return __send__(account)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def retrieve_password(request):
|
|
20
|
+
includes = json.loads(request.body)
|
|
21
|
+
account = Account.objects.filter(**includes).first()
|
|
22
|
+
account.token = account.token or secrets.token_hex(16)
|
|
23
|
+
account.save()
|
|
24
|
+
if account:
|
|
25
|
+
return __send__(account)
|
|
26
|
+
else:
|
|
27
|
+
return ValarResponse(False, '该邮箱未在系统中注册', code='error')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@auth_required
|
|
31
|
+
def change_password(request):
|
|
32
|
+
body = json.loads(request.body)
|
|
33
|
+
old_password = body.get('old_password')
|
|
34
|
+
new_password = body.get('new_password')
|
|
35
|
+
account_id = request.user_id
|
|
36
|
+
account = Account.objects.get(id=account_id)
|
|
37
|
+
if not account:
|
|
38
|
+
return ValarResponse(False, '账户不存在', code='error')
|
|
39
|
+
if not account.is_auth(old_password):
|
|
40
|
+
return ValarResponse(False, '当前密码错误', code='error')
|
|
41
|
+
if old_password == new_password:
|
|
42
|
+
return ValarResponse(False, '新密码不能与当前密码一致', code='error')
|
|
43
|
+
account.password = make_password(new_password)
|
|
44
|
+
account.token = None
|
|
45
|
+
account.save()
|
|
46
|
+
return ValarResponse(True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __send__(account):
|
|
50
|
+
if not account:
|
|
51
|
+
return ValarResponse(False, '账户不存在', code='error')
|
|
52
|
+
token = account.token
|
|
53
|
+
if not token:
|
|
54
|
+
return ValarResponse(False, '未生成临时密码', code='error')
|
|
55
|
+
email = account.email
|
|
56
|
+
if not email:
|
|
57
|
+
return ValarResponse(False, '该账户未登记邮箱信息', code='error')
|
|
58
|
+
content = f'Your temporary password is {token}, please change it as soon as possible.'
|
|
59
|
+
ValarEngine().send_email('Retrieve Password', content, email)
|
|
60
|
+
return ValarResponse(True, '临时密码发送成功', 'success')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: valar
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: valar for morghulis
|
|
5
5
|
Home-page: https://gitee.com/GRIFFIN120/valar_dev
|
|
6
6
|
Author: LYP
|
|
@@ -16,6 +16,7 @@ Requires-Dist: pandas==2.2.3
|
|
|
16
16
|
Requires-Dist: openpyxl==3.1.5
|
|
17
17
|
Requires-Dist: deepmerge~=2.0
|
|
18
18
|
Requires-Dist: minio==7.2.2
|
|
19
|
+
Requires-Dist: PyJWT~=2.10.1
|
|
19
20
|
Dynamic: author
|
|
20
21
|
Dynamic: author-email
|
|
21
22
|
Dynamic: description
|
|
@@ -30,11 +31,20 @@ valar for morghulis
|
|
|
30
31
|
|
|
31
32
|
# 1. installation
|
|
32
33
|
|
|
34
|
+
- you need to install valar in a django project
|
|
35
|
+
|
|
33
36
|
```shell
|
|
34
37
|
pip install valar
|
|
35
38
|
```
|
|
36
39
|
|
|
37
|
-
#
|
|
40
|
+
# 2. root app
|
|
41
|
+
|
|
42
|
+
- you only need 3 files in your root app
|
|
43
|
+
- settings.py
|
|
44
|
+
- asgi.py
|
|
45
|
+
- urls.py
|
|
46
|
+
|
|
47
|
+
## 2.1 settings.py
|
|
38
48
|
|
|
39
49
|
```python
|
|
40
50
|
from pathlib import Path
|
|
@@ -99,7 +109,7 @@ MINIO_ROOT = "https://host:9001"
|
|
|
99
109
|
|
|
100
110
|
```
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
## 2.2 asgi.py
|
|
103
113
|
|
|
104
114
|
```python
|
|
105
115
|
from django.core.asgi import get_asgi_application
|
|
@@ -118,18 +128,19 @@ application = ProtocolTypeRouter({
|
|
|
118
128
|
|
|
119
129
|
```
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
## 2.3 urls.py
|
|
122
132
|
|
|
123
|
-
- no need to
|
|
133
|
+
- no need to provide urls for Valar, Valar will auto set urlpatterns for Morghulis (
|
|
134
|
+
see https://www.npmjs.com/package/morghulis)
|
|
135
|
+
- go to section 4 to see how to register channel handlers (a Vue - Django async communication tool) in urls.
|
|
124
136
|
|
|
125
|
-
#
|
|
137
|
+
# 3. migrate
|
|
126
138
|
|
|
127
|
-
no need to
|
|
128
|
-
see https://www.npmjs.com/package/morghulis)
|
|
139
|
+
- no need to makemigrations and migrate for valar, valar will auto migration
|
|
129
140
|
|
|
130
|
-
#
|
|
141
|
+
# 4. how to register a channel handler for Morghulis async methods
|
|
131
142
|
|
|
132
|
-
##
|
|
143
|
+
## 4.1 create a handler
|
|
133
144
|
|
|
134
145
|
```python
|
|
135
146
|
import time
|
|
@@ -148,7 +159,7 @@ def valar_test_handler(sender: ValarChannelSender):
|
|
|
148
159
|
sender.load(tick)
|
|
149
160
|
```
|
|
150
161
|
|
|
151
|
-
###
|
|
162
|
+
### 4.2 create a dict (e.g. using the name 'channel_mapping') to save your handler
|
|
152
163
|
|
|
153
164
|
- I'd like to put it in the root urls.py, you can put it anywhere
|
|
154
165
|
|
|
@@ -158,13 +169,13 @@ channel_mapping = {
|
|
|
158
169
|
}
|
|
159
170
|
```
|
|
160
171
|
|
|
161
|
-
##
|
|
172
|
+
## 4.3 register the channel_mapping in the settings.py
|
|
162
173
|
|
|
163
174
|
```python
|
|
164
175
|
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
165
176
|
```
|
|
166
177
|
|
|
167
|
-
###
|
|
178
|
+
### 4.4 you can copy the following codes to your urls.py
|
|
168
179
|
|
|
169
180
|
```python
|
|
170
181
|
import json
|
|
@@ -9,6 +9,9 @@ src/valar.egg-info/SOURCES.txt
|
|
|
9
9
|
src/valar.egg-info/dependency_links.txt
|
|
10
10
|
src/valar.egg-info/requires.txt
|
|
11
11
|
src/valar.egg-info/top_level.txt
|
|
12
|
+
src/valar/auth/Authentication.py
|
|
13
|
+
src/valar/auth/Middleware.py
|
|
14
|
+
src/valar/auth/__init__.py
|
|
12
15
|
src/valar/channels/__init__.py
|
|
13
16
|
src/valar/channels/consumer.py
|
|
14
17
|
src/valar/channels/counter.py
|
|
@@ -39,12 +42,15 @@ src/valar/dao/defaults/field_values_default.py
|
|
|
39
42
|
src/valar/dao/defaults/view_defaults.py
|
|
40
43
|
src/valar/migrations/__init__.py
|
|
41
44
|
src/valar/models/__init__.py
|
|
45
|
+
src/valar/models/auth.py
|
|
42
46
|
src/valar/models/core.py
|
|
43
47
|
src/valar/models/frame.py
|
|
44
48
|
src/valar/models/meta.py
|
|
45
49
|
src/valar/models/test.py
|
|
46
50
|
src/valar/views/__init__.py
|
|
51
|
+
src/valar/views/auth.py
|
|
47
52
|
src/valar/views/file.py
|
|
48
53
|
src/valar/views/handler.py
|
|
49
54
|
src/valar/views/meta.py
|
|
55
|
+
src/valar/views/password.py
|
|
50
56
|
src/valar/views/rest.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from . import core, frame, meta, test
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|