xn-model 0.10.3.dev1__tar.gz → 0.11.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.
@@ -2,4 +2,5 @@ POSTGRES_USER=user
2
2
  POSTGRES_PASSWORD=password
3
3
  POSTGRES_DB=bd_name
4
4
  POSTGRES_HOST=127.0.0.1
5
- POSTGRES_PORT=5432
5
+ POSTGRES_PORT=5432
6
+ VENV=venv
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xn-model
3
- Version: 0.10.3.dev1
3
+ Version: 0.11.0
4
4
  Summary: Base model for xn-api
5
5
  Author-email: Mike Artemiev <mixartemev@gmail.com>
6
6
  License: MIT
@@ -10,8 +10,6 @@ Keywords: tortoise,model,crud,generator,api,admin
10
10
  Requires-Python: >=3.12
11
11
  Description-Content-Type: text/markdown
12
12
  Requires-Dist: tortoise-orm[accel,asyncpg]
13
- Requires-Dist: passlib[bcrypt]
14
- Requires-Dist: pydantic
15
13
  Requires-Dist: python-dotenv
16
14
  Provides-Extra: dev
17
15
  Requires-Dist: pytest; extra == "dev"
@@ -1,5 +1,5 @@
1
+ include .env
1
2
  PACKAGE := x_model
2
- VENV := venv
3
3
  VPYTHON := . $(VENV)/bin/activate && python
4
4
 
5
5
  .PHONY: all install pre-commit test clean build twine patch
@@ -8,20 +8,20 @@ all:
8
8
  make install test clean build
9
9
 
10
10
  install: $(VENV)
11
- $(VPYTHON) -m pip install -e .[dev]; make pre-commit
11
+ $(VPYTHON) -m pip install .[dev]; make pre-commit
12
12
  pre-commit: .pre-commit-config.yaml
13
13
  pre-commit install -t pre-commit -t post-commit -t pre-push
14
14
 
15
- test:
15
+ test: $(VENV)
16
16
  $(VPYTHON) -m pytest
17
17
 
18
18
  clean: .pytest_cache dist $(PACKAGE).egg-info
19
19
  rm -rf .pytest_cache dist/* $(PACKAGE).egg-info $(PACKAGE)/__pycache__ dist/__pycache__
20
20
 
21
- build:
21
+ build: $(VENV)
22
22
  $(VPYTHON) -m build; make twine
23
- twine: dist
23
+ twine: $(VENV) dist
24
24
  $(VPYTHON) -m twine upload dist/* --skip-existing
25
25
 
26
- patch:
26
+ patch: $(VENV)
27
27
  git tag `$(VPYTHON) -m setuptools_scm --strip-dev`; git push --tags --prune -f
@@ -6,8 +6,6 @@ authors = [
6
6
  ]
7
7
  dependencies = [
8
8
  'tortoise-orm[accel,asyncpg]',
9
- 'passlib[bcrypt]',
10
- 'pydantic',
11
9
  "python-dotenv"
12
10
  ]
13
11
  keywords = ["tortoise", "model", "crud", "generator", "api", "admin"]
@@ -3,25 +3,6 @@ from tortoise import Tortoise, connections, ConfigurationError
3
3
  from tortoise.backends.asyncpg import AsyncpgDBClient
4
4
  from tortoise.exceptions import DBConnectionError
5
5
 
6
- from .enum import FieldType
7
- from .field import PointField, RangeField, PolygonField, CollectionField, ListField, DatetimeSecField
8
- from .model import Model, TsModel, User
9
- from .func import Array
10
-
11
- __all__ = [
12
- "FieldType",
13
- "PointField",
14
- "RangeField",
15
- "PolygonField",
16
- "CollectionField",
17
- "ListField",
18
- "DatetimeSecField",
19
- "Model",
20
- "TsModel",
21
- "User",
22
- "Array",
23
- ]
24
-
25
6
 
26
7
  async def init_db(dsn: str, models: ModuleType, create_tables: bool = False) -> AsyncpgDBClient | str:
27
8
  try:
@@ -0,0 +1,172 @@
1
+ from datetime import datetime
2
+ from pydantic import create_model
3
+ from tortoise import Model as TortoiseModel
4
+ from tortoise.contrib.pydantic import pydantic_model_creator, PydanticModel
5
+ from tortoise import fields
6
+ from tortoise.models import MetaInfo
7
+ from tortoise.queryset import QuerySet
8
+
9
+ from x_model.field import DatetimeSecField
10
+ from x_model.pydantic import PydList
11
+
12
+
13
+ class BaseModel(TortoiseModel):
14
+ # todo: resolve ownable + add only own list method
15
+ # todo: refact: clean old garbage
16
+ id: int = fields.IntField(True)
17
+
18
+ _name: tuple[str] = {"name"}
19
+ _sorts: tuple[str] = ["-id"]
20
+
21
+ def repr(self, sep: str = " ") -> str:
22
+ if self._name in self._meta.db_fields:
23
+ return sep.join(getattr(self, name_fragment) for name_fragment in self._name)
24
+ return self.__repr__()
25
+
26
+ @classmethod
27
+ async def get_or_create_by_name(cls, name: str, attr_name: str = None, def_dict: dict = None) -> TortoiseModel:
28
+ attr_name = attr_name or list(cls._name)[0]
29
+ if not (obj := await cls.get_or_none(**{attr_name: name})):
30
+ next_id = (await cls.all().order_by("-id").first()).id + 1
31
+ obj = await cls.create(id=next_id, **{attr_name: name}, **(def_dict or {}))
32
+ return obj
33
+
34
+ @classmethod
35
+ def _page_query(cls, sorts: tuple[str], limit: int = 1000, offset: int = 0, q: str = None, **filters) -> QuerySet:
36
+ query = cls.filter(**filters).order_by(*sorts).limit(limit).offset(offset)
37
+ if q:
38
+ query = query.filter(**{f"{cls._name}__icontains": q})
39
+ return query
40
+
41
+ @classmethod
42
+ async def upsert(cls, data: dict, oid=None):
43
+ meta: MetaInfo = cls._meta
44
+
45
+ # pop fields for relations from general data dict # todo: add backwards fields for save
46
+ m2ms = {k: data.pop(k) for k in meta.m2m_fields if k in data}
47
+ # bfks = {k: data.pop(k) for k in meta.backward_fk_fields if k in data}
48
+ # bo2os = {k: data.pop(k) for k in meta.backward_o2o_fields if k in data}
49
+
50
+ # save general model
51
+ # if pk := meta.pk_attr in data.keys():
52
+ # unq = {pk: data.pop(pk)}
53
+ # else:
54
+ # unq = {key: data.pop(key) for key, ft in meta.fields_map.items() if ft.unique and key in data.keys()}
55
+ # # unq = meta.unique_together
56
+ # obj, is_created = await cls.update_or_create(data, **unq)
57
+ obj = (await cls.update_or_create(data, id=oid))[0] if oid else await cls.create(**data)
58
+
59
+ # save relations
60
+ for k, ids in m2ms.items():
61
+ if ids:
62
+ m2m_rel: fields.ManyToManyRelation = getattr(obj, k)
63
+ items = [await m2m_rel.remote_model[i] for i in ids]
64
+ await m2m_rel.clear() # for updating, not just adding
65
+ await m2m_rel.add(*items)
66
+ # for k, ids in bfks.items():
67
+ # bfk_rel: ReverseRelation = getattr(obj, k)
68
+ # items = [await bfk_rel.remote_model[i] for i in ids]
69
+ # [await item.update_from_dict({bfk_rel.relation_field: obj.pk}).save() for item in items]
70
+ # for k, oid in bo2os.items():
71
+ # bo2o_rel: QuerySet = getattr(obj, k)
72
+ # item = await bo2o_rel.model[oid]
73
+ # await item.update_from_dict({obj._meta.db_table: obj}).save()
74
+
75
+ await obj.fetch_related(*cls._meta.fetch_fields)
76
+ return obj
77
+
78
+ class Meta:
79
+ abstract = True
80
+
81
+
82
+ class Model(BaseModel):
83
+ _pyd: type[PydanticModel] = None
84
+ _pydIn: type[PydanticModel] = None
85
+ _pydListItem: type[PydanticModel] = None
86
+
87
+ class PydanticMeta:
88
+ # include: tuple[str, ...] = ()
89
+ # exclude: tuple[str, ...] = ()
90
+ # computed: tuple[str, ...] = ()
91
+ exclude_raw_fields = False # default: True
92
+ max_recursion: int = 1 # default: 3
93
+
94
+ class PydanticMetaIn:
95
+ max_recursion: int = 0 # default: 3
96
+ backward_relations: bool = False # no need to disable when max_recursion=0 # default: True
97
+ exclude_raw_fields: bool = False # default: True
98
+
99
+ class PydanticMetaListItem:
100
+ max_recursion: int = 0 # default: 3
101
+ backward_relations: bool = False # default: True
102
+ exclude_raw_fields = False # default: True
103
+ sort_alphabetically: bool = True # default: False
104
+
105
+ @classmethod
106
+ def pyd(cls) -> type[PydanticModel]:
107
+ cls._pyd = cls._pyd or pydantic_model_creator(cls)
108
+ return cls._pyd
109
+
110
+ @classmethod
111
+ def pyd_in(cls) -> type[PydanticModel]:
112
+ if not cls._pydIn:
113
+ opts = tuple(k for k, v in cls._meta.fields_map.items() if not v.required)
114
+ cls._pydIn = pydantic_model_creator(
115
+ cls,
116
+ name=cls.__name__ + "In",
117
+ meta_override=cls.PydanticMetaIn,
118
+ optional=opts,
119
+ exclude_readonly=True,
120
+ exclude=("created_at", "updated_at"),
121
+ )
122
+ if m2ms := cls._meta.m2m_fields: # hack for direct inserting m2m values
123
+ cls._pydIn = create_model(
124
+ cls._pydIn.__name__, __base__=cls._pydIn, **{m2m: (list[int] | None, None) for m2m in m2ms}
125
+ )
126
+ return cls._pydIn
127
+
128
+ @classmethod
129
+ def pyd_list_item(cls) -> type[PydanticModel]:
130
+ if not cls._pydListItem:
131
+ cls._pydListItem = pydantic_model_creator(
132
+ cls, name=cls.__name__ + "ListItem", meta_override=cls.PydanticMetaListItem
133
+ )
134
+ return cls._pydListItem
135
+
136
+ @classmethod
137
+ def pyds_list(cls) -> type[PydList]:
138
+ return create_model(
139
+ cls.__name__ + "List",
140
+ data=(list[cls.pyd_list_item()], []),
141
+ total=(int, 0),
142
+ filtered=(int | None, None),
143
+ __base__=PydList[cls.pyd_list_item()],
144
+ )
145
+
146
+ # # # CRUD Methods # # #
147
+ @classmethod
148
+ async def one_pyd(cls, uid: int, **filters) -> PydanticModel:
149
+ q = cls.get(id=uid, **filters)
150
+ return await cls.pyd().from_queryset_single(q)
151
+
152
+ @classmethod
153
+ async def page_pyd(cls, sorts: tuple[str], limit: int = 1000, offset: int = 0, q: str = None, **filters) -> PydList:
154
+ filters = {k: v for k, v in filters.items() if v is not None}
155
+ pyd_item = cls.pyd_list_item()
156
+ query = cls._page_query(sorts, limit, offset, q, **filters)
157
+ data = await pyd_item.from_queryset(query)
158
+ if limit - (li := len(data)):
159
+ filtered = total = li + offset
160
+ else:
161
+ total = await cls.all().count()
162
+ filtered_query = cls.filter(**filters)
163
+ if q:
164
+ filtered_query = filtered_query.filter(**{f"{cls._name}__icontains": q})
165
+ filtered = await filtered_query.count()
166
+ pyds = cls.pyds_list()
167
+ return pyds(data=data, total=total, filtered=filtered)
168
+
169
+
170
+ class TsTrait:
171
+ created_at: datetime | None = DatetimeSecField(auto_now_add=True)
172
+ updated_at: datetime | None = DatetimeSecField(auto_now=True)
@@ -1,8 +1,6 @@
1
1
  from typing import TypeVar, Generic
2
2
  from pydantic import BaseModel, ConfigDict
3
3
 
4
- from x_model.enum import UserStatus, UserRole
5
-
6
4
 
7
5
  RootModelType = TypeVar("RootModelType")
8
6
 
@@ -13,32 +11,6 @@ class PydList(BaseModel, Generic[RootModelType]):
13
11
  total: int
14
12
 
15
13
 
16
- class UserPwd(BaseModel):
17
- password: str
18
-
19
-
20
- class UserReg(UserPwd):
21
- username: str
22
- email: str | None = None
23
- phone: int | None = None
24
-
25
-
26
- class UserUpdate(BaseModel):
27
- username: str
28
- status: UserStatus
29
- email: str | None
30
- phone: int | None
31
- role: UserRole
32
-
33
-
34
- class UserAuth(UserUpdate):
35
- id: int
36
- username: str
37
- status: UserStatus
38
- role: UserRole
39
- # ref_id: int | None
40
-
41
-
42
14
  class Names(BaseModel):
43
15
  # models for name endpoint for select2 inputs
44
16
  class Name(BaseModel):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xn-model
3
- Version: 0.10.3.dev1
3
+ Version: 0.11.0
4
4
  Summary: Base model for xn-api
5
5
  Author-email: Mike Artemiev <mixartemev@gmail.com>
6
6
  License: MIT
@@ -10,8 +10,6 @@ Keywords: tortoise,model,crud,generator,api,admin
10
10
  Requires-Python: >=3.12
11
11
  Description-Content-Type: text/markdown
12
12
  Requires-Dist: tortoise-orm[accel,asyncpg]
13
- Requires-Dist: passlib[bcrypt]
14
- Requires-Dist: pydantic
15
13
  Requires-Dist: python-dotenv
16
14
  Provides-Extra: dev
17
15
  Requires-Dist: pytest; extra == "dev"
@@ -7,7 +7,6 @@ pyproject.toml
7
7
  tests/__init__.py
8
8
  tests/test_db.py
9
9
  x_model/__init__.py
10
- x_model/enum.py
11
10
  x_model/field.py
12
11
  x_model/func.py
13
12
  x_model/model.py
@@ -1,6 +1,4 @@
1
1
  tortoise-orm[accel,asyncpg]
2
- passlib[bcrypt]
3
- pydantic
4
2
  python-dotenv
5
3
 
6
4
  [dev]
@@ -1,31 +0,0 @@
1
- from enum import IntEnum
2
-
3
-
4
- class FieldType(IntEnum):
5
- input = 1
6
- checkbox = 2
7
- select = 3
8
- textarea = 4
9
- collection = 5
10
- list = 6
11
-
12
-
13
- class UserStatus(IntEnum):
14
- BANNED = 0
15
- WAIT = 1 # waiting for approve
16
- TEST = 2 # trial
17
- ACTIVE = 3
18
- PREMIUM = 4
19
-
20
-
21
- class Scope(IntEnum):
22
- READ = 4
23
- WRITE = 2
24
- ALL = 1 # not only my
25
-
26
-
27
- class UserRole(IntEnum):
28
- CLIENT = Scope.READ # 4
29
- AUTHOR = Scope.WRITE # 2
30
- MANAGER = Scope.READ + Scope.WRITE # 6
31
- ADMIN = Scope.READ + Scope.WRITE + Scope.ALL # 7
@@ -1,330 +0,0 @@
1
- from datetime import datetime
2
- from passlib.context import CryptContext
3
- from pydantic import create_model
4
- from tortoise import Model as BaseModel
5
- from tortoise.contrib.postgres.fields import ArrayField
6
- from tortoise.contrib.pydantic import pydantic_model_creator, PydanticModel
7
- from tortoise.fields import (
8
- Field,
9
- CharField,
10
- IntField,
11
- SmallIntField,
12
- BigIntField,
13
- DecimalField,
14
- FloatField,
15
- TextField,
16
- BooleanField,
17
- DatetimeField,
18
- DateField,
19
- TimeField,
20
- JSONField,
21
- ForeignKeyRelation,
22
- OneToOneRelation,
23
- ManyToManyRelation,
24
- ForeignKeyNullableRelation,
25
- OneToOneNullableRelation,
26
- IntEnumField,
27
- )
28
- from tortoise.fields.data import IntEnumFieldInstance, CharEnumFieldInstance
29
- from tortoise.fields.relational import (
30
- BackwardFKRelation,
31
- ForeignKeyFieldInstance,
32
- ManyToManyFieldInstance,
33
- OneToOneFieldInstance,
34
- BackwardOneToOneRelation,
35
- RelationalField,
36
- )
37
- from tortoise.models import MetaInfo
38
- from tortoise.queryset import QuerySet
39
-
40
- from x_model import FieldType, PointField, PolygonField, RangeField
41
- from x_model.enum import UserStatus, UserRole
42
- from x_model.field import DatetimeSecField, SetField
43
- from x_model.pydantic import PydList
44
-
45
-
46
- class Model(BaseModel):
47
- id: int = IntField(pk=True)
48
- _name: set[str] = {"name"}
49
- _icon: str = "" # https://unpkg.com/@tabler/icons@2.30.0/icons/icon_name.svg
50
- _sorts: list[str] = ["-id"]
51
- _ownable_fields: dict[str, str | None] = {"one": None, "list": None, "in": None}
52
- _pydIn: type[PydanticModel] = None
53
- _pyd: type[PydanticModel] = None
54
- _pydListItem: type[PydanticModel] = None
55
- _permissions: tuple[bool, bool, bool] = True, True, True
56
-
57
- @classmethod
58
- def cols(cls) -> list[dict]:
59
- meta = cls._meta
60
- return [
61
- {"data": c, "orderable": c not in meta.fetch_fields or c in meta.fk_fields}
62
- for c in meta.fields_map
63
- if not c.endswith("_id")
64
- ]
65
-
66
- @classmethod
67
- def pyd(cls) -> type[PydanticModel]:
68
- cls._pyd = cls._pyd or pydantic_model_creator(cls)
69
- return cls._pyd
70
-
71
- @classmethod
72
- def pydIn(cls) -> type[PydanticModel]:
73
- if not cls._pydIn:
74
- opts = tuple(k for k, v in cls._meta.fields_map.items() if not v.required)
75
- cls._pydIn = pydantic_model_creator(
76
- cls,
77
- name=cls.__name__ + "In",
78
- meta_override=cls.PydanticMetaIn,
79
- optional=opts,
80
- exclude_readonly=True,
81
- exclude=("created_at", "updated_at"),
82
- )
83
- if m2ms := cls._meta.m2m_fields: # hack for direct inserting m2m values
84
- cls._pydIn = create_model(
85
- cls._pydIn.__name__, __base__=cls._pydIn, **{m2m: (list[int] | None, None) for m2m in m2ms}
86
- )
87
- return cls._pydIn
88
-
89
- @classmethod
90
- def pydListItem(cls) -> type[PydanticModel]:
91
- if not cls._pydListItem:
92
- cls._pydListItem = pydantic_model_creator(
93
- cls, name=cls.__name__ + "ListItem", meta_override=cls.PydanticMetaListItem
94
- )
95
- return cls._pydListItem
96
-
97
- @classmethod
98
- def pydsList(cls) -> type[PydList]:
99
- return create_model(
100
- cls.__name__ + "List",
101
- data=(list[cls.pydListItem()], []),
102
- total=(int, 0),
103
- filtered=(int | None, None),
104
- __base__=PydList[cls.pydListItem()],
105
- )
106
-
107
- @classmethod
108
- async def one(cls, uid: int, owner: int = None, **kwargs) -> PydanticModel:
109
- if owner and (of := cls._ownable_fields.get("one")):
110
- kwargs.update({of: owner})
111
- q = cls.get(id=uid, **kwargs)
112
- return await cls.pyd().from_queryset_single(q)
113
-
114
- @classmethod
115
- def pageQuery(
116
- cls, sorts: list[str], limit: int = 1000, offset: int = 0, q: str = None, owner: int = None, **kwargs
117
- ) -> QuerySet:
118
- rels, keys = [], ["id"]
119
- for nam in cls._name:
120
- parts = nam.split("__")
121
- if len(parts) > 1:
122
- rels.append("__".join(parts[:-1]))
123
- keys.append(nam)
124
- query = (
125
- cls.filter(**kwargs)
126
- .order_by(*sorts)
127
- .limit(limit)
128
- .offset(offset)
129
- .prefetch_related(*(cls._meta.fetch_fields & set(kwargs)), *rels)
130
- )
131
- if q:
132
- query = query.filter(**{f"{cls._name}__icontains": q})
133
- if owner and (of := cls._ownable_fields.get("list")):
134
- query = query.filter(**{of: owner})
135
- return query
136
-
137
- @classmethod
138
- async def pagePyd(
139
- cls, sorts: list[str], limit: int = 1000, offset: int = 0, q: str = None, owner: int = None, **kwargs
140
- ) -> PydList:
141
- kwargs = {k: v for k, v in kwargs.items() if v is not None}
142
- pyd = cls.pydListItem()
143
- query = cls.pageQuery(sorts, limit, offset, q, owner, **kwargs)
144
- await query
145
- data = await pyd.from_queryset(query)
146
- if limit - (li := len(data)):
147
- filtered = total = li + offset
148
- else:
149
- total = await cls.all().count()
150
- filtered_query = cls.filter(**kwargs)
151
- if q:
152
- filtered_query = filtered_query.filter(**{f"{cls._name}__icontains": q})
153
- filtered = await filtered_query.count()
154
- pyds = cls.pydsList()
155
- return pyds(data=data, total=total, filtered=filtered)
156
-
157
- def repr(self) -> str:
158
- if self._name in self._meta.db_fields:
159
- return " ".join(getattr(self, name_fragment) for name_fragment in self._name)
160
- return self.__repr__()
161
-
162
- @classmethod
163
- async def getOrCreateByName(cls, name: str, attr_name: str = None, def_dict: dict = None) -> BaseModel:
164
- attr_name = attr_name or list(cls._name)[0]
165
- if not (obj := await cls.get_or_none(**{attr_name: name})):
166
- next_id = (await cls.all().order_by("-id").first()).id + 1
167
- obj = await cls.create(id=next_id, **{attr_name: name}, **(def_dict or {}))
168
- return obj
169
-
170
- @classmethod
171
- async def upsert(cls, data: dict, oid=None):
172
- meta: MetaInfo = cls._meta
173
-
174
- # pop fields for relations from general data dict
175
- m2ms = {k: data.pop(k) for k in meta.m2m_fields if k in data}
176
- # bfks = {k: data.pop(k) for k in meta.backward_fk_fields if k in data}
177
- # bo2os = {k: data.pop(k) for k in meta.backward_o2o_fields if k in data}
178
-
179
- # save general model
180
- # if pk := meta.pk_attr in data.keys():
181
- # unq = {pk: data.pop(pk)}
182
- # else:
183
- # unq = {key: data.pop(key) for key, ft in meta.fields_map.items() if ft.unique and key in data.keys()}
184
- # # unq = meta.unique_together
185
- # obj, is_created = await cls.update_or_create(data, **unq)
186
- obj = (await cls.update_or_create(data, **{meta.pk_attr: oid}))[0] if oid else await cls.create(**data)
187
-
188
- # save relations
189
- for k, ids in m2ms.items():
190
- if ids:
191
- m2m_rel: ManyToManyRelation = getattr(obj, k)
192
- items = [await m2m_rel.remote_model[i] for i in ids]
193
- await m2m_rel.clear() # for updating, not just adding
194
- await m2m_rel.add(*items)
195
- # for k, ids in bfks.items():
196
- # bfk_rel: ReverseRelation = getattr(obj, k)
197
- # items = [await bfk_rel.remote_model[i] for i in ids]
198
- # [await item.update_from_dict({bfk_rel.relation_field: obj.pk}).save() for item in items]
199
- # for k, oid in bo2os.items():
200
- # bo2o_rel: QuerySet = getattr(obj, k)
201
- # item = await bo2o_rel.model[oid]
202
- # await item.update_from_dict({obj._meta.db_table: obj}).save()
203
-
204
- await obj.fetch_related(*cls._meta.fetch_fields)
205
- return obj
206
-
207
- @classmethod
208
- def field_input_map(cls) -> dict:
209
- def type2input(ft: type[Field]):
210
- dry = {
211
- "base_field": hasattr(ft, "base_field") and {**type2input(ft.base_field)},
212
- "step": hasattr(ft, "step") and ft.step,
213
- "labels": hasattr(ft, "labels") and ft.labels,
214
- }
215
- type2inputs: {Field: dict} = {
216
- CharField: {"input": FieldType.input.name},
217
- IntField: {"input": FieldType.input.name, "type": "number"},
218
- SmallIntField: {"input": FieldType.input.name, "type": "number"},
219
- BigIntField: {"input": FieldType.input.name, "type": "number"},
220
- DecimalField: {"input": FieldType.input.name, "type": "number", "step": "0.01"},
221
- FloatField: {"input": FieldType.input.name, "type": "number", "step": "0.001"},
222
- TextField: {"input": FieldType.textarea.name, "rows": "2"},
223
- BooleanField: {"input": FieldType.checkbox.name},
224
- DatetimeField: {"input": FieldType.input.name, "type": "datetime"},
225
- DatetimeSecField: {"input": FieldType.input.name, "type": "datetime"},
226
- DateField: {"input": FieldType.input.name, "type": "date"},
227
- TimeField: {"input": FieldType.input.name, "type": "time"},
228
- JSONField: {"input": FieldType.input.name},
229
- IntEnumFieldInstance: {"input": FieldType.select.name},
230
- CharEnumFieldInstance: {"input": FieldType.select.name},
231
- ForeignKeyFieldInstance: {"input": FieldType.select.name},
232
- OneToOneFieldInstance: {"input": FieldType.select.name},
233
- ManyToManyFieldInstance: {"input": FieldType.select.name, "multiple": True},
234
- ForeignKeyRelation: {"input": FieldType.select.name, "multiple": True},
235
- OneToOneRelation: {"input": FieldType.select.name},
236
- BackwardOneToOneRelation: {"input": FieldType.select.name},
237
- ManyToManyRelation: {"input": FieldType.select.name, "multiple": True},
238
- ForeignKeyNullableRelation: {"input": FieldType.select.name, "multiple": True},
239
- BackwardFKRelation: {"input": FieldType.select.name, "multiple": True},
240
- ArrayField: {"input": FieldType.select.name, "multiple": True},
241
- SetField: {"input": FieldType.select.name, "multiple": True},
242
- OneToOneNullableRelation: {"input": FieldType.select.name},
243
- PointField: {"input": FieldType.collection.name, **dry},
244
- PolygonField: {"input": FieldType.list.name, **dry},
245
- RangeField: {"input": FieldType.collection.name, **dry},
246
- }
247
- return type2inputs[ft]
248
-
249
- def field2input(_key: str, field: Field):
250
- attrs: dict = {"required": not field.null}
251
- if isinstance(field, CharEnumFieldInstance):
252
- attrs.update({"options": {en.name: en.value for en in field.enum_type}})
253
- elif isinstance(field, IntEnumFieldInstance) or isinstance(field, SetField):
254
- attrs.update({"options": {en.value: en.name.replace("_", " ") for en in field.enum_type}})
255
- elif isinstance(field, RelationalField):
256
- attrs.update({"source_field": field.source_field}) # 'table': attrs[key]['multiple'],
257
- elif field.generated or ("auto_now" in field.__dict__ and (field.auto_now or field.auto_now_add)): # noqa
258
- attrs.update({"auto": True})
259
- return {**type2input(type(field)), **attrs}
260
-
261
- return {key: field2input(key, field) for key, field in cls._meta.fields_map.items() if not key.endswith("_id")}
262
-
263
- class Meta:
264
- abstract = True
265
-
266
- class PydanticMeta:
267
- #: If not empty, only fields this property contains will be in the pydantic model
268
- # include: tuple[str, ...] = ()
269
- # #: Fields listed in this property will be excluded from pydantic model
270
- # exclude: tuple[str, ...] = ()
271
- # #: Computed fields can be listed here to use in pydantic model
272
- # computed: tuple[str, ...] = ()
273
-
274
- exclude_raw_fields = False # default: True
275
- max_recursion: int = 1 # default: 3
276
-
277
- class PydanticMetaIn:
278
- max_recursion: int = 0 # default: 3
279
- backward_relations: bool = False # no need to disable when max_recursion=0 # default: True
280
- exclude_raw_fields: bool = False # default: True
281
-
282
- class PydanticMetaListItem:
283
- max_recursion: int = 0 # default: 3
284
- backward_relations: bool = False # default: True
285
- exclude_raw_fields = False # default: True
286
- sort_alphabetically: bool = True # default: False
287
-
288
-
289
- class TsModel(Model):
290
- created_at: datetime | None = DatetimeSecField(auto_now_add=True)
291
- updated_at: datetime | None = DatetimeSecField(auto_now=True)
292
-
293
- class Meta:
294
- abstract = True
295
-
296
-
297
- class User(TsModel):
298
- status: UserStatus = IntEnumField(UserStatus, default=UserStatus.WAIT)
299
- username: str | None = CharField(95, unique=True, null=True)
300
- email: str | None = CharField(100, unique=True, null=True)
301
- password: str | None = CharField(60, null=True)
302
- phone: int | None = BigIntField(null=True)
303
- role: UserRole = IntEnumField(UserRole, default=UserRole.CLIENT)
304
-
305
- _icon = "user"
306
- _name = {"username"}
307
-
308
- class Meta:
309
- table_description = "Users"
310
-
311
-
312
- class UserPasswordTrait(TsModel):
313
- password: str | None = CharField(60, null=True)
314
-
315
- __cc = CryptContext(schemes=["bcrypt"])
316
-
317
- def pwd_vrf(self, pwd: str) -> bool:
318
- return self.__cc.verify(pwd, self.password)
319
-
320
- @classmethod
321
- async def create(cls, using_db=None, **kwargs) -> "User":
322
- user: "User" | Model = await super().create(using_db, **kwargs)
323
- if pwd := kwargs.get("password"):
324
- # noinspection PyUnresolvedReferences
325
- await user.set_pwd(pwd)
326
- return user
327
-
328
- async def set_pwd(self, pwd: str = password) -> None:
329
- self.password = self.__cc.hash(pwd)
330
- await self.save()
File without changes
File without changes
File without changes