Files
SMANAGE/_flask_backup/PDAPI_django.py
T
2026-06-16 11:55:14 +08:00

1181 lines
39 KiB
Python

import os
import uuid
import json
import re
import datetime
import hashlib
import logging
import threading
logger = logging.getLogger(__name__)
import django
from django.conf import settings
from django.http import HttpResponse, JsonResponse
from gvsds.apps.core.models import User, UserAvatar, EphemeralToken, User2FA, UserPending2FA, AccountActivation, UserLoginLog
from gvsds.apps.gerp.models import (
Tenant, CompanyInfo, Company, Department, Role, UserRole, RoleDept,
Permission, FileAsset, WorkOrder, Message, Menu,
UserABAC, DepartmentSMTM, Password, ItemReference, Backpack,
SimulatedTrigger, CashReceiptsPaymentsLedger, Asset, Liability,
)
GERPUser = User
GERPUser2FA = User2FA
GERPUserPending2FA = UserPending2FA
GERPRole = Role
GERPMenu = Menu
GERPMessage = Message
GERPCompany = Company
GERPDept = Department
GERPPermission = Permission
GERPUserRole = UserRole
GERPWorkOrder = WorkOrder
GERPFileAsset = FileAsset
GERPTenant = Tenant
GERPCompanyInfo = CompanyInfo
GERPCashReceiptsPaymentsLedger = CashReceiptsPaymentsLedger
GERPAsset = Asset
GERPLiability = Liability
GERPBackpack = Backpack
GERPItemReference = ItemReference
GERPWorkgroup = type('GERPWorkgroup', (), {})
from gvsds.model_base import snake_to_pascal
def _pascal_to_snake(name):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
from gvsds.template_engine import ProcessTemplate
class _FlaskCompat:
request = None
session = None
g = type('g', (), {})()
jsonify = staticmethod(lambda *args, **kwargs: kwargs)
@staticmethod
def Response(content, mimetype='text/html', status=200, headers=None):
content_type = mimetype or 'text/html'
resp = HttpResponse(content, content_type=content_type, status=status)
if headers:
for k, v in headers.items():
resp[k] = v
return resp
flask = _FlaskCompat()
UserDefaultAvatar = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xc0\x00\x00\x00\xc0\x08\x03\x00\x00\x00e\x02\x9c5\x00\x00\x003PLTE\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xf0\xf0\xf0\xe0\xe0\xe0\xc8\xc8\xc8\xb8\xb8\xb8\xc0\xc0\xc0\x8f\x8f\x8f\x90\x90\x90\x98\x98\x98\xa0\xa0\xa0\xa8\xa8\xa8\xb0\xb0\xb0\xd0\xd0\xd0\xd8\xd8\xd8\xe8\xe8\xe8\x92\x11\xe9\x17\x00\x00\x00\x02tRNS\xfe\xfd?\xa0\x8f\xaa\x00\x00\x02\x97IDATx\x01\xed\xda\x07\x92\xad:\x0c\x84\xe1y\xc8\xa1G2\xe6\xec\x7f\xb5\xef\xe6\x9c\xa0N3rU\xff+\xe0#\x83\xf5\xb2\xad\xdd\x8b\x00\x02\x08 \x80\x00\x02\x08 \x80\x00\x02\x08 @\x8e\x04\x10@\x00\x01\x04\x10@\x00\x01\x04\x10@\x00\x01\x04\x10@\x00\x01\x04\xb0R[\x04\x00\xc4\xd8kY\x0c`\xc7p|\x97\xefs\x1d\x80U\xc7/\x8af,\x00\x7f\xf3?U-?\xe0\x08\xfc\xa1\x98\xc9\x01\xd6\xf1\x97\xbae\x06X\xe0\xaf\x85\xe5\x05X\x00|\x01\x0f`\x01\xf0\x05<\x80\x05\xc0\x17\xf0\x00\x16\x00_@\x04t\x9c\xa8\xe7\x03L\x9c\xea\xc8\x06\xb0\xc0\xa9\xc2\x92\x01*N\xd6s\x01\x0cgsK\x05h8]O\x05\x080\x0f\x01\x1f0q\xa1#\x11`\xc7\x85F"\x80\xe3Bni\x00\x05\x97*i\x00\x15\x97\xaai\x00;.\xd5\xd2\x00\x06.\x15i\x00\xb1:\x00\x17c\x02\x04\x10@\x171\x1f0\xd2\x00\x1a.\xb5\xebUB/s\x9f2\xc7\x85<\xd1\xf7\xc0\xe0^\x02|\xc0\x81\x0bM*\x80\x7f\x0eE\xaa\xbf\x12\x1d\xa7k\\\x00\xff\x10X*\xc0\xd6\t\xff\xb5\xf4s\x97y#\x9aZ\xe0\xd0\x12\x13\x7f\x91O\xcb\xac$A\x98F\r4\xec\xf1\x87f\xe0\x0f\xc5A\x1c\xb7\xe1\x1f\x04\xef\xb6\xc6\xc8Y\x0b\xf2\xe6\xf3\xa7\x16\xe7\xee\xf8.\x1f\x87-67Z\xea>\x02\x00"Z-\xa6\xc1W\x01R%\x80\x00\x02\x08Pz8\x9e\x94G+7\x03f\xe0\xc9\xc5\xbc\x11`\x03\x84\x86\xdd\x05\xb0\x00\xa5\xb0{\x00\x16\x00_@\x04\x0c\xd0\x1aw\x00&\x88\xcd\x1b\x00\x01b\xc1\x07\x14P+t@\x03\xb5N\x07\x04\xa8\x05\x1d\xe0\xa0\xe6t\x00\xc8\t \x80\x00\xba\x8d\xbe)`\xac\xfe$\xde\xe9\x80\xbe\xfa\xab\xc4\x01j\x07\x1d`\xa0ft\xc06\xf8\xd70\x17PW\xff"3\x10\xb3\xc5?\xea\xdbv\x07\xa0\x80\x96Q\x01\xfcC\xd0\xee\xfa3\xe7\xa0\xf4jT\x00\xffi\xdc\xef\xfb;\x1d \x14\x1b\x13\xc0?\x89\xc2n\x04l\x0f<\xbb\xd7\xc7\xbd+4\x93\xff\x0c\xe6\x01\x08\x82\xd7y\xff"\xdf\xe1xZ^\xdeb\x95\xd2\x82\x7f\xfdR\x01\x9b5<\xa5f\x1b\x0f\xc0_\xac\xf4\x83\xb9N\xcc?\x08\xdd6>\x80G\x18\xc6_\xa9\xe7\x11\xbc\x95$\xb3\x12v\xe5Z\x88n\x99\x86=J;e\xf0^\xf2M\xab<\xeap\xfcC>\xea#\xed\xb8M\xa9\xedO\n\x8fV\x1f\x0bL\xee\x96\xd9\xdb\x18\x11\x9f(\x1e1\xf6\xd6\xe7a\x1ax\x12@\x00\x01\x04\x10@\x00\x01\x04\x10@\x00\x01\x04\x10@\x00\x01\x04\x10@\x00\x01\x04\x10@\x00\x01\x04\x10\xe0\xcd\x13@\x00\x01\x16\xef\xbf\xff\x01\xf0\x16\xb7@\x05\x81\xe7\\\x00\x00\x00\x00IEND\xaeB`\x82'
def _uuid_to_bytes(val):
if val is None:
return None
if isinstance(val, bytes):
return val
if isinstance(val, str):
try:
return uuid.UUID(val).bytes
except ValueError:
return None
if isinstance(val, uuid.UUID):
return val.bytes
return None
def _uuid_to_str(val):
if val is None:
return None
if isinstance(val, str):
return val
if isinstance(val, bytes) and len(val) == 16:
return str(uuid.UUID(bytes=val))
if isinstance(val, uuid.UUID):
return str(val)
return str(val)
class UUIDObj(str):
def __new__(cls, value):
if isinstance(value, bytes) and len(value) == 16:
uuid_str = str(uuid.UUID(bytes=value))
elif isinstance(value, str):
uuid_str = str(uuid.UUID(value))
elif isinstance(value, uuid.UUID):
uuid_str = str(value)
else:
raise ValueError(f"Invalid UUID: {value}")
return str.__new__(cls, uuid_str)
def __init__(self, value):
if isinstance(value, bytes) and len(value) == 16:
self._uuid = uuid.UUID(bytes=value)
elif isinstance(value, str):
self._uuid = uuid.UUID(value)
elif isinstance(value, uuid.UUID):
self._uuid = value
else:
raise ValueError(f"Invalid UUID: {value}")
@property
def ok(self):
return True
@property
def Bytes(self):
return self._uuid.bytes
@property
def bytes(self):
return self._uuid.bytes
@property
def LowerStr(self):
return str(self._uuid).lower()
@property
def UpperStr(self):
return str(self._uuid).upper().replace('-', '')
def NewUUID():
return uuid.uuid4()
def NewBUUID():
return uuid.uuid4().bytes
def Get16toBytes(val):
if val is None:
return None
if isinstance(val, bytes) and len(val) == 16:
return val
if isinstance(val, str):
try:
return uuid.UUID(val).bytes
except ValueError:
return None
if isinstance(val, uuid.UUID):
return val.bytes
return None
def GetBytesTo16(val):
if val is None:
return None
if isinstance(val, bytes) and len(val) == 16:
return str(uuid.UUID(bytes=val)).upper().replace('-', '')
if isinstance(val, str):
return val
if isinstance(val, uuid.UUID):
return str(val).upper().replace('-', '')
return str(val)
def GenerateUUID():
return uuid.uuid4().bytes
def openr(filepath):
try:
with open(filepath, 'r', encoding='utf-8') as f:
return f.read()
except Exception:
return ''
def openw(filepath, content):
try:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
return True
except Exception:
return False
def opena(filepath, content):
try:
with open(filepath, 'a', encoding='utf-8') as f:
f.write(content)
return True
except Exception:
return False
def JoinPath(*args):
return os.path.join(*args)
def Is_UUID(u):
try:
uuid.UUID(str(u))
return True
except (ValueError, AttributeError):
return False
def GetFileSha256(filepath):
h = hashlib.sha256()
try:
with open(filepath, 'rb') as f:
while chunk := f.read(8192):
h.update(chunk)
return h.hexdigest()
except Exception:
return None
class Cookie:
_local = threading.local()
@staticmethod
def _get_pending():
if not hasattr(Cookie._local, 'pending'):
Cookie._local.pending = []
return Cookie._local.pending
@staticmethod
def get(name, default=None):
req = _get_current_request()
if req:
return req.COOKIES.get(name, default)
return default
@staticmethod
def set(name, value, domain='.gvsds.com', max_age=30*24*60*60, path='/', httponly=False, samesite='None', secure=True):
Cookie._get_pending().append({
'name': name, 'value': value, 'domain': domain,
'max_age': max_age, 'path': path, 'httponly': httponly, 'samesite': samesite, 'secure': secure,
})
@staticmethod
def delete(name, path='/', domain='.gvsds.com'):
Cookie._get_pending().append({
'name': name, 'value': '', 'max_age': 0, 'path': path, 'domain': domain, 'secure': True, 'samesite': 'None',
})
@staticmethod
def clear():
Cookie._get_pending().clear()
@staticmethod
def _apply_to_response(response):
pending = Cookie._get_pending()
for cookie in pending:
response.set_cookie(
cookie['name'],
cookie.get('value', ''),
max_age=cookie.get('max_age', 30*24*60*60),
path=cookie.get('path', '/'),
domain=cookie.get('domain', '.gvsds.com'),
secure=cookie.get('secure', True),
httponly=cookie.get('httponly', False),
samesite=cookie.get('samesite', 'None'),
)
pending.clear()
class IP:
@staticmethod
def Get(request=None):
if request:
xff = request.META.get('HTTP_X_FORWARDED_FOR', '')
if xff:
return xff.split(',')[0].strip()
return request.META.get('REMOTE_ADDR', '')
return '127.0.0.1'
class UGM:
@staticmethod
def GetELTUser(elt=None):
try:
request = _get_current_request()
if not request:
return None
elt_token = request.COOKIES.get('ELToken')
if not elt_token:
return None
elt_uuid = _uuid_to_bytes(elt_token)
elt_obj = EphemeralToken.objects.filter(elt_uuid=elt_uuid, elt_status=1).first()
if not elt_obj or elt_obj.is_expired():
return None
user = User.objects.filter(user_uuid=elt_obj.elt_user_uuid).first()
return user
except Exception as e:
logger.exception("GetELTUser failed")
return None
@staticmethod
def GetUserDataByUserUUID(user_uuid):
try:
return User.objects.get(user_uuid=_uuid_to_bytes(user_uuid))
except User.DoesNotExist:
return None
@staticmethod
def GetUserDataByUserName(username):
try:
return User.objects.get(user_name=username)
except User.DoesNotExist:
return None
@staticmethod
def CreateELT(user, MaxTime=30, Type=0, AllowIP=None):
elt = EphemeralToken.objects.create(
elt_uuid=NewBUUID(),
elt_user_uuid=user.user_uuid if hasattr(user, 'user_uuid') else _uuid_to_bytes(user),
elt_end_time=datetime.datetime.utcnow() + datetime.timedelta(days=MaxTime),
elt_type=Type,
elt_status=1,
)
return elt
@staticmethod
def GetUserAvatar(user):
try:
user_uuid = user.user_uuid if hasattr(user, 'user_uuid') else _uuid_to_bytes(user)
return UserAvatar.objects.get(user_uuid=user_uuid)
except UserAvatar.DoesNotExist:
return None
@staticmethod
def VerifyELT(ELT=None):
try:
request = _get_current_request()
if not request:
return False
elt_token = request.COOKIES.get('ELToken')
if not elt_token:
return False
elt_uuid = _uuid_to_bytes(elt_token)
elt_obj = EphemeralToken.objects.filter(elt_uuid=elt_uuid, elt_status=1).first()
if not elt_obj or elt_obj.is_expired():
return False
return elt_obj
except Exception:
return False
def PermissionVerifier(permission, attributes):
if not permission:
return True
user = UGM.GetELTUser()
if not user:
return False
from gvsds.apps.gerp.models import UserABAC
for attr in attributes:
abac_match = UserABAC.objects.filter(
user_uuid=user.user_uuid,
hierarchical_permission_identifiers=attr
).first()
if abac_match:
continue
rbac_result = user.has_permission(attr)
if rbac_result:
continue
return False
return True
_request_local = threading.local()
def _get_query_cache():
if not hasattr(_request_local, 'query_cache'):
_request_local.query_cache = {}
return _request_local.query_cache
def _invalidate_query_cache(model_class=None):
cache = getattr(_request_local, 'query_cache', None)
if cache is None:
return
if model_class is None:
cache.clear()
else:
keys_to_remove = [k for k in cache if k[0] == model_class]
for k in keys_to_remove:
del cache[k]
def _get_current_request():
return getattr(_request_local, 'request', None)
def set_current_request(request):
_request_local.request = request
def _get_db_session():
session = getattr(_request_local, 'db_session', None)
if session is None:
session = _DBSession()
_request_local.db_session = session
return session
class _AggregateQuery:
def __init__(self, model, *aggregates):
self.model = model
self.aggregates = aggregates
self._filters_q = []
self._filters_kwargs = {}
def filter(self, *args, **kwargs):
converted_kwargs = {}
for key, val in kwargs.items():
parts = key.split('__')
parts[0] = _pascal_to_snake(parts[0])
converted_kwargs['__'.join(parts)] = val
self._filters_q.extend(args)
self._filters_kwargs.update(converted_kwargs)
return self
def scalar(self):
try:
qs = self.model.objects.filter(*self._filters_q, **self._filters_kwargs)
agg_names = [f'_agg_{i}' for i in range(len(self.aggregates))]
result = qs.aggregate(**dict(zip(agg_names, self.aggregates)))
if len(self.aggregates) == 1:
return result.get(agg_names[0])
return tuple(result[n] for n in agg_names)
except Exception:
return None
def all(self):
return self.scalar()
def first(self):
return self.scalar()
class _MultiTableRow:
def __init__(self, values, field_names):
for name, val in zip(field_names, values):
setattr(self, name, val)
class _MultiTableQuery:
def __init__(self, model, select_fields=None):
self.model = model
self._select_fields = select_fields or []
self._joins = []
self._outerjoins = []
self._filters_q = []
self._filters_kwargs = {}
def join(self, target, condition=None):
self._joins.append((target, condition))
return self
def outerjoin(self, target, condition=None):
self._outerjoins.append((target, condition))
return self
def filter(self, *args, **kwargs):
self._filters_q.extend(args)
self._filters_kwargs.update(kwargs)
return self
def _remap_q(self, q):
from django.db.models import Q as DjQ
new_children = []
for child in q.children:
if isinstance(child, DjQ):
new_children.append(self._remap_q(child))
elif isinstance(child, tuple):
key, val = child
new_key = self._remap_key(key)
new_children.append((new_key, val))
else:
new_children.append(child)
if new_children:
new_q = DjQ(*new_children)
new_q.connector = q.connector
new_q.negated = q.negated
return new_q
return q
def _remap_key(self, key):
parts = key.split('__')
field_name = parts[0]
try:
self.model._meta.get_field(field_name)
return key
except Exception:
pass
for joined_model, _ in self._joins + self._outerjoins:
if not isinstance(joined_model, type):
continue
rel_path = self._find_relation_path(joined_model)
if rel_path is None:
continue
base_field = field_name
if base_field.endswith('_id'):
base_field = base_field[:-3]
try:
joined_model._meta.get_field(base_field)
if len(parts) > 1:
return f'{rel_path}__{base_field}__{"__".join(parts[1:])}'
return f'{rel_path}__{base_field}'
except Exception:
pass
for f in joined_model._meta.local_fields:
pascal = snake_to_pascal(f.name)
if pascal == field_name or pascal == base_field:
if len(parts) > 1:
return f'{rel_path}__{f.name}__{"__".join(parts[1:])}'
return f'{rel_path}__{f.name}'
return key
def _build_queryset(self):
qs = self.model.objects.all()
select_related_fields = []
for target, condition in self._joins + self._outerjoins:
if isinstance(target, type) and issubclass(target, django.db.models.Model):
rel_path = self._find_relation_path(target)
if rel_path is None:
continue
is_forward_fk = False
for f in self.model._meta.local_fields:
if hasattr(f, 'related_model') and f.related_model == target and f.name == rel_path:
is_forward_fk = True
break
if is_forward_fk:
select_related_fields.append(rel_path)
if select_related_fields:
qs = qs.select_related(*select_related_fields)
remapped_q = []
for q in self._filters_q:
from django.db.models import Q as DjQ
if isinstance(q, DjQ):
remapped_q.append(self._remap_q(q))
else:
remapped_q.append(q)
remapped_kwargs = {}
for k, v in self._filters_kwargs.items():
remapped_kwargs[self._remap_key(k)] = v
if remapped_q or remapped_kwargs:
qs = qs.filter(*remapped_q, **remapped_kwargs)
return qs
def _find_relation_path(self, target_model):
for rel in self.model._meta.get_fields():
if hasattr(rel, 'related_model') and rel.related_model == target_model:
if hasattr(rel, 'field'):
return rel.name
if hasattr(rel, 'get_accessor_name'):
return rel.get_accessor_name()
for f in self.model._meta.local_fields:
if hasattr(f, 'related_model') and f.related_model == target_model:
return f.name
return None
def _get_field_names(self):
names = []
for f in self._select_fields:
raw_name = None
if hasattr(f, '_django_name'):
raw_name = f._django_name
elif hasattr(f, 'field_name'):
raw_name = f.field_name
elif hasattr(f, '__name__'):
raw_name = f.__name__
else:
raw_name = str(f)
if raw_name.endswith('_id'):
pascal = snake_to_pascal(raw_name[:-3])
else:
pascal = snake_to_pascal(raw_name)
names.append(pascal)
return names
def _resolve_field_on_obj(self, obj, django_field):
val = getattr(obj, django_field, None)
if val is not None:
return self._convert_uuid_field(obj, django_field, val)
for f in self._select_fields:
if hasattr(f, '_django_name') and f._django_name == django_field:
if hasattr(f, 'model_class') and f.model_class != self.model:
target_model = f.model_class
rel_path = self._find_relation_path(target_model)
if rel_path:
related_obj = getattr(obj, rel_path, None)
if related_obj is not None:
val = getattr(related_obj, django_field, None)
if val is not None:
return self._convert_uuid_field(related_obj, django_field, val)
break
return None
def _convert_uuid_field(self, obj, field_name, value):
try:
field = obj._meta.get_field(field_name)
from gvsds.apps.core.models import UUIDField
if isinstance(field, UUIDField) and isinstance(value, str):
return uuid.UUID(value).bytes
except Exception:
pass
if field_name.endswith('_id') and isinstance(value, str):
try:
uuid.UUID(value)
return uuid.UUID(value).bytes
except (ValueError, AttributeError):
pass
return value
def all(self):
qs = self._build_queryset()
if self._select_fields:
field_names = self._get_field_names()
django_fields = []
for f in self._select_fields:
if hasattr(f, '_django_name'):
django_fields.append(f._django_name)
elif hasattr(f, 'field_name'):
django_fields.append(f.field_name)
else:
django_fields.append(str(f))
results = []
for obj in qs:
values = [self._resolve_field_on_obj(obj, df) for df in django_fields]
results.append(_MultiTableRow(values, field_names))
return results
return list(qs)
def first(self):
qs = self._build_queryset()
obj = qs.first()
if obj is None:
return None
if self._select_fields:
field_names = self._get_field_names()
django_fields = []
for f in self._select_fields:
if hasattr(f, '_django_name'):
django_fields.append(f._django_name)
elif hasattr(f, 'field_name'):
django_fields.append(f.field_name)
else:
django_fields.append(str(f))
values = [self._resolve_field_on_obj(obj, df) for df in django_fields]
return _MultiTableRow(values, field_names)
return obj
class _DeleteStmt:
def __init__(self, model_class, conditions=None):
self.model_class = model_class
self.conditions = conditions or Q()
def where(self, *args, **kwargs):
from django.db.models import Q
new_conditions = Q()
for a in args:
if isinstance(a, Q):
new_conditions &= a
self.conditions = new_conditions
return self
def execute(self):
if self.model_class and self.conditions:
self.model_class.objects.filter(self.conditions).delete()
elif self.model_class:
self.model_class.objects.all().delete()
class _DBSession:
def __init__(self):
self._pending_adds = []
self._pending_deletes = []
self._tracked_instances = set()
def add(self, obj):
logger.debug(f"[DBSession.add] {obj.__class__.__name__} pk={getattr(obj, 'pk', '?')}")
self._pending_adds.append(obj)
def delete(self, obj):
logger.debug(f"[DBSession.delete] {obj.__class__.__name__} pk={obj.pk}")
self._pending_deletes.append(obj)
self._tracked_instances.discard(obj)
def track(self, obj):
if isinstance(obj, django.db.models.Model):
self._tracked_instances.add(obj)
def commit(self):
from django.db import transaction
logger.debug(f"[DBSession.commit] deletes={len(self._pending_deletes)} adds={len(self._pending_adds)} tracked={len(self._tracked_instances)}")
delete_pks = {(obj.__class__, obj.pk) for obj in self._pending_deletes if obj.pk is not None}
with transaction.atomic():
for obj in self._pending_deletes:
pk = obj.pk
model_name = obj.__class__.__name__
result = obj.delete()
logger.debug(f"[DBSession.commit] DELETE {model_name} pk={pk} result={result}")
for obj in self._pending_adds:
obj.save()
for obj in self._tracked_instances:
if obj.pk is not None and (obj.__class__, obj.pk) in delete_pks:
logger.debug(f"[DBSession.commit] SKIP save for deleted {obj.__class__.__name__} pk={obj.pk}")
continue
try:
obj.save()
except Exception as e:
logger.exception(f"[DBSession.commit] Failed to save {obj.__class__.__name__} pk={obj.pk}")
self._pending_adds.clear()
self._pending_deletes.clear()
self._tracked_instances.clear()
_invalidate_query_cache()
def rollback(self):
self._pending_adds.clear()
self._pending_deletes.clear()
self._tracked_instances.clear()
_invalidate_query_cache()
def execute(self, stmt):
if isinstance(stmt, _DeleteStmt):
stmt.execute()
elif isinstance(stmt, str):
from django.db import connection
with connection.cursor() as cursor:
cursor.execute(stmt)
def query(self, *args):
aggregates = []
model = None
select_fields = []
for a in args:
try:
from gvsds.fsr import _AggregateExpr
if isinstance(a, _AggregateExpr):
if a.model:
model = a.model
aggregates.append(a.aggregate)
continue
except Exception:
pass
from django.db.models import Aggregate
if isinstance(a, Aggregate):
aggregates.append(a)
elif isinstance(a, type) and issubclass(a, django.db.models.Model):
model = a
elif hasattr(a, '_django_name'):
from gvsds.model_base import FieldExpression
if isinstance(a, FieldExpression):
if a.model_class and model is None:
model = a.model_class
select_fields.append(a)
if aggregates:
return _AggregateQuery(model, *aggregates)
if select_fields:
return _MultiTableQuery(model, select_fields)
if model:
return _QueryCompat(model)
return None
class _DB:
@property
def func(self):
from gvsds.fsr import _CompatFunc
if not hasattr(self, '_func_instance'):
self._func_instance = _CompatFunc()
return self._func_instance
Float = float
String = str
@property
def session(self):
from gvsds.fluent import _get_session
return _get_session()
@staticmethod
def or_(*args):
from django.db.models import Q
result = None
for q in args:
if isinstance(q, Q):
if result is None:
result = q
else:
result |= q
return result if result is not None else Q()
@staticmethod
def and_(*args):
from django.db.models import Q
result = Q()
for q in args:
if isinstance(q, Q):
result &= q
return result
class _ValuesQueryCompat:
def __init__(self, qs, fields):
self._qs = qs
self._fields = fields
def filter(self, *args, **kwargs):
self._qs = self._qs.filter(*args, **kwargs)
return self
def first(self):
obj = self._qs.first()
if obj is None:
return None
if len(self._fields) == 1:
return getattr(obj, 'val0', None)
return tuple(getattr(obj, f'val{i}', None) for i in range(len(self._fields)))
def all(self):
results = []
for obj in self._qs:
if len(self._fields) == 1:
results.append(getattr(obj, 'val0', None))
else:
results.append(tuple(getattr(obj, f'val{i}', None) for i in range(len(self._fields))))
return results
def scalar(self):
obj = self._qs.first()
if obj is None:
return None
return getattr(obj, 'val0', None)
class _QueryCompat:
def __init__(self, model_class):
self.model = model_class
self._qs = model_class.objects.all()
self._joined_models = []
self._patched_fks = []
self._cache_enabled = True
def _cache_key(self, method):
qs = self._qs
try:
query_str = str(qs.query)
except Exception:
query_str = id(qs)
return (self.model, method, query_str)
def _ensure_reverse_relation(self, joined_model):
for fk in joined_model._meta.local_fields:
if not hasattr(fk, 'remote_field') or not fk.remote_field:
continue
if fk.remote_field.related_name == '+' and fk.remote_field.model == self.model:
rel_name = joined_model._meta.model_name
fk.remote_field.related_name = rel_name
self._patched_fks.append((fk, '+'))
def _restore_fks(self):
for fk, original in self._patched_fks:
fk.remote_field.related_name = original
self._patched_fks.clear()
def _convert_kwargs(self, kwargs):
django_kwargs = {}
for key, val in kwargs.items():
if key.endswith('__isnull'):
django_kwargs[key] = val
else:
parts = key.split('__')
parts[0] = _pascal_to_snake(parts[0])
django_kwargs['__'.join(parts)] = val
return django_kwargs
def _find_rel_path(self, field_name):
try:
self.model._meta.get_field(field_name)
return None
except Exception:
pass
lookup_name = field_name[:-3] if field_name.endswith('_id') else None
for joined in self._joined_models:
found = False
try:
joined._meta.get_field(field_name)
found = True
except Exception:
if lookup_name:
try:
joined._meta.get_field(lookup_name)
found = True
except Exception:
pass
if not found:
continue
for rel in self.model._meta.get_fields(include_hidden=True):
if hasattr(rel, 'related_model') and rel.related_model == joined:
if hasattr(rel, 'get_accessor_name'):
acc = rel.get_accessor_name()
if acc != '+':
return acc
if hasattr(rel, 'name') and rel.name != '+':
return rel.name
return joined._meta.model_name
return None
def _remap_q(self, q):
from django.db.models import Q as DjQ
new_children = []
for child in q.children:
if isinstance(child, DjQ):
new_children.append(self._remap_q(child))
elif isinstance(child, tuple):
key, val = child
new_key = self._remap_key(key)
new_children.append((new_key, val))
else:
new_children.append(child)
if new_children:
new_q = DjQ(*new_children)
new_q.connector = q.connector
new_q.negated = q.negated
return new_q
return q
def _remap_key(self, key):
parts = key.split('__')
field_name = parts[0]
try:
self.model._meta.get_field(field_name)
return key
except Exception:
pass
rel_path = self._find_rel_path(field_name)
if rel_path:
return rel_path + '__' + key
return key
def filter(self, *args, **kwargs):
converted_args = []
for arg in args:
from django.db.models import Q as DjQ
if isinstance(arg, DjQ) and self._joined_models:
converted_args.append(self._remap_q(arg))
else:
converted_args.append(arg)
django_kwargs = self._convert_kwargs(kwargs)
self._qs = self._qs.filter(*converted_args, **django_kwargs)
return self
def order_by(self, *args):
converted = []
for a in args:
if isinstance(a, str):
if a.startswith('-'):
converted.append('-' + _pascal_to_snake(a[1:]))
else:
converted.append(_pascal_to_snake(a))
else:
converted.append(str(a))
self._qs = self._qs.order_by(*converted)
return self
def first(self):
if self._cache_enabled:
cache = _get_query_cache()
key = self._cache_key('first')
if key in cache:
return cache[key]
obj = self._qs.first()
if obj is not None:
_get_db_session().track(obj)
if self._cache_enabled:
_get_query_cache()[self._cache_key('first')] = obj
return obj
def all(self):
if self._cache_enabled:
cache = _get_query_cache()
key = self._cache_key('all')
if key in cache:
return cache[key]
results = list(self._qs)
session = _get_db_session()
for obj in results:
session.track(obj)
if self._cache_enabled:
_get_query_cache()[self._cache_key('all')] = results
return results
def count(self):
if self._cache_enabled:
cache = _get_query_cache()
key = self._cache_key('count')
if key in cache:
return cache[key]
result = self._qs.count()
if self._cache_enabled:
_get_query_cache()[self._cache_key('count')] = result
return result
def get(self, **kwargs):
django_kwargs = self._convert_kwargs(kwargs)
obj = self._qs.get(**django_kwargs)
_get_db_session().track(obj)
return obj
def limit(self, n):
self._qs = self._qs[:n]
return self
def offset(self, n):
self._qs = self._qs[n:]
return self
def join(self, *args, **kwargs):
for arg in args:
if isinstance(arg, type) and issubclass(arg, django.db.models.Model):
if arg not in self._joined_models:
self._joined_models.append(arg)
self._ensure_reverse_relation(arg)
return self
def values(self, *fields):
qs = self._qs
annotations = {}
for i, f in enumerate(fields):
from gvsds.fsr import _AggregateExpr
if isinstance(f, _AggregateExpr):
annotations[f'val{i}'] = f.aggregate
elif isinstance(f, (django.db.models.Aggregate, django.db.models.Expression)):
annotations[f'val{i}'] = f
if annotations:
qs = qs.annotate(**annotations)
return _ValuesQueryCompat(qs, fields)
def update(self, values):
if isinstance(values, dict):
converted = {}
for k, v in values.items():
converted[_pascal_to_snake(k)] = v
return self._qs.update(**converted)
return 0
def delete(self):
return self._qs.delete()
def params(self, **kwargs):
return self
def paginate(self, page=1, per_page=20, error_out=False):
from django.core.paginator import Paginator
if not self._qs.ordered:
meta = getattr(self.model, '_meta', None)
if meta and meta.pk:
self._qs = self._qs.order_by(meta.pk.name)
paginator = Paginator(self._qs, per_page)
try:
page_obj = paginator.page(page)
except Exception:
page_obj = paginator.page(1)
page_obj.total = paginator.count
page_obj.items = page_obj.object_list
page_obj.pages = paginator.num_pages
page_obj.page = page
page_obj.per_page = per_page
page_obj.has_next = page_obj.has_next()
page_obj.has_prev = page_obj.has_previous()
page_obj.next_num = page_obj.next_page_number() if page_obj.has_next else None
page_obj.prev_num = page_obj.previous_page_number() if page_obj.has_prev else None
return page_obj
def _add_query_compat_to_models():
from gvsds.apps.crb.models import Enterprise, EnterpriseUser
for model in [User, Company, Department, Role, UserRole, RoleDept,
Permission, FileAsset, WorkOrder, Message, Menu,
Tenant, CompanyInfo, Enterprise, EnterpriseUser]:
if not hasattr(model, 'query'):
model.query = type('query', (), {
'__getattr__': lambda self, name: getattr(_QueryCompat(model), name)
})()
db = _DB()
GetELTUser = UGM.GetELTUser
def Gender2Str(c):
if c == -1:
return "未更改"
elif c == 1:
return "男"
elif c == 0:
return "女"
else:
return "错误"
import smtplib
import email.mime.text
import email.mime.multipart
import email.header
import email.encoders
from email.mime.base import MIMEBase
class EmailSender:
def __init__(self, smtpServer, smtpPort, senderEmail, senderPassword, senderName=None):
self.SmtpServer = smtpServer
self.SmtpPort = smtpPort
self.SenderEmail = senderEmail
self.SenderPassword = senderPassword
self.SenderName = senderName if senderName else senderEmail
self.FromHeader = f"{email.header.Header(self.SenderName, 'utf-8')}"
def SendTextEmail(self, receiverEmails, subject, content):
msg = email.mime.text.MIMEText(content, 'plain', 'utf-8')
msg['From'] = self.FromHeader
msg['To'] = ','.join(receiverEmails)
msg['Subject'] = email.header.Header(subject, 'utf-8')
try:
with smtplib.SMTP_SSL(self.SmtpServer, self.SmtpPort) as server:
server.login(self.SenderEmail, self.SenderPassword)
server.sendmail(self.SenderEmail, receiverEmails, msg.as_string())
return True
except Exception as e:
logger.error(f"发送文本邮件失败: {str(e)}")
return False
def SendHtmlEmail(self, receiverEmails, subject, htmlContent):
msg = email.mime.text.MIMEText(htmlContent, 'html', 'utf-8')
msg['From'] = self.FromHeader
msg['To'] = ','.join(receiverEmails)
msg['Subject'] = email.header.Header(subject, 'utf-8')
try:
with smtplib.SMTP_SSL(self.SmtpServer, self.SmtpPort) as server:
server.login(self.SenderEmail, self.SenderPassword)
server.sendmail(self.SenderEmail, receiverEmails, msg.as_string())
return True
except Exception as e:
logger.error(f"发送HTML邮件失败: {str(e)}")
return False
def NonLegalChar(s):
errors = r"\/:*?" + '"' + "<>!@#$%^&*()【】|、{}《》,。,."
for c in str(s):
if c in errors:
return True
return False
class BadH:
pass
class StoreValueRule:
pass