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.

Files changed (60) hide show
  1. {valar-1.2.4/src/valar.egg-info → valar-1.3.0}/PKG-INFO +24 -13
  2. {valar-1.2.4 → valar-1.3.0}/README.md +22 -12
  3. {valar-1.2.4 → valar-1.3.0}/setup.py +2 -2
  4. {valar-1.2.4 → valar-1.3.0}/src/valar/apps.py +10 -4
  5. valar-1.3.0/src/valar/auth/Authentication.py +32 -0
  6. valar-1.3.0/src/valar/auth/Middleware.py +28 -0
  7. {valar-1.2.4 → valar-1.3.0}/src/valar/channels/sender.py +4 -1
  8. valar-1.3.0/src/valar/classes/valar_response.py +9 -0
  9. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/abstract.py +6 -3
  10. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/defaults/field_keys_default.py +10 -1
  11. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/engine.py +9 -0
  12. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/orm_field.py +4 -6
  13. valar-1.3.0/src/valar/models/__init__.py +1 -0
  14. valar-1.3.0/src/valar/models/auth.py +47 -0
  15. {valar-1.2.4 → valar-1.3.0}/src/valar/urls.py +10 -4
  16. valar-1.3.0/src/valar/views/__init__.py +0 -0
  17. valar-1.3.0/src/valar/views/auth.py +110 -0
  18. valar-1.3.0/src/valar/views/password.py +60 -0
  19. {valar-1.2.4 → valar-1.3.0/src/valar.egg-info}/PKG-INFO +24 -13
  20. {valar-1.2.4 → valar-1.3.0}/src/valar.egg-info/SOURCES.txt +6 -0
  21. {valar-1.2.4 → valar-1.3.0}/src/valar.egg-info/requires.txt +1 -0
  22. valar-1.2.4/src/valar/classes/valar_response.py +0 -8
  23. valar-1.2.4/src/valar/models/__init__.py +0 -1
  24. {valar-1.2.4 → valar-1.3.0}/LICENSE +0 -0
  25. {valar-1.2.4 → valar-1.3.0}/setup.cfg +0 -0
  26. {valar-1.2.4 → valar-1.3.0}/src/valar/__init__.py +0 -0
  27. {valar-1.2.4/src/valar/channels → valar-1.3.0/src/valar/auth}/__init__.py +0 -0
  28. {valar-1.2.4/src/valar/classes → valar-1.3.0/src/valar/channels}/__init__.py +0 -0
  29. {valar-1.2.4 → valar-1.3.0}/src/valar/channels/consumer.py +0 -0
  30. {valar-1.2.4 → valar-1.3.0}/src/valar/channels/counter.py +0 -0
  31. {valar-1.2.4 → valar-1.3.0}/src/valar/channels/executer.py +0 -0
  32. {valar-1.2.4 → valar-1.3.0}/src/valar/channels/mapping.py +0 -0
  33. {valar-1.2.4 → valar-1.3.0}/src/valar/channels/views.py +0 -0
  34. {valar-1.2.4/src/valar/classes/app_mixins → valar-1.3.0/src/valar/classes}/__init__.py +0 -0
  35. {valar-1.2.4/src/valar/dao/defaults → valar-1.3.0/src/valar/classes/app_mixins}/__init__.py +0 -0
  36. {valar-1.2.4 → valar-1.3.0}/src/valar/classes/app_mixins/auto_migration_mixin.py +0 -0
  37. {valar-1.2.4 → valar-1.3.0}/src/valar/classes/app_mixins/auto_urlpatterns_mixin.py +0 -0
  38. {valar-1.2.4 → valar-1.3.0}/src/valar/classes/singleton_meta.py +0 -0
  39. {valar-1.2.4 → valar-1.3.0}/src/valar/classes/valar_minio.py +0 -0
  40. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/__init__.py +0 -0
  41. {valar-1.2.4/src/valar/migrations → valar-1.3.0/src/valar/dao/defaults}/__init__.py +0 -0
  42. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/defaults/field_values_default.py +0 -0
  43. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/defaults/view_defaults.py +0 -0
  44. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/frame.py +0 -0
  45. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/meta.py +0 -0
  46. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/mon_dao.py +0 -0
  47. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/mon_field.py +0 -0
  48. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/orm_dao.py +0 -0
  49. {valar-1.2.4 → valar-1.3.0}/src/valar/dao/query.py +0 -0
  50. {valar-1.2.4/src/valar/views → valar-1.3.0/src/valar/migrations}/__init__.py +0 -0
  51. {valar-1.2.4 → valar-1.3.0}/src/valar/models/core.py +0 -0
  52. {valar-1.2.4 → valar-1.3.0}/src/valar/models/frame.py +0 -0
  53. {valar-1.2.4 → valar-1.3.0}/src/valar/models/meta.py +0 -0
  54. {valar-1.2.4 → valar-1.3.0}/src/valar/models/test.py +0 -0
  55. {valar-1.2.4 → valar-1.3.0}/src/valar/views/file.py +0 -0
  56. {valar-1.2.4 → valar-1.3.0}/src/valar/views/handler.py +0 -0
  57. {valar-1.2.4 → valar-1.3.0}/src/valar/views/meta.py +0 -0
  58. {valar-1.2.4 → valar-1.3.0}/src/valar/views/rest.py +0 -0
  59. {valar-1.2.4 → valar-1.3.0}/src/valar.egg-info/dependency_links.txt +0 -0
  60. {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.2.4
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
- # 1. settings
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
- # 2. asgi
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
- # 3. migrate
131
+ ## 2.3 urls.py
122
132
 
123
- - no need to makemigrations and migrate for valar, valar will auto migration
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
- # 4. root urls
137
+ # 3. migrate
126
138
 
127
- no need to provide urls for val, valar will auto set urlpatterns for Morghulis (
128
- see https://www.npmjs.com/package/morghulis)
139
+ - no need to makemigrations and migrate for valar, valar will auto migration
129
140
 
130
- # 5. how to register a channel handler for Morghulis async methods
141
+ # 4. how to register a channel handler for Morghulis async methods
131
142
 
132
- ## 5.1 create a handler
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
- ### 5.2 create a dict (e.g. using the name 'channel_mapping') to save your handler
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
- ## 5.2 register the channel_mapping in the settings.py
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
- ### 5.4 you can copy the following codes to your urls.py
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
- # 1. settings
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
- # 2. asgi
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
- # 3. migrate
102
+ ## 2.3 urls.py
94
103
 
95
- - no need to makemigrations and migrate for valar, valar will auto migration
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
- # 4. root urls
108
+ # 3. migrate
98
109
 
99
- no need to provide urls for val, valar will auto set urlpatterns for Morghulis (
100
- see https://www.npmjs.com/package/morghulis)
110
+ - no need to makemigrations and migrate for valar, valar will auto migration
101
111
 
102
- # 5. how to register a channel handler for Morghulis async methods
112
+ # 4. how to register a channel handler for Morghulis async methods
103
113
 
104
- ## 5.1 create a handler
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
- ### 5.2 create a dict (e.g. using the name 'channel_mapping') to save your handler
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
- ## 5.2 register the channel_mapping in the settings.py
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
- ### 5.4 you can copy the following codes to your urls.py
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.2.4", # 版本号
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
- from .dao.frame import MetaFrame
15
- getattr(super(), 'set_url', None)()
16
- getattr(super(), 'auto_migrate', None)()
17
- MetaFrame()
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
- raise Exception('Unauthorized!')
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.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', 'FileField', 'JSONField']:
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', 'ManyToManyRel', 'ManyToOneRel']:
139
- return 80
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.2.4
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
- # 1. settings
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
- # 2. asgi
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
- # 3. migrate
131
+ ## 2.3 urls.py
122
132
 
123
- - no need to makemigrations and migrate for valar, valar will auto migration
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
- # 4. root urls
137
+ # 3. migrate
126
138
 
127
- no need to provide urls for val, valar will auto set urlpatterns for Morghulis (
128
- see https://www.npmjs.com/package/morghulis)
139
+ - no need to makemigrations and migrate for valar, valar will auto migration
129
140
 
130
- # 5. how to register a channel handler for Morghulis async methods
141
+ # 4. how to register a channel handler for Morghulis async methods
131
142
 
132
- ## 5.1 create a handler
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
- ### 5.2 create a dict (e.g. using the name 'channel_mapping') to save your handler
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
- ## 5.2 register the channel_mapping in the settings.py
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
- ### 5.4 you can copy the following codes to your urls.py
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
@@ -6,3 +6,4 @@ pandas==2.2.3
6
6
  openpyxl==3.1.5
7
7
  deepmerge~=2.0
8
8
  minio==7.2.2
9
+ PyJWT~=2.10.1
@@ -1,8 +0,0 @@
1
- from django.http import JsonResponse
2
-
3
-
4
- class ValarResponse(JsonResponse):
5
- def __init__(self, data=True, message='', code='info'):
6
- self.message = message
7
- self.code = code
8
- super(ValarResponse, self).__init__(data, safe=False)
@@ -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