valar 1.0.17__tar.gz → 1.0.19__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 (35) hide show
  1. {valar-1.0.17/src/valar.egg-info → valar-1.0.19}/PKG-INFO +5 -1
  2. {valar-1.0.17 → valar-1.0.19}/setup.py +11 -2
  3. {valar-1.0.17 → valar-1.0.19}/src/valar/channels/__init__.py +7 -3
  4. {valar-1.0.17 → valar-1.0.19}/src/valar/channels/views.py +3 -2
  5. valar-1.0.19/src/valar/data/file/__init__.py +91 -0
  6. {valar-1.0.17 → valar-1.0.19}/src/valar/data/migrations/0001_initial.py +56 -28
  7. {valar-1.0.17 → valar-1.0.19}/src/valar/data/models.py +26 -13
  8. {valar-1.0.17 → valar-1.0.19}/src/valar/data/mon/__init__.py +44 -5
  9. valar-1.0.19/src/valar/data/mon/query_translator.py +91 -0
  10. {valar-1.0.17 → valar-1.0.19}/src/valar/data/orm/__init__.py +57 -9
  11. valar-1.0.19/src/valar/data/orm/meta.py +99 -0
  12. valar-1.0.19/src/valar/data/orm/meta_frame.py +49 -0
  13. {valar-1.0.17 → valar-1.0.19}/src/valar/data/orm/meta_loader.py +85 -20
  14. {valar-1.0.17 → valar-1.0.19}/src/valar/data/orm/values.py +11 -2
  15. {valar-1.0.17 → valar-1.0.19}/src/valar/data/query.py +14 -14
  16. valar-1.0.19/src/valar/data/urls.py +24 -0
  17. valar-1.0.19/src/valar/data/views.py +173 -0
  18. {valar-1.0.17 → valar-1.0.19/src/valar.egg-info}/PKG-INFO +5 -1
  19. {valar-1.0.17 → valar-1.0.19}/src/valar.egg-info/SOURCES.txt +3 -0
  20. {valar-1.0.17 → valar-1.0.19}/src/valar.egg-info/requires.txt +4 -0
  21. valar-1.0.17/src/valar/data/orm/meta.py +0 -24
  22. valar-1.0.17/src/valar/data/urls.py +0 -15
  23. valar-1.0.17/src/valar/data/views.py +0 -76
  24. {valar-1.0.17 → valar-1.0.19}/LICENSE +0 -0
  25. {valar-1.0.17 → valar-1.0.19}/README.md +0 -0
  26. {valar-1.0.17 → valar-1.0.19}/setup.cfg +0 -0
  27. {valar-1.0.17 → valar-1.0.19}/src/valar/__init__.py +0 -0
  28. {valar-1.0.17 → valar-1.0.19}/src/valar/channels/utils.py +0 -0
  29. {valar-1.0.17 → valar-1.0.19}/src/valar/data/__init__.py +0 -0
  30. {valar-1.0.17 → valar-1.0.19}/src/valar/data/handlers.py +0 -0
  31. {valar-1.0.17 → valar-1.0.19}/src/valar/data/migrations/__init__.py +0 -0
  32. {valar-1.0.17 → valar-1.0.19}/src/valar/data/orm/detacher.py +0 -0
  33. {valar-1.0.17 → valar-1.0.19}/src/valar/data/utils.py +0 -0
  34. {valar-1.0.17 → valar-1.0.19}/src/valar.egg-info/dependency_links.txt +0 -0
  35. {valar-1.0.17 → valar-1.0.19}/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.0.17
3
+ Version: 1.0.19
4
4
  Summary: valar for morghulis
5
5
  Author: LYP
6
6
  Author-email: liuyinpeng@buaa.edu.cn
@@ -11,6 +11,10 @@ Requires-Dist: channels==3.0.3
11
11
  Requires-Dist: pymongo~=4.11.2
12
12
  Requires-Dist: asgiref~=3.8.1
13
13
  Requires-Dist: django-cors-headers==4.2.0
14
+ Requires-Dist: pandas==2.2.3
15
+ Requires-Dist: openpyxl==3.1.5
16
+ Requires-Dist: deepmerge~=2.0
17
+ Requires-Dist: minio==7.2.2
14
18
  Dynamic: author
15
19
  Dynamic: author-email
16
20
  Dynamic: description
@@ -6,11 +6,20 @@ with open("README.md", "r", encoding="utf-8") as f:
6
6
  # with open('requirements.txt', "r", encoding="utf-8") as f:
7
7
  # required = f.read().splitlines()
8
8
 
9
- requires = ['channels==3.0.3','pymongo~=4.11.2','asgiref~=3.8.1','django-cors-headers==4.2.0']
9
+ requires = [
10
+ 'channels==3.0.3',
11
+ 'pymongo~=4.11.2',
12
+ 'asgiref~=3.8.1',
13
+ 'django-cors-headers==4.2.0',
14
+ 'pandas==2.2.3',
15
+ 'openpyxl==3.1.5',
16
+ 'deepmerge~=2.0',
17
+ 'minio==7.2.2'
18
+ ]
10
19
 
11
20
  setup(
12
21
  name="valar", # 包名
13
- version="1.0.17", # 版本号
22
+ version="1.0.19", # 版本号
14
23
  author="LYP", # 作者
15
24
  author_email="liuyinpeng@buaa.edu.cn", # 邮箱
16
25
  description="valar for morghulis", # 简短描述
@@ -1,3 +1,4 @@
1
+ import json
1
2
  from datetime import datetime
2
3
 
3
4
  from asgiref.sync import async_to_sync
@@ -13,7 +14,8 @@ except AttributeError:
13
14
  GROUP = 'VALAR'
14
15
 
15
16
  class ValarSocketSender:
16
- def __init__(self, handler: str, request: HttpRequest):
17
+ def __init__(self, request: HttpRequest):
18
+ body = json.loads(request.body)
17
19
  client = request.headers.get('CLIENT')
18
20
  auth = request.headers.get('AUTH')
19
21
  uid = request.session.get('UID')
@@ -21,7 +23,8 @@ class ValarSocketSender:
21
23
  raise Exception('Unauthorized!')
22
24
  self.client = client
23
25
  self.uid = uid
24
- self.handler = handler
26
+ self.handlerKey = body.get('handlerKey')
27
+ self.channelKey = body.get('channelKey', 'default')
25
28
  self.send = get_channel_layer().group_send
26
29
 
27
30
 
@@ -31,7 +34,8 @@ class ValarSocketSender:
31
34
  'type': emit,
32
35
  'data': {
33
36
  'status': status,
34
- 'handler': self.handler,
37
+ 'handlerKey': self.handlerKey,
38
+ 'channelKey': self.channelKey,
35
39
  'payload': data,
36
40
  'timestamp': datetime.now().timestamp()
37
41
  },
@@ -7,8 +7,9 @@ from ..channels import ValarSocketSender
7
7
 
8
8
  async def handel_channel(request, handler):
9
9
  method = get_channel_handler(handler)
10
- data = json.loads(request.body)
11
- sender = ValarSocketSender(handler,request)
10
+ body = json.loads(request.body)
11
+ data = body.get('data')
12
+ sender = ValarSocketSender(request)
12
13
  await execute_channel(method, data, sender)
13
14
  return ValarResponse(True)
14
15
 
@@ -0,0 +1,91 @@
1
+ import json
2
+ from io import BytesIO
3
+
4
+ from django.conf import settings
5
+ from minio import Minio
6
+ from urllib3 import HTTPResponse
7
+
8
+ def minio_upload_object(bucket_name, object_name, _bytes):
9
+ client = __get_minio_client__()
10
+ __create_bucket__(bucket_name, client)
11
+ file_data = BytesIO(_bytes)
12
+ file_size = len(_bytes) # file.siz
13
+ client.put_object(bucket_name=bucket_name, object_name=object_name, data=file_data, length=file_size)
14
+ return f'{bucket_name}/{object_name}'
15
+
16
+ def minio_remove_object(bucket_name, object_name):
17
+ client = __get_minio_client__()
18
+ client.remove_object(bucket_name=bucket_name, object_name=object_name)
19
+
20
+ def minio_remove_path(path):
21
+ [bucket_name, object_name] = path.split('/')
22
+ client = __get_minio_client__()
23
+ client.remove_object(bucket_name=bucket_name, object_name=object_name)
24
+
25
+
26
+ def minio_read_object(bucket_name, object_name) -> HTTPResponse:
27
+ client = __get_minio_client__()
28
+ return client.get_object(bucket_name=bucket_name, object_name=object_name)
29
+
30
+
31
+
32
+
33
+
34
+ def __get_minio_client__(bucket_name=None):
35
+ options = settings.MINIO_SETTINGS
36
+ client = Minio(**options)
37
+ if bucket_name:
38
+ __create_bucket__(bucket_name, client)
39
+ return client
40
+
41
+ def __create_bucket__(bucket_name, client=None):
42
+ client = client or __get_minio_client__()
43
+ exists = client.bucket_exists(bucket_name)
44
+ if not exists:
45
+ client.make_bucket(bucket_name)
46
+ policy = generate_policy(bucket_name)
47
+ client.set_bucket_policy(bucket_name, policy)
48
+
49
+
50
+ def get_minio_bucket_name(entity):
51
+ value = f'{settings.BASE_DIR.name}.{entity}'
52
+ bucket_name = value.replace('_','-').lower()
53
+ __create_bucket__(bucket_name)
54
+ return bucket_name
55
+
56
+ def get_minio_object_name(_id, prop, file_name):
57
+ return f"{_id}-{prop}-{file_name}"
58
+
59
+ def generate_policy(bucket_name):
60
+ return json.dumps({
61
+ "Version": "2012-10-17",
62
+ "Statement": [
63
+ {
64
+ "Sid": "",
65
+ "Effect": "Allow",
66
+ "Principal": {"AWS": "*"},
67
+ "Action": "s3:GetBucketLocation",
68
+ "Resource": f"arn:aws:s3:::{bucket_name}"
69
+ },
70
+ {
71
+ "Sid": "",
72
+ "Effect": "Allow",
73
+ "Principal": {"AWS": "*"},
74
+ "Action": "s3:ListBucket",
75
+ "Resource": f"arn:aws:s3:::{bucket_name}"
76
+ },
77
+ {
78
+ "Sid": "",
79
+ "Effect": "Allow",
80
+ "Principal": {"AWS": "*"},
81
+ "Action": "s3:GetObject",
82
+ "Resource": f"arn:aws:s3:::{bucket_name}/*"
83
+ },
84
+ {
85
+ "Sid": "",
86
+ "Effect": "Allow",
87
+ "Principal": {"AWS": "*"},
88
+ "Action": "s3:PutObject",
89
+ "Resource": f"arn:aws:s3:::{bucket_name}/*"
90
+ }
91
+ ]})
@@ -1,7 +1,7 @@
1
- # Generated by Django 5.1.7 on 2025-04-04 14:58
1
+ # Generated by Django 4.2.21 on 2025-05-17 11:07
2
2
 
3
- import django.db.models.deletion
4
3
  from django.db import migrations, models
4
+ import django.db.models.deletion
5
5
 
6
6
 
7
7
  class Migration(migrations.Migration):
@@ -46,6 +46,13 @@ class Migration(migrations.Migration):
46
46
  'verbose_name': '元数据字段工具',
47
47
  },
48
48
  ),
49
+ migrations.CreateModel(
50
+ name='TestM',
51
+ fields=[
52
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
53
+ ('name', models.CharField(max_length=100, verbose_name='<UNK>')),
54
+ ],
55
+ ),
49
56
  migrations.CreateModel(
50
57
  name='Vala',
51
58
  fields=[
@@ -70,21 +77,36 @@ class Migration(migrations.Migration):
70
77
  },
71
78
  ),
72
79
  migrations.CreateModel(
73
- name='MetaFieldDomain',
80
+ name='ValaTree',
74
81
  fields=[
75
82
  ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
76
83
  ('sort', models.BigIntegerField(null=True, verbose_name='序号')),
84
+ ('name', models.CharField(max_length=50, null=True)),
77
85
  ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
78
86
  ('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
79
87
  ('saved', models.BooleanField(default=False)),
80
- ('name', models.CharField(max_length=255, null=True, unique=True, verbose_name='名称')),
81
- ('align', models.CharField(max_length=10, null=True, verbose_name='对齐方式')),
82
- ('default', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='data.metafieldtool', verbose_name='默认工具')),
83
- ('search', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='data.metafieldtool', verbose_name='搜索工具')),
84
- ('tools', models.ManyToManyField(to='data.metafieldtool', verbose_name='工具集')),
88
+ ('pid', models.IntegerField(default=0, verbose_name='父节点')),
89
+ ('isLeaf', models.BooleanField(default=False, verbose_name='叶子节点')),
90
+ ('icon', models.CharField(max_length=255, null=True, verbose_name='图标')),
91
+ ('text', models.TextField(null=True, verbose_name='text')),
85
92
  ],
86
93
  options={
87
- 'verbose_name': '元数据字段类型',
94
+ 'verbose_name': '树形测试',
95
+ },
96
+ ),
97
+ migrations.CreateModel(
98
+ name='O2O',
99
+ fields=[
100
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
101
+ ('sort', models.BigIntegerField(null=True, verbose_name='序号')),
102
+ ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
103
+ ('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
104
+ ('saved', models.BooleanField(default=False)),
105
+ ('name', models.CharField(max_length=100, null=True, verbose_name='name')),
106
+ ('vala', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='data.vala', verbose_name='vala')),
107
+ ],
108
+ options={
109
+ 'abstract': False,
88
110
  },
89
111
  ),
90
112
  migrations.CreateModel(
@@ -120,6 +142,24 @@ class Migration(migrations.Migration):
120
142
  'unique_together': {('meta', 'code')},
121
143
  },
122
144
  ),
145
+ migrations.CreateModel(
146
+ name='MetaFieldDomain',
147
+ fields=[
148
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
149
+ ('sort', models.BigIntegerField(null=True, verbose_name='序号')),
150
+ ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
151
+ ('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
152
+ ('saved', models.BooleanField(default=False)),
153
+ ('name', models.CharField(max_length=255, null=True, unique=True, verbose_name='名称')),
154
+ ('align', models.CharField(max_length=10, null=True, verbose_name='对齐方式')),
155
+ ('default', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='data.metafieldtool', verbose_name='默认工具')),
156
+ ('search', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='data.metafieldtool', verbose_name='搜索工具')),
157
+ ('tools', models.ManyToManyField(to='data.metafieldtool', verbose_name='工具集')),
158
+ ],
159
+ options={
160
+ 'verbose_name': '元数据字段类型',
161
+ },
162
+ ),
123
163
  migrations.CreateModel(
124
164
  name='MetaField',
125
165
  fields=[
@@ -135,20 +175,20 @@ class Migration(migrations.Migration):
135
175
  ('tool', models.CharField(default='default', max_length=100, verbose_name='工具组件')),
136
176
  ('refer', models.JSONField(default=dict, verbose_name='索引')),
137
177
  ('format', models.JSONField(default=dict, verbose_name='格式')),
138
- ('unit', models.CharField(max_length=55, null=True, verbose_name='单位符')),
139
178
  ('not_null', models.BooleanField(default=False, verbose_name='不为空')),
140
- ('read_only', models.BooleanField(default=False, verbose_name='只读')),
141
- ('sortable', models.BooleanField(default=False, verbose_name='可排序')),
179
+ ('allow_edit', models.BooleanField(default=True, verbose_name='可编辑')),
180
+ ('allow_sort', models.BooleanField(default=True, verbose_name='可排序')),
142
181
  ('allow_search', models.BooleanField(default=True, verbose_name='可搜索')),
143
182
  ('allow_download', models.BooleanField(default=True, verbose_name='可下载')),
144
183
  ('allow_upload', models.BooleanField(default=False, verbose_name='可上传')),
145
184
  ('allow_update', models.BooleanField(default=False, verbose_name='可更新')),
185
+ ('unit', models.CharField(max_length=55, null=True, verbose_name='单位符')),
146
186
  ('column_width', models.FloatField(default=0, verbose_name='表头宽度')),
147
187
  ('align', models.CharField(default='left', max_length=55, verbose_name='对齐方式')),
148
188
  ('fixed', models.CharField(max_length=100, null=True, verbose_name='固定位置')),
149
189
  ('header_color', models.CharField(max_length=55, null=True, verbose_name='表头颜色')),
150
190
  ('cell_color', models.CharField(max_length=55, null=True, verbose_name='单元颜色')),
151
- ('edit_on_table', models.BooleanField(default=False, verbose_name='表格编辑')),
191
+ ('edit_on_table', models.BooleanField(default=True, verbose_name='表格编辑')),
152
192
  ('hide_on_table', models.BooleanField(default=False, verbose_name='表内隐藏')),
153
193
  ('span', models.IntegerField(default=0, verbose_name='表单占位')),
154
194
  ('hide_on_form', models.BooleanField(default=False, verbose_name='表单隐藏')),
@@ -162,21 +202,6 @@ class Migration(migrations.Migration):
162
202
  'verbose_name': '视图字段',
163
203
  },
164
204
  ),
165
- migrations.CreateModel(
166
- name='O2O',
167
- fields=[
168
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
169
- ('sort', models.BigIntegerField(null=True, verbose_name='序号')),
170
- ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
171
- ('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
172
- ('saved', models.BooleanField(default=False)),
173
- ('name', models.CharField(max_length=100, null=True, verbose_name='name')),
174
- ('vala', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='data.vala', verbose_name='vala')),
175
- ],
176
- options={
177
- 'abstract': False,
178
- },
179
- ),
180
205
  migrations.CreateModel(
181
206
  name='M2O',
182
207
  fields=[
@@ -200,6 +225,9 @@ class Migration(migrations.Migration):
200
225
  ('create_time', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
201
226
  ('modify_time', models.DateTimeField(auto_now=True, null=True, verbose_name='修改时间')),
202
227
  ('saved', models.BooleanField(default=False)),
228
+ ('pid', models.IntegerField(default=0, verbose_name='父节点')),
229
+ ('isLeaf', models.BooleanField(default=False, verbose_name='叶子节点')),
230
+ ('icon', models.CharField(max_length=255, null=True, verbose_name='图标')),
203
231
  ('name', models.CharField(max_length=100, null=True, verbose_name='name')),
204
232
  ('valas', models.ManyToManyField(to='data.vala', verbose_name='valas')),
205
233
  ],
@@ -2,7 +2,9 @@ from django.db import models
2
2
  from django.db.models.fields.files import FieldFile
3
3
 
4
4
 
5
+
5
6
  class VModel(models.Model):
7
+ objects = models.Manager()
6
8
  sort = models.BigIntegerField(null=True, verbose_name='序号')
7
9
  name = models.CharField(max_length=50, null=True)
8
10
  create_time = models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')
@@ -69,7 +71,7 @@ class VModel(models.Model):
69
71
  elif domain in ['ManyToManyField', 'ManyToManyRel', 'ManyToOneRel']:
70
72
  accessor = prop if domain == 'ManyToManyField' else field.get_accessor_name()
71
73
  try:
72
- _set = getattr(self, accessor).all()
74
+ _set = getattr(self, accessor).all().order_by('sort')
73
75
  data[prop] = [item.id for item in _set]
74
76
  data[f'{prop}_set'] = [item.json for item in _set]
75
77
  except Exception as e:
@@ -138,22 +140,22 @@ class MetaField(VModel):
138
140
 
139
141
 
140
142
  """rest"""
141
- unit = models.CharField(max_length=55, verbose_name='单位符', null=True)
142
143
  not_null = models.BooleanField(default=False, verbose_name='不为空') #
143
- read_only = models.BooleanField(default=False, verbose_name='只读')
144
- sortable = models.BooleanField(default=False, verbose_name='可排序')
144
+ allow_edit = models.BooleanField(default=True, verbose_name='可编辑')
145
+ allow_sort = models.BooleanField(default=True, verbose_name='可排序')
145
146
  allow_search = models.BooleanField(default=True, verbose_name='可搜索')
146
147
  allow_download = models.BooleanField(default=True, verbose_name='可下载')
147
148
  allow_upload = models.BooleanField(default=False, verbose_name='可上传')
148
149
  allow_update = models.BooleanField(default=False, verbose_name='可更新')
149
150
 
150
151
  """table"""
152
+ unit = models.CharField(max_length=55, verbose_name='单位符', null=True)
151
153
  column_width = models.FloatField(default=0, verbose_name='表头宽度')
152
154
  align = models.CharField(max_length=55, default='left', verbose_name='对齐方式') #
153
155
  fixed = models.CharField(max_length=100, verbose_name='固定位置', null=True)
154
156
  header_color = models.CharField(max_length=55, verbose_name='表头颜色', null=True)
155
157
  cell_color = models.CharField(max_length=55, verbose_name='单元颜色', null=True)
156
- edit_on_table = models.BooleanField(default=False, verbose_name='表格编辑')
158
+ edit_on_table = models.BooleanField(default=True, verbose_name='表格编辑')
157
159
  hide_on_table = models.BooleanField(default=False, verbose_name='表内隐藏')
158
160
 
159
161
  """form"""
@@ -164,6 +166,7 @@ class MetaField(VModel):
164
166
  hide_on_form_branch = models.BooleanField(default=False, verbose_name='分支隐藏')
165
167
  hide_on_form_leaf = models.BooleanField(default=False, verbose_name='叶子隐藏')
166
168
 
169
+
167
170
  class Meta:
168
171
  verbose_name = '视图字段'
169
172
 
@@ -178,21 +181,31 @@ class MetaFieldTool(VTree):
178
181
  verbose_name = '元数据字段工具'
179
182
  #
180
183
  #
184
+ class TestM(models.Model):
185
+ name = models.CharField(max_length=100, verbose_name='<UNK>')
186
+
181
187
  class MetaFieldDomain(VModel):
182
188
  name = models.CharField(max_length=255, unique=True, null=True, verbose_name='名称')
183
189
  tools = models.ManyToManyField(to=MetaFieldTool, verbose_name='工具集')
184
- default = models.ForeignKey(to=MetaFieldTool, null=True, on_delete=models.SET_NULL, related_name='+',
185
- verbose_name='默认工具')
186
- search = models.ForeignKey(to=MetaFieldTool, null=True, on_delete=models.SET_NULL, related_name='+',
187
- verbose_name='搜索工具')
190
+ default = models.ForeignKey(
191
+ to=MetaFieldTool, null=True,
192
+ on_delete=models.SET_NULL,
193
+ related_name='+',
194
+ verbose_name='默认工具')
195
+ search = models.ForeignKey(
196
+ to=MetaFieldTool, null=True,
197
+ on_delete=models.SET_NULL,
198
+ related_name='+',
199
+ verbose_name='搜索工具')
188
200
  align = models.CharField(max_length=10, null=True, verbose_name='对齐方式')
189
-
190
201
  class Meta:
191
202
  verbose_name = '元数据字段类型'
192
203
 
193
204
 
194
-
195
-
205
+ class ValaTree(VTree):
206
+ text = models.TextField(null=True, verbose_name='text')
207
+ class Meta:
208
+ verbose_name = '树形测试'
196
209
 
197
210
  class Vala(VModel):
198
211
  text_field = models.TextField(null=True, verbose_name='text')
@@ -218,6 +231,6 @@ class O2O(VModel):
218
231
  name = models.CharField(max_length=100, null=True, verbose_name='name')
219
232
 
220
233
 
221
- class M2M(VModel):
234
+ class M2M(VTree):
222
235
  valas = models.ManyToManyField(to=Vala, verbose_name='valas')
223
236
  name = models.CharField(max_length=100, null=True, verbose_name='name')
@@ -1,12 +1,14 @@
1
1
  from bson import ObjectId
2
- from django.db.models import QuerySet
3
- from pymongo.results import InsertOneResult, UpdateResult
2
+ from pymongo.results import InsertOneResult
4
3
 
5
4
  from django.conf import settings
6
5
  import pymongo
7
6
 
8
7
  from ..query import Query
9
8
 
9
+ from pymongo import MongoClient
10
+ from typing import Dict, Any, List, Optional
11
+
10
12
  try:
11
13
  MONGO = settings.MONGO_SETTINGS
12
14
  except AttributeError:
@@ -71,14 +73,51 @@ class MongoDao:
71
73
  def find_one(self, _id):
72
74
  return self.collection.find_one({'_id': ObjectId(_id)})
73
75
 
74
- def find_many(self, query: Query, size=0, page=1) -> [QuerySet, int]:
76
+ def find_many(self, query: Query, size=0, page=1) :
75
77
  skip = (page - 1) * size
76
78
  condition = query.mon_conditions()
77
79
  total = self.collection.count_documents(condition)
78
- cursor = self.collection.find(condition).sort(query.orders).skip(skip).limit(size)
79
- return [cursor, total]
80
+ cursor = self.collection.find(condition)
81
+
82
+ sort_fields = []
83
+ for key, value in query.orders.items():
84
+ sort_direction = pymongo.DESCENDING if value == -1 else pymongo.ASCENDING
85
+ sort_fields.append((key, sort_direction))
80
86
 
87
+ if sort_fields:
88
+ cursor = cursor.sort(sort_fields)
89
+
90
+ if size:
91
+ cursor = cursor.skip(skip).limit(size)
92
+
93
+ return cursor, total
81
94
 
82
95
  def meta(self):
83
96
  one = self.collection.find_one()
84
97
  return one
98
+
99
+ class MongoDBQuery:
100
+ #Mongo的查询
101
+ def __init__(self, connection_string: str, database_name: str):
102
+ self.client = MongoClient(connection_string)
103
+ self.db = self.client[database_name]
104
+
105
+ def find(self, collection: str, query: Dict[str, Any],
106
+ projection: Optional[Dict[str, Any]] = None,
107
+ sort: Optional[List[tuple]] = None,
108
+ limit: Optional[int] = None,
109
+ skip: Optional[int] = None) -> List[Dict[str, Any]]:
110
+ cursor = self.db[collection].find(query, projection)
111
+ if sort:
112
+ cursor = cursor.sort(sort) #好像有警告……但是能跑
113
+ if skip:
114
+ cursor = cursor.skip(skip)
115
+ if limit:
116
+ cursor = cursor.limit(limit)
117
+ return list(cursor)
118
+
119
+ def count(self, collection: str, query: Dict[str, Any]) -> int:
120
+ return self.db[collection].count_documents(query)
121
+
122
+ def close(self):
123
+ self.client.close()
@@ -0,0 +1,91 @@
1
+ class MongoQueryTranslator:
2
+ # 将Django ORM查询操作符映射到MongoDB操作符的字典
3
+ MONGO_OPERATORS = {
4
+ 'exact': lambda v, e: {'$ne': v} if e else v,
5
+ 'iexact': lambda v, e: {'$not': {'$regex': f'^{v}$', '$options': 'i'}} if e else {'$regex': f'^{v}$', '$options': 'i'},
6
+ 'contains': lambda v, e: {'$not': {'$regex': f'{v}'}} if e else {'$regex': f'{v}'},
7
+ 'icontains': lambda v, e: {'$not': {'$regex': f'{v}', '$options': 'i'}} if e else {'$regex': f'{v}', '$options': 'i'},
8
+ 'startswith': lambda v, e: {'$not': {'$regex': f'^{v}'}} if e else {'$regex': f'^{v}'},
9
+ 'istartswith': lambda v, e: {'$not': {'$regex': f'^{v}', '$options': 'i'}} if e else {'$regex': f'^{v}', '$options': 'i'},
10
+ 'endswith': lambda v, e: {'$not': {'$regex': f'{v}$'}} if e else {'$regex': f'{v}$'},
11
+ 'iendswith': lambda v, e: {'$not': {'$regex': f'{v}$', '$options': 'i'}} if e else {'$regex': f'{v}$', '$options': 'i'},
12
+ 'gt': lambda v, e: {'$not': {'$gt': v}} if e else {'$gt': v},
13
+ 'gte': lambda v, e: {'$not': {'$gte': v}} if e else {'$gte': v},
14
+ 'lt': lambda v, e: {'$not': {'$lt': v}} if e else {'$lt': v},
15
+ 'lte': lambda v, e: {'$not': {'$lte': v}} if e else {'$lte': v},
16
+ 'in': lambda v, e: {'$nin': v} if e else {'$in': v},
17
+ 'exists': lambda v, e: {'$eq': None} if (e and bool(v)) or (not e and not bool(v)) else {'$ne': None},
18
+ 'isnull': lambda v, e: {'$ne': None} if e else {'$eq': None},
19
+ }
20
+
21
+ @classmethod
22
+ def process_field_query(cls, key, value, is_exclude=False):
23
+ """处理单个字段查询"""
24
+ if '__' in key:
25
+ field, op = key.split('__', 1)
26
+ if op in cls.MONGO_OPERATORS:
27
+ return field, cls.MONGO_OPERATORS[op](value, is_exclude)
28
+ return key, {'$ne': value} if is_exclude else value
29
+
30
+ @classmethod
31
+ def process_condition(cls, condition_dict, is_exclude=False):
32
+ """处理条件字典,返回MongoDB查询条件"""
33
+ return {field: query for field, query in
34
+ [cls.process_field_query(k, v, is_exclude) for k, v in condition_dict.items()]}
35
+
36
+ @classmethod
37
+ def process_finder(cls, finder):
38
+ """处理finder字段,转换为MongoDB查询条件"""
39
+ if not finder:
40
+ return {}
41
+ mongo_query = {}
42
+ if 'term' in finder and finder['term'] and isinstance(finder['term'], str):
43
+ term = finder['term']
44
+ fields = finder.get('fields', [])
45
+ if fields:
46
+ mongo_query['$or'] = [{field: {'$regex': term, '$options': 'i'}} for field in fields]
47
+ else:
48
+ mongo_query['$text'] = {'$search': term}
49
+ if 'range' in finder:
50
+ for field, ranges in finder['range'].items():
51
+ field_query = {f'${op}': val for op, val in ranges.items() if op in ['gt', 'gte', 'lt', 'lte']}
52
+ if field_query:
53
+ mongo_query[field] = field_query
54
+ for query_type, operator in [('match', None), ('exists', '$ne')]:
55
+ if query_type in finder:
56
+ for field, value in finder[query_type].items():
57
+ if operator == '$ne':
58
+ # 处理exists查询,检查字段是否为空
59
+ mongo_query[field] = {'$ne': None} if bool(value) else {'$eq': None}
60
+ else:
61
+ mongo_query[field] = value
62
+ return mongo_query
63
+
64
+ @classmethod
65
+ def translate_query(cls, query_obj):
66
+ """
67
+ 将Query对象转换为MongoDB查询
68
+ Args:
69
+ query_obj: Query对象,包含condition, search和finder
70
+ Returns:
71
+ 转换后的MongoDB查询条件
72
+ """
73
+ query = {}
74
+ query.update(cls.process_condition(query_obj.condition.includes))
75
+ query.update(cls.process_condition(query_obj.condition.excludes, True))
76
+ # 处理搜索条件
77
+ if query_obj.search:
78
+ or_conditions = []
79
+ for sea in query_obj.search:
80
+ search_query = {}
81
+ search_query.update(cls.process_condition(sea.includes))
82
+ search_query.update(cls.process_condition(sea.excludes, True))
83
+ if search_query:
84
+ or_conditions.append(search_query)
85
+ if or_conditions:
86
+ query["$or"] = or_conditions
87
+ # 处理和合并finder条件
88
+ finder_query = cls.process_finder(query_obj.finder)
89
+ if finder_query and query:
90
+ return {"$and": [query, finder_query]}
91
+ return finder_query or query