valar 1.1.2__tar.gz → 1.1.4__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.1.2/src/valar.egg-info → valar-1.1.4}/PKG-INFO +50 -57
- {valar-1.1.2 → valar-1.1.4}/README.md +49 -56
- {valar-1.1.2 → valar-1.1.4}/setup.py +2 -2
- valar-1.1.4/src/valar/__init__.py +0 -0
- valar-1.1.4/src/valar/apps.py +21 -0
- valar-1.1.4/src/valar/channels/__init__.py +0 -0
- valar-1.1.4/src/valar/channels/consumer.py +48 -0
- valar-1.1.4/src/valar/channels/executer.py +13 -0
- valar-1.1.4/src/valar/channels/mapping.py +23 -0
- valar-1.1.4/src/valar/channels/sender.py +64 -0
- valar-1.1.4/src/valar/channels/views.py +12 -0
- valar-1.1.4/src/valar/classes/__init__.py +0 -0
- valar-1.1.4/src/valar/classes/auto_migration_mixin.py +8 -0
- valar-1.1.4/src/valar/classes/auto_urlpatterns_mixin.py +15 -0
- valar-1.1.4/src/valar/classes/counter.py +9 -0
- valar-1.1.4/src/valar/classes/singleton_meta.py +6 -0
- valar-1.1.4/src/valar/classes/valar_response.py +7 -0
- valar-1.1.4/src/valar/frame/__init__.py +0 -0
- valar-1.1.4/src/valar/migrations/0001_initial.py +141 -0
- valar-1.1.4/src/valar/migrations/__init__.py +0 -0
- valar-1.1.4/src/valar/models/__init__.py +1 -0
- valar-1.1.4/src/valar/models/core.py +82 -0
- valar-1.1.4/src/valar/models/frame.py +30 -0
- valar-1.1.4/src/valar/models/meta.py +87 -0
- valar-1.1.4/src/valar/urls.py +7 -0
- valar-1.1.4/src/valar/views/__init__.py +0 -0
- {valar-1.1.2 → valar-1.1.4/src/valar.egg-info}/PKG-INFO +50 -57
- valar-1.1.4/src/valar.egg-info/SOURCES.txt +31 -0
- valar-1.1.4/src/valar.egg-info/top_level.txt +1 -0
- valar-1.1.2/src/valar.egg-info/SOURCES.txt +0 -8
- valar-1.1.2/src/valar.egg-info/top_level.txt +0 -1
- {valar-1.1.2 → valar-1.1.4}/LICENSE +0 -0
- {valar-1.1.2 → valar-1.1.4}/setup.cfg +0 -0
- {valar-1.1.2 → valar-1.1.4}/src/valar.egg-info/dependency_links.txt +0 -0
- {valar-1.1.2 → valar-1.1.4}/src/valar.egg-info/requires.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: valar
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.4
|
|
4
4
|
Summary: valar for morghulis
|
|
5
5
|
Home-page: https://gitee.com/GRIFFIN120/valar_dev
|
|
6
6
|
Author: LYP
|
|
@@ -36,16 +36,28 @@ pip install valar
|
|
|
36
36
|
# 1. settings
|
|
37
37
|
|
|
38
38
|
```python
|
|
39
|
-
|
|
39
|
+
from django.core.management.utils import get_random_secret_key
|
|
40
40
|
from pathlib import Path
|
|
41
41
|
|
|
42
|
-
"""
|
|
42
|
+
""" Compulsory settings """
|
|
43
|
+
|
|
43
44
|
DEBUG = True
|
|
44
45
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
45
46
|
BASE_APP = str(BASE_DIR.name)
|
|
46
47
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
47
|
-
SECRET_KEY =
|
|
48
|
-
|
|
48
|
+
SECRET_KEY = get_random_secret_key()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
""" Valar Options """
|
|
53
|
+
|
|
54
|
+
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
55
|
+
MONGO_URI = 'mongodb://root:19870120@121.41.111.175:27017/'
|
|
56
|
+
MINIO_URL = "s3://admin:password@120.27.8.186:9000"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
""" Minimized compulsory settings """
|
|
49
61
|
|
|
50
62
|
DATABASES = {
|
|
51
63
|
'default': {
|
|
@@ -53,55 +65,38 @@ DATABASES = {
|
|
|
53
65
|
'NAME': BASE_DIR / 'db.sqlite3',
|
|
54
66
|
}
|
|
55
67
|
}
|
|
56
|
-
|
|
57
68
|
INSTALLED_APPS = [
|
|
58
69
|
'django.contrib.sessions',
|
|
59
70
|
"corsheaders",
|
|
60
71
|
'channels',
|
|
61
|
-
'
|
|
72
|
+
'valar',
|
|
62
73
|
]
|
|
63
|
-
|
|
64
74
|
MIDDLEWARE = [
|
|
65
75
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
66
76
|
'corsheaders.middleware.CorsMiddleware',
|
|
67
77
|
'django.middleware.common.CommonMiddleware',
|
|
68
78
|
]
|
|
69
|
-
|
|
70
79
|
CHANNEL_LAYERS = {
|
|
71
80
|
"default": {
|
|
72
81
|
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
|
-
|
|
76
84
|
CORS_ORIGIN_ALLOW_ALL = True
|
|
77
85
|
CORS_ALLOW_CREDENTIALS = True
|
|
78
86
|
ROOT_URLCONF = "%s.urls" % BASE_APP
|
|
79
87
|
ASGI_APPLICATION = "%s.asgi.application" % BASE_APP
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
'host': '<IP>',
|
|
83
|
-
'port': '<PORT>',
|
|
84
|
-
"username": "<USERNAME>",
|
|
85
|
-
"password": '<PASSWORD>'
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
MINIO = {
|
|
89
|
-
'endpoint': '<IP>:<PORT>',
|
|
90
|
-
'access_key': '<USERNAME>',
|
|
91
|
-
"secret_key": "<PASSWORD>",
|
|
92
|
-
'secure': False
|
|
93
|
-
}
|
|
89
|
+
""" Optional settings """
|
|
94
90
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
91
|
+
ALLOWED_HOSTS = ['*']
|
|
92
|
+
LANGUAGE_CODE = 'en-us'
|
|
93
|
+
TIME_ZONE = 'Asia/Shanghai'
|
|
94
|
+
USE_I18N = True
|
|
95
|
+
USE_TZ = False
|
|
96
|
+
SESSION_SAVE_EVERY_REQUEST = True
|
|
97
|
+
SESSION_COOKIE_AGE = 60 * 60
|
|
98
|
+
FILE_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
99
|
+
DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
105
100
|
|
|
106
101
|
```
|
|
107
102
|
# 2. asgi
|
|
@@ -110,7 +105,7 @@ MINIO = {
|
|
|
110
105
|
from django.core.asgi import get_asgi_application
|
|
111
106
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
112
107
|
from django.urls import re_path
|
|
113
|
-
from
|
|
108
|
+
from valar.channels.consumer import ValarConsumer
|
|
114
109
|
|
|
115
110
|
application = ProtocolTypeRouter({
|
|
116
111
|
'http': get_asgi_application(),
|
|
@@ -122,41 +117,39 @@ application = ProtocolTypeRouter({
|
|
|
122
117
|
```
|
|
123
118
|
|
|
124
119
|
# 3. migrate
|
|
125
|
-
|
|
126
|
-
python manage.py makemigrations
|
|
127
|
-
python manage.py migrate
|
|
128
|
-
```
|
|
120
|
+
no need, valar will auto migration
|
|
129
121
|
|
|
130
122
|
|
|
131
123
|
# 4. root urls
|
|
132
124
|
|
|
133
|
-
|
|
134
|
-
from django.urls import path, include
|
|
125
|
+
no need, valar will auto set urlpatterns
|
|
135
126
|
|
|
136
|
-
|
|
137
|
-
path('data/', include('src.valar.data.urls')),
|
|
138
|
-
]
|
|
127
|
+
# 5. how to use valar_channel
|
|
139
128
|
|
|
140
|
-
channel_mapping = {
|
|
141
|
-
# 'test': test_handler
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
129
|
|
|
145
|
-
|
|
130
|
+
5.1 set HANDLER_MAPPING in settings
|
|
146
131
|
|
|
147
132
|
```python
|
|
133
|
+
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
134
|
+
```
|
|
148
135
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
from src.valar.channels.sender import ValarSocketSender
|
|
152
|
-
from src.valar import Counter
|
|
153
|
-
|
|
136
|
+
5.2 create a handler
|
|
154
137
|
|
|
138
|
+
```python
|
|
139
|
+
from valar.channels.sender import ValarSocketSender
|
|
155
140
|
def test_handler(sender: ValarSocketSender):
|
|
156
141
|
data = sender.data
|
|
157
|
-
length = data.get('length',
|
|
158
|
-
counter =
|
|
142
|
+
length = data.get('length',30)
|
|
143
|
+
counter = sender.create_counter(length)
|
|
159
144
|
for i in range(length):
|
|
160
|
-
|
|
161
|
-
sender.to_clients(counter.tick(),
|
|
145
|
+
# sync method
|
|
146
|
+
sender.to_clients(counter.tick() ,sender.client, wait=True)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
5.3 register handler in channel_mapping
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
channel_mapping = {
|
|
153
|
+
'test': test_handler,
|
|
154
|
+
}
|
|
162
155
|
```
|
|
@@ -8,16 +8,28 @@ pip install valar
|
|
|
8
8
|
# 1. settings
|
|
9
9
|
|
|
10
10
|
```python
|
|
11
|
-
|
|
11
|
+
from django.core.management.utils import get_random_secret_key
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
"""
|
|
14
|
+
""" Compulsory settings """
|
|
15
|
+
|
|
15
16
|
DEBUG = True
|
|
16
17
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
17
18
|
BASE_APP = str(BASE_DIR.name)
|
|
18
19
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
19
|
-
SECRET_KEY =
|
|
20
|
-
|
|
20
|
+
SECRET_KEY = get_random_secret_key()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
""" Valar Options """
|
|
25
|
+
|
|
26
|
+
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
27
|
+
MONGO_URI = 'mongodb://root:19870120@121.41.111.175:27017/'
|
|
28
|
+
MINIO_URL = "s3://admin:password@120.27.8.186:9000"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
""" Minimized compulsory settings """
|
|
21
33
|
|
|
22
34
|
DATABASES = {
|
|
23
35
|
'default': {
|
|
@@ -25,55 +37,38 @@ DATABASES = {
|
|
|
25
37
|
'NAME': BASE_DIR / 'db.sqlite3',
|
|
26
38
|
}
|
|
27
39
|
}
|
|
28
|
-
|
|
29
40
|
INSTALLED_APPS = [
|
|
30
41
|
'django.contrib.sessions',
|
|
31
42
|
"corsheaders",
|
|
32
43
|
'channels',
|
|
33
|
-
'
|
|
44
|
+
'valar',
|
|
34
45
|
]
|
|
35
|
-
|
|
36
46
|
MIDDLEWARE = [
|
|
37
47
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
38
48
|
'corsheaders.middleware.CorsMiddleware',
|
|
39
49
|
'django.middleware.common.CommonMiddleware',
|
|
40
50
|
]
|
|
41
|
-
|
|
42
51
|
CHANNEL_LAYERS = {
|
|
43
52
|
"default": {
|
|
44
53
|
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
|
-
|
|
48
56
|
CORS_ORIGIN_ALLOW_ALL = True
|
|
49
57
|
CORS_ALLOW_CREDENTIALS = True
|
|
50
58
|
ROOT_URLCONF = "%s.urls" % BASE_APP
|
|
51
59
|
ASGI_APPLICATION = "%s.asgi.application" % BASE_APP
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
'host': '<IP>',
|
|
55
|
-
'port': '<PORT>',
|
|
56
|
-
"username": "<USERNAME>",
|
|
57
|
-
"password": '<PASSWORD>'
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
MINIO = {
|
|
61
|
-
'endpoint': '<IP>:<PORT>',
|
|
62
|
-
'access_key': '<USERNAME>',
|
|
63
|
-
"secret_key": "<PASSWORD>",
|
|
64
|
-
'secure': False
|
|
65
|
-
}
|
|
61
|
+
""" Optional settings """
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
63
|
+
ALLOWED_HOSTS = ['*']
|
|
64
|
+
LANGUAGE_CODE = 'en-us'
|
|
65
|
+
TIME_ZONE = 'Asia/Shanghai'
|
|
66
|
+
USE_I18N = True
|
|
67
|
+
USE_TZ = False
|
|
68
|
+
SESSION_SAVE_EVERY_REQUEST = True
|
|
69
|
+
SESSION_COOKIE_AGE = 60 * 60
|
|
70
|
+
FILE_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
71
|
+
DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
77
72
|
|
|
78
73
|
```
|
|
79
74
|
# 2. asgi
|
|
@@ -82,7 +77,7 @@ MINIO = {
|
|
|
82
77
|
from django.core.asgi import get_asgi_application
|
|
83
78
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
84
79
|
from django.urls import re_path
|
|
85
|
-
from
|
|
80
|
+
from valar.channels.consumer import ValarConsumer
|
|
86
81
|
|
|
87
82
|
application = ProtocolTypeRouter({
|
|
88
83
|
'http': get_asgi_application(),
|
|
@@ -94,41 +89,39 @@ application = ProtocolTypeRouter({
|
|
|
94
89
|
```
|
|
95
90
|
|
|
96
91
|
# 3. migrate
|
|
97
|
-
|
|
98
|
-
python manage.py makemigrations
|
|
99
|
-
python manage.py migrate
|
|
100
|
-
```
|
|
92
|
+
no need, valar will auto migration
|
|
101
93
|
|
|
102
94
|
|
|
103
95
|
# 4. root urls
|
|
104
96
|
|
|
105
|
-
|
|
106
|
-
from django.urls import path, include
|
|
97
|
+
no need, valar will auto set urlpatterns
|
|
107
98
|
|
|
108
|
-
|
|
109
|
-
path('data/', include('src.valar.data.urls')),
|
|
110
|
-
]
|
|
99
|
+
# 5. how to use valar_channel
|
|
111
100
|
|
|
112
|
-
channel_mapping = {
|
|
113
|
-
# 'test': test_handler
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
101
|
|
|
117
|
-
|
|
102
|
+
5.1 set HANDLER_MAPPING in settings
|
|
118
103
|
|
|
119
104
|
```python
|
|
105
|
+
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
106
|
+
```
|
|
120
107
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
from src.valar.channels.sender import ValarSocketSender
|
|
124
|
-
from src.valar import Counter
|
|
125
|
-
|
|
108
|
+
5.2 create a handler
|
|
126
109
|
|
|
110
|
+
```python
|
|
111
|
+
from valar.channels.sender import ValarSocketSender
|
|
127
112
|
def test_handler(sender: ValarSocketSender):
|
|
128
113
|
data = sender.data
|
|
129
|
-
length = data.get('length',
|
|
130
|
-
counter =
|
|
114
|
+
length = data.get('length',30)
|
|
115
|
+
counter = sender.create_counter(length)
|
|
131
116
|
for i in range(length):
|
|
132
|
-
|
|
133
|
-
sender.to_clients(counter.tick(),
|
|
117
|
+
# sync method
|
|
118
|
+
sender.to_clients(counter.tick() ,sender.client, wait=True)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
5.3 register handler in channel_mapping
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
channel_mapping = {
|
|
125
|
+
'test': test_handler,
|
|
126
|
+
}
|
|
134
127
|
```
|
|
@@ -16,14 +16,14 @@ requires = [
|
|
|
16
16
|
|
|
17
17
|
setup(
|
|
18
18
|
name="valar", # 包名
|
|
19
|
-
version="1.1.
|
|
19
|
+
version="1.1.4", # 版本号
|
|
20
20
|
author="LYP", # 作者
|
|
21
21
|
author_email="liuyinpeng@buaa.edu.cn", # 邮箱
|
|
22
22
|
description="valar for morghulis", # 简短描述
|
|
23
23
|
long_description=long_description, # 详细说明
|
|
24
24
|
long_description_content_type="text/markdown", # 详细说明使用标记类型
|
|
25
25
|
url="https://gitee.com/GRIFFIN120/valar_dev", # 项目主页
|
|
26
|
-
packages=find_packages(where="
|
|
26
|
+
packages=find_packages(where="src"), # 需要打包的部分
|
|
27
27
|
package_dir={"": "src"}, # 设置src目录为根目录
|
|
28
28
|
python_requires=">=3.9", # 项目支持的Python版本
|
|
29
29
|
install_requires=requires, # 项目必须的依赖
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from django.apps import AppConfig
|
|
3
|
+
|
|
4
|
+
from .classes.auto_migration_mixin import AutoMigrationMixin
|
|
5
|
+
from .classes.auto_urlpatterns_mixin import AutoUrlPatternsMixin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ValarConfig(AutoMigrationMixin,AutoUrlPatternsMixin,AppConfig):
|
|
9
|
+
default_auto_field = 'django.db.models.BigAutoField'
|
|
10
|
+
name = __package__
|
|
11
|
+
|
|
12
|
+
def ready(self):
|
|
13
|
+
if os.environ.get('RUN_MAIN') == 'true':
|
|
14
|
+
getattr(super(), 'set_url', None)()
|
|
15
|
+
getattr(super(), 'auto_migrate', None)()
|
|
16
|
+
print(__package__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
|
3
|
+
|
|
4
|
+
VALAR_CHANNEL_GROUP = 'VALAR'
|
|
5
|
+
|
|
6
|
+
class ValarConsumer(AsyncJsonWebsocketConsumer):
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.client = None
|
|
10
|
+
self.uid = None
|
|
11
|
+
super().__init__()
|
|
12
|
+
|
|
13
|
+
async def connect(self):
|
|
14
|
+
params = self.scope['url_route']['kwargs']
|
|
15
|
+
self.client = params.get('client')
|
|
16
|
+
await self.channel_layer.group_add(VALAR_CHANNEL_GROUP, self.channel_name)
|
|
17
|
+
await self.accept()
|
|
18
|
+
|
|
19
|
+
async def disconnect(self, code):
|
|
20
|
+
await self.channel_layer.group_discard(VALAR_CHANNEL_GROUP, self.channel_name)
|
|
21
|
+
await self.close(code)
|
|
22
|
+
|
|
23
|
+
async def receive_json(self, data, *args, **kwargs):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
async def user_emit(self, event):
|
|
27
|
+
users: list = event.get('users',[])
|
|
28
|
+
data = event.get('data',{})
|
|
29
|
+
if self.uid in users:
|
|
30
|
+
await self.send_json(data)
|
|
31
|
+
|
|
32
|
+
async def client_emit(self, event):
|
|
33
|
+
clients: list = event.get('clients',[])
|
|
34
|
+
data = event.get('data',{})
|
|
35
|
+
if self.client in clients:
|
|
36
|
+
await self.send_json(data)
|
|
37
|
+
|
|
38
|
+
async def broadcast_emit(self, event):
|
|
39
|
+
data = event.get('data',{})
|
|
40
|
+
await self.send_json(data)
|
|
41
|
+
|
|
42
|
+
async def register_emit(self, event):
|
|
43
|
+
users: list = event.get('users', [])
|
|
44
|
+
clients: list = event.get('clients',[])
|
|
45
|
+
if self.client in clients:
|
|
46
|
+
self.uid = users[0]
|
|
47
|
+
|
|
48
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
async def execute_channel(method, sender):
|
|
4
|
+
thread = asyncio.to_thread(__execute__, method, sender)
|
|
5
|
+
asyncio.create_task(thread)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def __execute__(method, sender):
|
|
9
|
+
sender.to_clients(None, [sender.client], 'start')
|
|
10
|
+
method(sender)
|
|
11
|
+
sender.to_clients(None, [sender.client], 'stop')
|
|
12
|
+
|
|
13
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
5
|
+
|
|
6
|
+
from ..classes.singleton_meta import SingletonMeta
|
|
7
|
+
|
|
8
|
+
class ChannelMapping(metaclass=SingletonMeta):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
mapping = settings.HANDLER_MAPPING
|
|
11
|
+
root, name = mapping.rsplit('.', 1)
|
|
12
|
+
module = importlib.import_module(root)
|
|
13
|
+
if hasattr(module, name):
|
|
14
|
+
self.mapping: dict = getattr(module, name)
|
|
15
|
+
else:
|
|
16
|
+
raise ImproperlyConfigured("%r has no attribute %r" % (root, name))
|
|
17
|
+
|
|
18
|
+
def get_handler(self, handler):
|
|
19
|
+
method = self.mapping.get(handler)
|
|
20
|
+
if method is None:
|
|
21
|
+
raise ImproperlyConfigured("Cannot find handler - %r" % handler)
|
|
22
|
+
return method
|
|
23
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from asgiref.sync import async_to_sync
|
|
6
|
+
from channels.layers import get_channel_layer
|
|
7
|
+
from django.http import HttpRequest
|
|
8
|
+
|
|
9
|
+
from .consumer import VALAR_CHANNEL_GROUP
|
|
10
|
+
from ..classes.counter import Counter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ValarSocketSender:
|
|
14
|
+
def __init__(self, request: HttpRequest, data=None):
|
|
15
|
+
body = json.loads(request.body)
|
|
16
|
+
auth = request.headers.get('AUTH')
|
|
17
|
+
self.client = request.headers.get('CLIENT')
|
|
18
|
+
self.uid = request.session.get('UID')
|
|
19
|
+
self.handlerKey = body.get('handlerKey')
|
|
20
|
+
self.channelKey = body.get('channelKey', 'default')
|
|
21
|
+
self.data = data or body.get('data')
|
|
22
|
+
self.send = get_channel_layer().group_send
|
|
23
|
+
self.start_time = time.time()
|
|
24
|
+
if auth and not self.uid:
|
|
25
|
+
raise Exception('Unauthorized!')
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def create_counter(length:int):
|
|
29
|
+
return Counter(length)
|
|
30
|
+
|
|
31
|
+
def __convert_body(self, emit, payload, status ,clients = None, users = None):
|
|
32
|
+
return {
|
|
33
|
+
'type': emit,
|
|
34
|
+
'data': {
|
|
35
|
+
'status': status,
|
|
36
|
+
'handlerKey': self.handlerKey,
|
|
37
|
+
'channelKey': self.channelKey,
|
|
38
|
+
'payload': payload,
|
|
39
|
+
'timestamp': datetime.now().timestamp()
|
|
40
|
+
},
|
|
41
|
+
'clients': clients or [],
|
|
42
|
+
'users': users or [],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def to_users(self, payload, users, status='proceed'):
|
|
47
|
+
body = self.__convert_body(emit='user.emit', payload=payload, status=status, users=users)
|
|
48
|
+
async_to_sync(self.send)(VALAR_CHANNEL_GROUP, body)
|
|
49
|
+
|
|
50
|
+
def to_clients(self,payload, clients, status='proceed', wait=False):
|
|
51
|
+
current_time = time.time()
|
|
52
|
+
time_span = current_time - self.start_time
|
|
53
|
+
if (wait and time_span > 1 and status == 'proceed') or not wait:
|
|
54
|
+
body = self.__convert_body(emit='client.emit', payload=payload, status=status, clients=clients)
|
|
55
|
+
async_to_sync(self.send)(VALAR_CHANNEL_GROUP, body)
|
|
56
|
+
self.start_time = current_time
|
|
57
|
+
|
|
58
|
+
def broadcast(self, payload, status):
|
|
59
|
+
body = self.__convert_body(emit='broadcast.emit', payload=payload, status=status)
|
|
60
|
+
async_to_sync(self.send)(VALAR_CHANNEL_GROUP, body)
|
|
61
|
+
|
|
62
|
+
def register(self):
|
|
63
|
+
body = self.__convert_body(emit='register.emit', payload=None, status=None,clients=[self.client], users=[self.uid])
|
|
64
|
+
async_to_sync(self.send)(VALAR_CHANNEL_GROUP, body)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
from .executer import execute_channel
|
|
3
|
+
from .mapping import ChannelMapping
|
|
4
|
+
from .sender import ValarSocketSender
|
|
5
|
+
from ..classes.valar_response import ValarResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def handel_channel(request, handler):
|
|
9
|
+
sender = ValarSocketSender(request)
|
|
10
|
+
method = ChannelMapping().get_handler(handler)
|
|
11
|
+
await execute_channel(method, sender)
|
|
12
|
+
return ValarResponse(True)
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
class AutoMigrationMixin:
|
|
2
|
+
name = None # 子类必须提供
|
|
3
|
+
def auto_migrate(self):
|
|
4
|
+
from django.core.management import call_command
|
|
5
|
+
app = self.name.replace('src.','')
|
|
6
|
+
call_command('makemigrations', app, interactive=False, verbosity=0)
|
|
7
|
+
call_command('migrate', app, interactive=False, verbosity=0)
|
|
8
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.urls import path, include
|
|
5
|
+
|
|
6
|
+
class AutoUrlPatternsMixin:
|
|
7
|
+
name = None # 子类必须提供
|
|
8
|
+
def set_url(self):
|
|
9
|
+
root = settings.ROOT_URLCONF
|
|
10
|
+
module = importlib.import_module(root)
|
|
11
|
+
urlpatterns: list = getattr(module, 'urlpatterns')
|
|
12
|
+
url = f'{self.name}.urls'
|
|
13
|
+
urlpatterns.append(path('data/', include(url)))
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class Counter:
|
|
2
|
+
def __init__(self, array_or_int):
|
|
3
|
+
self.length = array_or_int if isinstance(array_or_int,int) else len(array_or_int)
|
|
4
|
+
self.index = 0
|
|
5
|
+
|
|
6
|
+
def tick(self):
|
|
7
|
+
self.index += 1
|
|
8
|
+
percentage = round(self.index * 100 / self.length) if self.length else 0
|
|
9
|
+
return {"index": self.index, "length": self.length, "percentage": percentage}
|
|
File without changes
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-07-15 18:11
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
initial = True
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.CreateModel(
|
|
16
|
+
name='Meta',
|
|
17
|
+
fields=[
|
|
18
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
19
|
+
('sort', models.BigIntegerField(null=True, verbose_name='序号')),
|
|
20
|
+
('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
|
21
|
+
('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
|
|
22
|
+
('saved', models.BooleanField(default=False)),
|
|
23
|
+
('db', models.CharField(max_length=100, null=True, verbose_name='数据库')),
|
|
24
|
+
('entity', models.CharField(max_length=100, null=True, verbose_name='数据源')),
|
|
25
|
+
('name', models.CharField(max_length=50, null=True, verbose_name='实体别名')),
|
|
26
|
+
],
|
|
27
|
+
options={
|
|
28
|
+
'verbose_name': '数据实体',
|
|
29
|
+
'unique_together': {('db', 'entity')},
|
|
30
|
+
},
|
|
31
|
+
),
|
|
32
|
+
migrations.CreateModel(
|
|
33
|
+
name='MetaFieldTool',
|
|
34
|
+
fields=[
|
|
35
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
36
|
+
('sort', models.BigIntegerField(null=True, verbose_name='序号')),
|
|
37
|
+
('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
|
38
|
+
('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
|
|
39
|
+
('saved', models.BooleanField(default=False)),
|
|
40
|
+
('pid', models.IntegerField(default=0, verbose_name='父节点')),
|
|
41
|
+
('isLeaf', models.BooleanField(default=False, verbose_name='叶子节点')),
|
|
42
|
+
('icon', models.CharField(max_length=255, null=True, verbose_name='图标')),
|
|
43
|
+
('name', models.CharField(max_length=255, null=True, verbose_name='名称')),
|
|
44
|
+
('code', models.CharField(max_length=100, null=True, unique=True, verbose_name='代码')),
|
|
45
|
+
],
|
|
46
|
+
options={
|
|
47
|
+
'verbose_name': '元数据字段工具',
|
|
48
|
+
},
|
|
49
|
+
),
|
|
50
|
+
migrations.CreateModel(
|
|
51
|
+
name='MetaView',
|
|
52
|
+
fields=[
|
|
53
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
54
|
+
('sort', models.BigIntegerField(null=True, verbose_name='序号')),
|
|
55
|
+
('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
|
56
|
+
('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
|
|
57
|
+
('saved', models.BooleanField(default=False)),
|
|
58
|
+
('code', models.CharField(default='default ', max_length=50, verbose_name='类视图')),
|
|
59
|
+
('name', models.CharField(max_length=50, null=True, verbose_name='视图名称')),
|
|
60
|
+
('form_width', models.IntegerField(default=0, verbose_name='表单宽度')),
|
|
61
|
+
('form_height', models.IntegerField(default=0, verbose_name='表单高度')),
|
|
62
|
+
('table_width', models.IntegerField(default=0, verbose_name='表格宽度')),
|
|
63
|
+
('table_height', models.IntegerField(default=0, verbose_name='表格高度')),
|
|
64
|
+
('enable', models.BooleanField(default=True, verbose_name='是否启用')),
|
|
65
|
+
('show_header', models.BooleanField(default=True, verbose_name='展示头部')),
|
|
66
|
+
('allow_search', models.BooleanField(default=True, verbose_name='检索功能')),
|
|
67
|
+
('allow_sort', models.BooleanField(default=True, verbose_name='移动功能')),
|
|
68
|
+
('allow_order', models.BooleanField(default=True, verbose_name='排序功能')),
|
|
69
|
+
('allow_insert', models.BooleanField(default=True, verbose_name='新增功能')),
|
|
70
|
+
('allow_edit', models.BooleanField(default=True, verbose_name='编辑功能')),
|
|
71
|
+
('allow_remove', models.BooleanField(default=True, verbose_name='删除功能')),
|
|
72
|
+
('allow_download', models.BooleanField(default=True, verbose_name='下载功能')),
|
|
73
|
+
('allow_upload', models.BooleanField(default=True, verbose_name='上传功能')),
|
|
74
|
+
('meta', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='valar.meta', verbose_name='元数据')),
|
|
75
|
+
],
|
|
76
|
+
options={
|
|
77
|
+
'verbose_name': '数据视图',
|
|
78
|
+
'unique_together': {('meta', 'code')},
|
|
79
|
+
},
|
|
80
|
+
),
|
|
81
|
+
migrations.CreateModel(
|
|
82
|
+
name='MetaFieldDomain',
|
|
83
|
+
fields=[
|
|
84
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
85
|
+
('sort', models.BigIntegerField(null=True, verbose_name='序号')),
|
|
86
|
+
('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
|
87
|
+
('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
|
|
88
|
+
('saved', models.BooleanField(default=False)),
|
|
89
|
+
('name', models.CharField(max_length=255, null=True, unique=True, verbose_name='名称')),
|
|
90
|
+
('align', models.CharField(max_length=10, null=True, verbose_name='对齐方式')),
|
|
91
|
+
('default', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='valar.metafieldtool', verbose_name='默认工具')),
|
|
92
|
+
('search', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='valar.metafieldtool', verbose_name='搜索工具')),
|
|
93
|
+
('tools', models.ManyToManyField(to='valar.metafieldtool', verbose_name='工具集')),
|
|
94
|
+
],
|
|
95
|
+
options={
|
|
96
|
+
'verbose_name': '元数据字段类型',
|
|
97
|
+
},
|
|
98
|
+
),
|
|
99
|
+
migrations.CreateModel(
|
|
100
|
+
name='MetaField',
|
|
101
|
+
fields=[
|
|
102
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
103
|
+
('sort', models.BigIntegerField(null=True, verbose_name='序号')),
|
|
104
|
+
('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
|
105
|
+
('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
|
|
106
|
+
('saved', models.BooleanField(default=False)),
|
|
107
|
+
('prop', models.CharField(max_length=100, verbose_name='字段名称')),
|
|
108
|
+
('label', models.CharField(max_length=100, verbose_name='字段标签')),
|
|
109
|
+
('name', models.CharField(max_length=100, verbose_name='字段别名')),
|
|
110
|
+
('domain', models.CharField(max_length=100, verbose_name='字段类型')),
|
|
111
|
+
('tool', models.CharField(default='default', max_length=100, verbose_name='工具组件')),
|
|
112
|
+
('refer', models.JSONField(default=dict, verbose_name='索引')),
|
|
113
|
+
('format', models.JSONField(default=dict, verbose_name='格式')),
|
|
114
|
+
('not_null', models.BooleanField(default=False, verbose_name='不为空')),
|
|
115
|
+
('allow_edit', models.BooleanField(default=True, verbose_name='可编辑')),
|
|
116
|
+
('allow_order', models.BooleanField(default=True, verbose_name='可排序')),
|
|
117
|
+
('allow_search', models.BooleanField(default=True, verbose_name='可搜索')),
|
|
118
|
+
('allow_download', models.BooleanField(default=True, verbose_name='可下载')),
|
|
119
|
+
('allow_upload', models.BooleanField(default=False, verbose_name='可上传')),
|
|
120
|
+
('allow_update', models.BooleanField(default=False, verbose_name='可更新')),
|
|
121
|
+
('unit', models.CharField(max_length=55, null=True, verbose_name='单位符')),
|
|
122
|
+
('column_width', models.FloatField(default=0, verbose_name='表头宽度')),
|
|
123
|
+
('align', models.CharField(default='left', max_length=55, verbose_name='对齐方式')),
|
|
124
|
+
('fixed', models.CharField(max_length=100, null=True, verbose_name='固定位置')),
|
|
125
|
+
('header_color', models.CharField(max_length=55, null=True, verbose_name='表头颜色')),
|
|
126
|
+
('cell_color', models.CharField(max_length=55, null=True, verbose_name='单元颜色')),
|
|
127
|
+
('edit_on_table', models.BooleanField(default=True, verbose_name='表格编辑')),
|
|
128
|
+
('hide_on_table', models.BooleanField(default=False, verbose_name='表内隐藏')),
|
|
129
|
+
('span', models.IntegerField(default=0, verbose_name='表单占位')),
|
|
130
|
+
('hide_on_form', models.BooleanField(default=False, verbose_name='表单隐藏')),
|
|
131
|
+
('hide_on_form_edit', models.BooleanField(default=False, verbose_name='编辑隐藏')),
|
|
132
|
+
('hide_on_form_insert', models.BooleanField(default=False, verbose_name='新增隐藏')),
|
|
133
|
+
('hide_on_form_branch', models.BooleanField(default=False, verbose_name='分支隐藏')),
|
|
134
|
+
('hide_on_form_leaf', models.BooleanField(default=False, verbose_name='叶子隐藏')),
|
|
135
|
+
('view', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='valar.metaview', verbose_name='数据视图')),
|
|
136
|
+
],
|
|
137
|
+
options={
|
|
138
|
+
'verbose_name': '视图字段',
|
|
139
|
+
},
|
|
140
|
+
),
|
|
141
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import core, frame, meta
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
from django.db import models
|
|
3
|
+
from django.db.models import ManyToOneRel, OneToOneRel, ManyToManyRel, ManyToManyField, UUIDField, FileField, \
|
|
4
|
+
ForeignKey, OneToOneField, DateField, TimeField, DateTimeField, BigAutoField
|
|
5
|
+
from django.db.models.options import Options
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class VModel(models.Model):
|
|
9
|
+
objects = models.Manager()
|
|
10
|
+
sort = models.BigIntegerField(null=True, verbose_name='序号')
|
|
11
|
+
name = models.CharField(max_length=50, null=True)
|
|
12
|
+
create_time = models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')
|
|
13
|
+
modify_time = models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')
|
|
14
|
+
saved = models.BooleanField(default=False)
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
abstract = True
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
return str(self.full())
|
|
21
|
+
|
|
22
|
+
def get_meta(self)->Options:
|
|
23
|
+
return getattr(self, '_meta')
|
|
24
|
+
|
|
25
|
+
def get_entity(self):
|
|
26
|
+
name = type(self).__name__
|
|
27
|
+
config: AppConfig = self.get_meta().app_config
|
|
28
|
+
return f'{config.label}.{name}'
|
|
29
|
+
|
|
30
|
+
def json(self, *args, **kwargs):
|
|
31
|
+
excludes = [ManyToOneRel, OneToOneRel, ManyToManyField, ManyToManyRel, UUIDField]
|
|
32
|
+
fields = [field for field in self.get_meta().get_fields() if type(field) not in excludes]
|
|
33
|
+
data = {}
|
|
34
|
+
for field in fields:
|
|
35
|
+
value = field.value_from_object(self)
|
|
36
|
+
prop = field.name
|
|
37
|
+
domain = type(field)
|
|
38
|
+
if value is not None:
|
|
39
|
+
if domain in [ForeignKey, OneToOneField]:
|
|
40
|
+
prop = f'{prop}_id'
|
|
41
|
+
elif domain in [DateTimeField]:
|
|
42
|
+
value = value.strftime('%Y-%m-%d %H:%M:%S')
|
|
43
|
+
elif domain in [DateField]:
|
|
44
|
+
value = value.strftime('%Y-%m-%d')
|
|
45
|
+
elif domain in [TimeField]:
|
|
46
|
+
value = value.strftime('%H:%M:%S')
|
|
47
|
+
elif domain in [FileField]:
|
|
48
|
+
value = value.name
|
|
49
|
+
elif domain in [BigAutoField]:
|
|
50
|
+
value = str(value)
|
|
51
|
+
data[prop] = value
|
|
52
|
+
data.update({f'${k}': v for k,v in kwargs.items()})
|
|
53
|
+
return data
|
|
54
|
+
|
|
55
|
+
def full(self):
|
|
56
|
+
includes = [ManyToManyField, ManyToManyRel, ForeignKey, ManyToOneRel, OneToOneField, OneToOneRel]
|
|
57
|
+
fields = [field for field in self.get_meta().get_fields() if type(field) in includes]
|
|
58
|
+
data = self.json()
|
|
59
|
+
for field in fields:
|
|
60
|
+
prop = field.name
|
|
61
|
+
domain = type(field)
|
|
62
|
+
if domain in [ForeignKey,OneToOneField]:
|
|
63
|
+
bean: VModel = getattr(self, prop)
|
|
64
|
+
data[prop] = bean.json() if bean else None
|
|
65
|
+
elif domain == OneToOneRel:
|
|
66
|
+
print('OneToOneRel')
|
|
67
|
+
pass
|
|
68
|
+
elif domain in [ManyToManyField,ManyToManyRel,ManyToOneRel]:
|
|
69
|
+
accessor = prop if domain == ManyToManyField else field.get_accessor_name()
|
|
70
|
+
_set = getattr(self, accessor).all().order_by('-sort')
|
|
71
|
+
data[prop] = [item.id for item in _set]
|
|
72
|
+
data[f'{prop}_set'] = [item.json() for item in _set]
|
|
73
|
+
return data
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class VTree(VModel):
|
|
77
|
+
pid = models.IntegerField(null=False, default=0, verbose_name='父节点')
|
|
78
|
+
isLeaf = models.BooleanField( default=False, verbose_name='叶子节点')
|
|
79
|
+
icon = models.CharField(max_length=255, null=True, verbose_name='图标')
|
|
80
|
+
class Meta:
|
|
81
|
+
abstract = True
|
|
82
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from .core import VModel, VTree
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MetaFieldTool(VTree):
|
|
6
|
+
name = models.CharField(max_length=255, null=True, verbose_name='名称')
|
|
7
|
+
code = models.CharField(max_length=100, unique=True, null=True, verbose_name='代码') #
|
|
8
|
+
|
|
9
|
+
class Meta:
|
|
10
|
+
verbose_name = '元数据字段工具'
|
|
11
|
+
#
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
class MetaFieldDomain(VModel):
|
|
15
|
+
name = models.CharField(max_length=255, unique=True, null=True, verbose_name='名称')
|
|
16
|
+
tools = models.ManyToManyField(to=MetaFieldTool, verbose_name='工具集')
|
|
17
|
+
default = models.ForeignKey(
|
|
18
|
+
to=MetaFieldTool, null=True,
|
|
19
|
+
on_delete=models.SET_NULL,
|
|
20
|
+
related_name='+',
|
|
21
|
+
verbose_name='默认工具')
|
|
22
|
+
search = models.ForeignKey(
|
|
23
|
+
to=MetaFieldTool, null=True,
|
|
24
|
+
on_delete=models.SET_NULL,
|
|
25
|
+
related_name='+',
|
|
26
|
+
verbose_name='搜索工具')
|
|
27
|
+
align = models.CharField(max_length=10, null=True, verbose_name='对齐方式')
|
|
28
|
+
class Meta:
|
|
29
|
+
verbose_name = '元数据字段类型'
|
|
30
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from .core import VModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Meta(VModel):
|
|
6
|
+
db = models.CharField(max_length=100, verbose_name='数据库', null=True)
|
|
7
|
+
entity = models.CharField(max_length=100, verbose_name='数据源', null=True)
|
|
8
|
+
name = models.CharField(max_length=50, verbose_name='实体别名', null=True)
|
|
9
|
+
|
|
10
|
+
class Meta:
|
|
11
|
+
verbose_name = '数据实体'
|
|
12
|
+
unique_together = ('db', 'entity')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MetaView(VModel):
|
|
16
|
+
meta = models.ForeignKey('Meta', on_delete=models.CASCADE, verbose_name='元数据')
|
|
17
|
+
|
|
18
|
+
code = models.CharField(max_length=50, verbose_name='类视图', default='default ')
|
|
19
|
+
name = models.CharField(max_length=50, verbose_name='视图名称', null=True)
|
|
20
|
+
|
|
21
|
+
form_width = models.IntegerField(default=0, verbose_name='表单宽度')
|
|
22
|
+
form_height = models.IntegerField(default=0, verbose_name='表单高度')
|
|
23
|
+
table_width = models.IntegerField(default=0, verbose_name='表格宽度')
|
|
24
|
+
table_height = models.IntegerField(default=0, verbose_name='表格高度')
|
|
25
|
+
|
|
26
|
+
enable = models.BooleanField(default=True, verbose_name='是否启用')
|
|
27
|
+
show_header = models.BooleanField(default=True, verbose_name='展示头部')
|
|
28
|
+
allow_search = models.BooleanField(default=True, verbose_name='检索功能')
|
|
29
|
+
allow_sort = models.BooleanField(default=True, verbose_name='移动功能')
|
|
30
|
+
allow_order = models.BooleanField(default=True, verbose_name='排序功能')
|
|
31
|
+
allow_insert = models.BooleanField(default=True, verbose_name='新增功能')
|
|
32
|
+
allow_edit = models.BooleanField(default=True, verbose_name='编辑功能')
|
|
33
|
+
allow_remove = models.BooleanField(default=True, verbose_name='删除功能')
|
|
34
|
+
allow_download = models.BooleanField(default=True, verbose_name='下载功能')
|
|
35
|
+
allow_upload = models.BooleanField(default=True, verbose_name='上传功能')
|
|
36
|
+
|
|
37
|
+
class Meta:
|
|
38
|
+
verbose_name = '数据视图'
|
|
39
|
+
unique_together = ('meta', 'code')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MetaField(VModel):
|
|
43
|
+
# 标识
|
|
44
|
+
view = models.ForeignKey('MetaView', on_delete=models.CASCADE, verbose_name='数据视图')
|
|
45
|
+
prop = models.CharField(max_length=100, verbose_name='字段名称') #
|
|
46
|
+
label = models.CharField(max_length=100, verbose_name='字段标签') #
|
|
47
|
+
name = models.CharField(max_length=100, verbose_name='字段别名') #
|
|
48
|
+
|
|
49
|
+
"""tool"""
|
|
50
|
+
domain = models.CharField(max_length=100, verbose_name='字段类型') #
|
|
51
|
+
tool = models.CharField(max_length=100, default='default', verbose_name='工具组件')
|
|
52
|
+
refer = models.JSONField(default=dict, verbose_name='索引') #
|
|
53
|
+
format = models.JSONField(default=dict, verbose_name='格式') #
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
"""rest"""
|
|
57
|
+
not_null = models.BooleanField(default=False, verbose_name='不为空') #
|
|
58
|
+
allow_edit = models.BooleanField(default=True, verbose_name='可编辑')
|
|
59
|
+
allow_order = models.BooleanField(default=True, verbose_name='可排序')
|
|
60
|
+
allow_search = models.BooleanField(default=True, verbose_name='可搜索')
|
|
61
|
+
allow_download = models.BooleanField(default=True, verbose_name='可下载')
|
|
62
|
+
allow_upload = models.BooleanField(default=False, verbose_name='可上传')
|
|
63
|
+
allow_update = models.BooleanField(default=False, verbose_name='可更新')
|
|
64
|
+
|
|
65
|
+
"""table"""
|
|
66
|
+
unit = models.CharField(max_length=55, verbose_name='单位符', null=True)
|
|
67
|
+
column_width = models.FloatField(default=0, verbose_name='表头宽度')
|
|
68
|
+
align = models.CharField(max_length=55, default='left', verbose_name='对齐方式') #
|
|
69
|
+
fixed = models.CharField(max_length=100, verbose_name='固定位置', null=True)
|
|
70
|
+
header_color = models.CharField(max_length=55, verbose_name='表头颜色', null=True)
|
|
71
|
+
cell_color = models.CharField(max_length=55, verbose_name='单元颜色', null=True)
|
|
72
|
+
edit_on_table = models.BooleanField(default=True, verbose_name='表格编辑')
|
|
73
|
+
hide_on_table = models.BooleanField(default=False, verbose_name='表内隐藏')
|
|
74
|
+
|
|
75
|
+
"""form"""
|
|
76
|
+
span = models.IntegerField(default=0, verbose_name='表单占位')
|
|
77
|
+
hide_on_form = models.BooleanField(default=False, verbose_name='表单隐藏')
|
|
78
|
+
hide_on_form_edit = models.BooleanField(default=False, verbose_name='编辑隐藏')
|
|
79
|
+
hide_on_form_insert = models.BooleanField(default=False, verbose_name='新增隐藏')
|
|
80
|
+
hide_on_form_branch = models.BooleanField(default=False, verbose_name='分支隐藏')
|
|
81
|
+
hide_on_form_leaf = models.BooleanField(default=False, verbose_name='叶子隐藏')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Meta:
|
|
85
|
+
verbose_name = '视图字段'
|
|
86
|
+
|
|
87
|
+
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: valar
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.4
|
|
4
4
|
Summary: valar for morghulis
|
|
5
5
|
Home-page: https://gitee.com/GRIFFIN120/valar_dev
|
|
6
6
|
Author: LYP
|
|
@@ -36,16 +36,28 @@ pip install valar
|
|
|
36
36
|
# 1. settings
|
|
37
37
|
|
|
38
38
|
```python
|
|
39
|
-
|
|
39
|
+
from django.core.management.utils import get_random_secret_key
|
|
40
40
|
from pathlib import Path
|
|
41
41
|
|
|
42
|
-
"""
|
|
42
|
+
""" Compulsory settings """
|
|
43
|
+
|
|
43
44
|
DEBUG = True
|
|
44
45
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
45
46
|
BASE_APP = str(BASE_DIR.name)
|
|
46
47
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
47
|
-
SECRET_KEY =
|
|
48
|
-
|
|
48
|
+
SECRET_KEY = get_random_secret_key()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
""" Valar Options """
|
|
53
|
+
|
|
54
|
+
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
55
|
+
MONGO_URI = 'mongodb://root:19870120@121.41.111.175:27017/'
|
|
56
|
+
MINIO_URL = "s3://admin:password@120.27.8.186:9000"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
""" Minimized compulsory settings """
|
|
49
61
|
|
|
50
62
|
DATABASES = {
|
|
51
63
|
'default': {
|
|
@@ -53,55 +65,38 @@ DATABASES = {
|
|
|
53
65
|
'NAME': BASE_DIR / 'db.sqlite3',
|
|
54
66
|
}
|
|
55
67
|
}
|
|
56
|
-
|
|
57
68
|
INSTALLED_APPS = [
|
|
58
69
|
'django.contrib.sessions',
|
|
59
70
|
"corsheaders",
|
|
60
71
|
'channels',
|
|
61
|
-
'
|
|
72
|
+
'valar',
|
|
62
73
|
]
|
|
63
|
-
|
|
64
74
|
MIDDLEWARE = [
|
|
65
75
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
66
76
|
'corsheaders.middleware.CorsMiddleware',
|
|
67
77
|
'django.middleware.common.CommonMiddleware',
|
|
68
78
|
]
|
|
69
|
-
|
|
70
79
|
CHANNEL_LAYERS = {
|
|
71
80
|
"default": {
|
|
72
81
|
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
|
-
|
|
76
84
|
CORS_ORIGIN_ALLOW_ALL = True
|
|
77
85
|
CORS_ALLOW_CREDENTIALS = True
|
|
78
86
|
ROOT_URLCONF = "%s.urls" % BASE_APP
|
|
79
87
|
ASGI_APPLICATION = "%s.asgi.application" % BASE_APP
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
'host': '<IP>',
|
|
83
|
-
'port': '<PORT>',
|
|
84
|
-
"username": "<USERNAME>",
|
|
85
|
-
"password": '<PASSWORD>'
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
MINIO = {
|
|
89
|
-
'endpoint': '<IP>:<PORT>',
|
|
90
|
-
'access_key': '<USERNAME>',
|
|
91
|
-
"secret_key": "<PASSWORD>",
|
|
92
|
-
'secure': False
|
|
93
|
-
}
|
|
89
|
+
""" Optional settings """
|
|
94
90
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
91
|
+
ALLOWED_HOSTS = ['*']
|
|
92
|
+
LANGUAGE_CODE = 'en-us'
|
|
93
|
+
TIME_ZONE = 'Asia/Shanghai'
|
|
94
|
+
USE_I18N = True
|
|
95
|
+
USE_TZ = False
|
|
96
|
+
SESSION_SAVE_EVERY_REQUEST = True
|
|
97
|
+
SESSION_COOKIE_AGE = 60 * 60
|
|
98
|
+
FILE_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
99
|
+
DATA_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024 * 100
|
|
105
100
|
|
|
106
101
|
```
|
|
107
102
|
# 2. asgi
|
|
@@ -110,7 +105,7 @@ MINIO = {
|
|
|
110
105
|
from django.core.asgi import get_asgi_application
|
|
111
106
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
112
107
|
from django.urls import re_path
|
|
113
|
-
from
|
|
108
|
+
from valar.channels.consumer import ValarConsumer
|
|
114
109
|
|
|
115
110
|
application = ProtocolTypeRouter({
|
|
116
111
|
'http': get_asgi_application(),
|
|
@@ -122,41 +117,39 @@ application = ProtocolTypeRouter({
|
|
|
122
117
|
```
|
|
123
118
|
|
|
124
119
|
# 3. migrate
|
|
125
|
-
|
|
126
|
-
python manage.py makemigrations
|
|
127
|
-
python manage.py migrate
|
|
128
|
-
```
|
|
120
|
+
no need, valar will auto migration
|
|
129
121
|
|
|
130
122
|
|
|
131
123
|
# 4. root urls
|
|
132
124
|
|
|
133
|
-
|
|
134
|
-
from django.urls import path, include
|
|
125
|
+
no need, valar will auto set urlpatterns
|
|
135
126
|
|
|
136
|
-
|
|
137
|
-
path('data/', include('src.valar.data.urls')),
|
|
138
|
-
]
|
|
127
|
+
# 5. how to use valar_channel
|
|
139
128
|
|
|
140
|
-
channel_mapping = {
|
|
141
|
-
# 'test': test_handler
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
129
|
|
|
145
|
-
|
|
130
|
+
5.1 set HANDLER_MAPPING in settings
|
|
146
131
|
|
|
147
132
|
```python
|
|
133
|
+
HANDLER_MAPPING = "%s.urls.channel_mapping" % BASE_APP
|
|
134
|
+
```
|
|
148
135
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
from src.valar.channels.sender import ValarSocketSender
|
|
152
|
-
from src.valar import Counter
|
|
153
|
-
|
|
136
|
+
5.2 create a handler
|
|
154
137
|
|
|
138
|
+
```python
|
|
139
|
+
from valar.channels.sender import ValarSocketSender
|
|
155
140
|
def test_handler(sender: ValarSocketSender):
|
|
156
141
|
data = sender.data
|
|
157
|
-
length = data.get('length',
|
|
158
|
-
counter =
|
|
142
|
+
length = data.get('length',30)
|
|
143
|
+
counter = sender.create_counter(length)
|
|
159
144
|
for i in range(length):
|
|
160
|
-
|
|
161
|
-
sender.to_clients(counter.tick(),
|
|
145
|
+
# sync method
|
|
146
|
+
sender.to_clients(counter.tick() ,sender.client, wait=True)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
5.3 register handler in channel_mapping
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
channel_mapping = {
|
|
153
|
+
'test': test_handler,
|
|
154
|
+
}
|
|
162
155
|
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
src/valar/__init__.py
|
|
5
|
+
src/valar/apps.py
|
|
6
|
+
src/valar/urls.py
|
|
7
|
+
src/valar.egg-info/PKG-INFO
|
|
8
|
+
src/valar.egg-info/SOURCES.txt
|
|
9
|
+
src/valar.egg-info/dependency_links.txt
|
|
10
|
+
src/valar.egg-info/requires.txt
|
|
11
|
+
src/valar.egg-info/top_level.txt
|
|
12
|
+
src/valar/channels/__init__.py
|
|
13
|
+
src/valar/channels/consumer.py
|
|
14
|
+
src/valar/channels/executer.py
|
|
15
|
+
src/valar/channels/mapping.py
|
|
16
|
+
src/valar/channels/sender.py
|
|
17
|
+
src/valar/channels/views.py
|
|
18
|
+
src/valar/classes/__init__.py
|
|
19
|
+
src/valar/classes/auto_migration_mixin.py
|
|
20
|
+
src/valar/classes/auto_urlpatterns_mixin.py
|
|
21
|
+
src/valar/classes/counter.py
|
|
22
|
+
src/valar/classes/singleton_meta.py
|
|
23
|
+
src/valar/classes/valar_response.py
|
|
24
|
+
src/valar/frame/__init__.py
|
|
25
|
+
src/valar/migrations/0001_initial.py
|
|
26
|
+
src/valar/migrations/__init__.py
|
|
27
|
+
src/valar/models/__init__.py
|
|
28
|
+
src/valar/models/core.py
|
|
29
|
+
src/valar/models/frame.py
|
|
30
|
+
src/valar/models/meta.py
|
|
31
|
+
src/valar/views/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
valar
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|