进行了 backend 的重构,实验中
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# Generated by Django 4.2.27 on 2026-06-17 17:17
|
# Generated by Django 4.2.27 on 2026-06-18 03:51
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -11,173 +12,6 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
|
||||||
name='Permission',
|
|
||||||
fields=[
|
|
||||||
('perm_id', models.AutoField(primary_key=True, serialize=False, verbose_name='权限ID')),
|
|
||||||
('perm_code', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='权限编码')),
|
|
||||||
('perm_name', models.CharField(max_length=100, verbose_name='权限名称')),
|
|
||||||
('description', models.CharField(blank=True, default='', max_length=200, verbose_name='权限描述')),
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '权限',
|
|
||||||
'verbose_name_plural': '权限',
|
|
||||||
'db_table': 'permission',
|
|
||||||
'managed': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Role',
|
|
||||||
fields=[
|
|
||||||
('role_id', models.AutoField(primary_key=True, serialize=False, verbose_name='角色ID')),
|
|
||||||
('role_code', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='角色编码')),
|
|
||||||
('role_name', models.CharField(max_length=50, verbose_name='角色名称')),
|
|
||||||
('description', models.CharField(blank=True, default='', max_length=200, verbose_name='角色描述')),
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '角色',
|
|
||||||
'verbose_name_plural': '角色',
|
|
||||||
'db_table': 'role',
|
|
||||||
'managed': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='RolePermission',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '角色权限关联',
|
|
||||||
'verbose_name_plural': '角色权限关联',
|
|
||||||
'db_table': 'role_permission',
|
|
||||||
'managed': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='UserRole',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
|
||||||
('account_id', models.CharField(db_index=True, max_length=11, verbose_name='账号ID')),
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '用户角色关联',
|
|
||||||
'verbose_name_plural': '用户角色关联',
|
|
||||||
'db_table': 'user_role',
|
|
||||||
'managed': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ZuzhangRiTongji',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('yonghuid', models.CharField(db_index=True, max_length=7, verbose_name='组长用户ID')),
|
|
||||||
('riqi', models.DateField(db_index=True, verbose_name='日期')),
|
|
||||||
('yaoqing_guanshi_shu', models.IntegerField(default=0, verbose_name='邀请管事总数')),
|
|
||||||
('shouru_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='收入总额(分佣)')),
|
|
||||||
('fenyong_jine', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='分佣金额')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '组长每日统计',
|
|
||||||
'db_table': 'zuzhang_ri_tongji',
|
|
||||||
'indexes': [models.Index(fields=['riqi', '-yaoqing_guanshi_shu'], name='zuzhang_ri__riqi_87169e_idx'), models.Index(fields=['riqi', '-shouru_zonge'], name='zuzhang_ri__riqi_1d7e51_idx')],
|
|
||||||
'unique_together': {('yonghuid', 'riqi')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='TixianRiTongji',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('riqi', models.DateField(db_index=True, verbose_name='日期')),
|
|
||||||
('leixing', models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '组长'), (4, '考核官'), (5, '打手押金'), (6, '商家余额')], verbose_name='用户类型')),
|
|
||||||
('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='当天提现总额(元)')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '每日提现统计',
|
|
||||||
'db_table': 'tixian_ri_tongji',
|
|
||||||
'indexes': [models.Index(fields=['riqi', 'leixing'], name='tixian_ri_t_riqi_c18bb2_idx')],
|
|
||||||
'unique_together': {('riqi', 'leixing')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ShangjiaRiTongji',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('yonghuid', models.CharField(db_index=True, max_length=7, verbose_name='商家用户ID')),
|
|
||||||
('riqi', models.DateField(db_index=True, verbose_name='日期')),
|
|
||||||
('paifa_dingdan_shu', models.IntegerField(default=0, verbose_name='派发订单总数')),
|
|
||||||
('paifa_jine', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='派发订单总金额')),
|
|
||||||
('jiesuan_dingdan_shu', models.IntegerField(default=0, verbose_name='结算订单总数')),
|
|
||||||
('jiesuan_jine', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='结算总金额')),
|
|
||||||
('tuikuan_dingdan_shu', models.IntegerField(default=0, verbose_name='退款订单总数')),
|
|
||||||
('tuikuan_jine', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='退款总金额')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '商家每日统计',
|
|
||||||
'db_table': 'shangjia_ri_tongji',
|
|
||||||
'indexes': [models.Index(fields=['riqi', '-paifa_jine'], name='shangjia_ri_riqi_d00f7f_idx'), models.Index(fields=['riqi', '-jiesuan_jine'], name='shangjia_ri_riqi_25772a_idx'), models.Index(fields=['riqi', '-paifa_dingdan_shu'], name='shangjia_ri_riqi_e05c54_idx')],
|
|
||||||
'unique_together': {('yonghuid', 'riqi')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='GuanshiXufeiRiTongji',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('yonghuid', models.CharField(db_index=True, max_length=7, verbose_name='管事用户ID')),
|
|
||||||
('riqi', models.DateField(db_index=True, verbose_name='日期')),
|
|
||||||
('xufei_zongshu', models.IntegerField(default=0, verbose_name='今日续费总数')),
|
|
||||||
('xufei_shouyi', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='续费收益金额')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '管事续费每日统计',
|
|
||||||
'db_table': 'guanshi_xufei_ri_tongji',
|
|
||||||
'indexes': [models.Index(fields=['riqi', '-xufei_zongshu'], name='guanshi_xuf_riqi_7c128f_idx'), models.Index(fields=['riqi', '-xufei_shouyi'], name='guanshi_xuf_riqi_8f09b5_idx')],
|
|
||||||
'unique_together': {('yonghuid', 'riqi')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='GuanshiRiTongji',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('yonghuid', models.CharField(db_index=True, max_length=7, verbose_name='管事用户ID')),
|
|
||||||
('riqi', models.DateField(db_index=True, verbose_name='日期')),
|
|
||||||
('yaoqing_dashou_shu', models.IntegerField(default=0, verbose_name='邀请打手总数')),
|
|
||||||
('chongzhi_dashou_shu', models.IntegerField(default=0, verbose_name='充值打手数量')),
|
|
||||||
('shouru_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='收入总额')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '管事每日统计',
|
|
||||||
'db_table': 'guanshi_ri_tongji',
|
|
||||||
'indexes': [models.Index(fields=['riqi', '-yaoqing_dashou_shu'], name='guanshi_ri__riqi_075e1a_idx'), models.Index(fields=['riqi', '-chongzhi_dashou_shu'], name='guanshi_ri__riqi_78a79d_idx'), models.Index(fields=['riqi', '-shouru_zonge'], name='guanshi_ri__riqi_334f52_idx')],
|
|
||||||
'unique_together': {('yonghuid', 'riqi')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='DashouRiTongji',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('yonghuid', models.CharField(db_index=True, max_length=7, verbose_name='打手用户ID')),
|
|
||||||
('riqi', models.DateField(db_index=True, verbose_name='日期')),
|
|
||||||
('jiedan_zongliang', models.IntegerField(default=0, verbose_name='接单总量')),
|
|
||||||
('chengjiao_zongliang', models.IntegerField(default=0, verbose_name='成交单总量')),
|
|
||||||
('jiedan_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='接单总金额')),
|
|
||||||
('chengjiao_zonge', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='成交总金额')),
|
|
||||||
('tuikuan_liang', models.IntegerField(default=0, verbose_name='退款订单量')),
|
|
||||||
('tuikuan_jine', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='退款总金额')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '打手每日统计',
|
|
||||||
'db_table': 'dashou_ri_tongji',
|
|
||||||
'indexes': [models.Index(fields=['riqi', '-jiedan_zongliang'], name='dashou_ri_t_riqi_95a785_idx'), models.Index(fields=['riqi', '-chengjiao_zongliang'], name='dashou_ri_t_riqi_ad9c1a_idx'), models.Index(fields=['riqi', '-jiedan_zonge'], name='dashou_ri_t_riqi_5da703_idx'), models.Index(fields=['riqi', '-chengjiao_zonge'], name='dashou_ri_t_riqi_0a3156_idx')],
|
|
||||||
'unique_together': {('yonghuid', 'riqi')},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='AbnormalUserLog',
|
name='AbnormalUserLog',
|
||||||
fields=[
|
fields=[
|
||||||
@@ -200,7 +34,270 @@ class Migration(migrations.Migration):
|
|||||||
'verbose_name': '异常用户日志',
|
'verbose_name': '异常用户日志',
|
||||||
'verbose_name_plural': '异常用户日志',
|
'verbose_name_plural': '异常用户日志',
|
||||||
'db_table': 'abnormal_user_log',
|
'db_table': 'abnormal_user_log',
|
||||||
'indexes': [models.Index(fields=['account_id'], name='abnormal_us_account_425899_idx'), models.Index(fields=['ip_address'], name='abnormal_us_ip_addr_3d612b_idx'), models.Index(fields=['create_time'], name='abnormal_us_create__ee16da_idx')],
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LeaderDailyStats',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('LeaderID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='组长用户ID')),
|
||||||
|
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||||
|
('InvitedManagerCount', models.IntegerField(db_column='yaoqing_guanshi_shu', default=0, verbose_name='邀请管事总数')),
|
||||||
|
('TotalIncome', models.DecimalField(db_column='shouru_zonge', decimal_places=2, default=0.0, max_digits=12, verbose_name='收入总额(分佣)')),
|
||||||
|
('CommissionAmount', models.DecimalField(db_column='fenyong_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='分佣金额')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '组长每日统计',
|
||||||
|
'db_table': 'zuzhang_ri_tongji',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ManagerDailyStats',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ManagerID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='管事用户ID')),
|
||||||
|
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||||
|
('InvitedPlayerCount', models.IntegerField(db_column='yaoqing_dashou_shu', default=0, verbose_name='邀请打手总数')),
|
||||||
|
('RechargedPlayerCount', models.IntegerField(db_column='chongzhi_dashou_shu', default=0, verbose_name='充值打手数量')),
|
||||||
|
('TotalIncome', models.DecimalField(db_column='shouru_zonge', decimal_places=2, default=0.0, max_digits=12, verbose_name='收入总额')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '管事每日统计',
|
||||||
|
'db_table': 'guanshi_ri_tongji',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ManagerRenewalDailyStats',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ManagerID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='管事用户ID')),
|
||||||
|
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||||
|
('RenewalTotal', models.IntegerField(db_column='xufei_zongshu', default=0, verbose_name='今日续费总数')),
|
||||||
|
('RenewalRevenue', models.DecimalField(db_column='xufei_shouyi', decimal_places=2, default=0.0, max_digits=12, verbose_name='续费收益金额')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '管事续费每日统计',
|
||||||
|
'db_table': 'guanshi_xufei_ri_tongji',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MerchantDailyStats',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('MerchantID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='商家用户ID')),
|
||||||
|
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||||
|
('AssignedOrderCount', models.IntegerField(db_column='paifa_dingdan_shu', default=0, verbose_name='派发订单总数')),
|
||||||
|
('AssignedAmount', models.DecimalField(db_column='paifa_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='派发订单总金额')),
|
||||||
|
('SettledOrderCount', models.IntegerField(db_column='jiesuan_dingdan_shu', default=0, verbose_name='结算订单总数')),
|
||||||
|
('SettledAmount', models.DecimalField(db_column='jiesuan_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='结算总金额')),
|
||||||
|
('RefundOrderCount', models.IntegerField(db_column='tuikuan_dingdan_shu', default=0, verbose_name='退款订单总数')),
|
||||||
|
('RefundAmount', models.DecimalField(db_column='tuikuan_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='退款总金额')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '商家每日统计',
|
||||||
|
'db_table': 'shangjia_ri_tongji',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Permission',
|
||||||
|
fields=[
|
||||||
|
('perm_id', models.AutoField(primary_key=True, serialize=False, verbose_name='权限ID')),
|
||||||
|
('perm_code', models.CharField(db_index=True, max_length=100, unique=True, verbose_name='权限编码')),
|
||||||
|
('perm_name', models.CharField(max_length=100, verbose_name='权限名称')),
|
||||||
|
('description', models.CharField(blank=True, default='', max_length=200, verbose_name='权限描述')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '权限',
|
||||||
|
'verbose_name_plural': '权限',
|
||||||
|
'db_table': 'permission',
|
||||||
|
'managed': True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PlayerDailyStats',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('PlayerID', models.CharField(db_column='yonghuid', db_index=True, max_length=7, verbose_name='打手用户ID')),
|
||||||
|
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||||
|
('AcceptedOrderTotal', models.IntegerField(db_column='jiedan_zongliang', default=0, verbose_name='接单总量')),
|
||||||
|
('CompletedOrderTotal', models.IntegerField(db_column='chengjiao_zongliang', default=0, verbose_name='成交单总量')),
|
||||||
|
('AcceptedAmount', models.DecimalField(db_column='jiedan_zonge', decimal_places=2, default=0.0, max_digits=12, verbose_name='接单总金额')),
|
||||||
|
('CompletedAmount', models.DecimalField(db_column='chengjiao_zonge', decimal_places=2, default=0.0, max_digits=12, verbose_name='成交总金额')),
|
||||||
|
('RefundCount', models.IntegerField(db_column='tuikuan_liang', default=0, verbose_name='退款订单量')),
|
||||||
|
('RefundAmount', models.DecimalField(db_column='tuikuan_jine', decimal_places=2, default=0.0, max_digits=12, verbose_name='退款总金额')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '打手每日统计',
|
||||||
|
'db_table': 'dashou_ri_tongji',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Role',
|
||||||
|
fields=[
|
||||||
|
('role_id', models.AutoField(primary_key=True, serialize=False, verbose_name='角色ID')),
|
||||||
|
('role_code', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='角色编码')),
|
||||||
|
('role_name', models.CharField(max_length=50, verbose_name='角色名称')),
|
||||||
|
('description', models.CharField(blank=True, default='', max_length=200, verbose_name='角色描述')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '角色',
|
||||||
|
'verbose_name_plural': '角色',
|
||||||
|
'db_table': 'role',
|
||||||
|
'managed': True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WithdrawalDailyStats',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('Date', models.DateField(db_column='riqi', db_index=True, verbose_name='日期')),
|
||||||
|
('WithdrawalType', models.IntegerField(choices=[(1, '打手'), (2, '管事'), (3, '组长'), (4, '考核官'), (5, '打手押金'), (6, '商家余额')], db_column='leixing', verbose_name='用户类型')),
|
||||||
|
('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=12, verbose_name='当天提现总额(元)')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '每日提现统计',
|
||||||
|
'db_table': 'tixian_ri_tongji',
|
||||||
|
'indexes': [models.Index(fields=['Date', 'WithdrawalType'], name='tixian_ri_t_riqi_c18bb2_idx')],
|
||||||
|
'unique_together': {('Date', 'WithdrawalType')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserRole',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('account_id', models.CharField(db_index=True, max_length=11, verbose_name='账号ID')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.role', verbose_name='角色')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '用户角色关联',
|
||||||
|
'verbose_name_plural': '用户角色关联',
|
||||||
|
'db_table': 'user_role',
|
||||||
|
'managed': True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RolePermission',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('permission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.permission', verbose_name='权限')),
|
||||||
|
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.role', verbose_name='角色')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '角色权限关联',
|
||||||
|
'verbose_name_plural': '角色权限关联',
|
||||||
|
'db_table': 'role_permission',
|
||||||
|
'managed': True,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='role',
|
||||||
|
index=models.Index(fields=['role_code'], name='role_role_co_b9f497_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='playerdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-AcceptedOrderTotal'], name='dashou_ri_t_riqi_95a785_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='playerdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-CompletedOrderTotal'], name='dashou_ri_t_riqi_ad9c1a_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='playerdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-AcceptedAmount'], name='dashou_ri_t_riqi_5da703_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='playerdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-CompletedAmount'], name='dashou_ri_t_riqi_0a3156_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='playerdailystats',
|
||||||
|
unique_together={('PlayerID', 'Date')},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='permission',
|
||||||
|
index=models.Index(fields=['perm_code'], name='permission_perm_co_56cbe3_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='merchantdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-AssignedAmount'], name='shangjia_ri_riqi_d00f7f_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='merchantdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-SettledAmount'], name='shangjia_ri_riqi_25772a_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='merchantdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-AssignedOrderCount'], name='shangjia_ri_riqi_e05c54_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='merchantdailystats',
|
||||||
|
unique_together={('MerchantID', 'Date')},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='managerrenewaldailystats',
|
||||||
|
index=models.Index(fields=['Date', '-RenewalTotal'], name='guanshi_xuf_riqi_7c128f_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='managerrenewaldailystats',
|
||||||
|
index=models.Index(fields=['Date', '-RenewalRevenue'], name='guanshi_xuf_riqi_8f09b5_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='managerrenewaldailystats',
|
||||||
|
unique_together={('ManagerID', 'Date')},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='managerdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-InvitedPlayerCount'], name='guanshi_ri__riqi_075e1a_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='managerdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-RechargedPlayerCount'], name='guanshi_ri__riqi_78a79d_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='managerdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-TotalIncome'], name='guanshi_ri__riqi_334f52_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='managerdailystats',
|
||||||
|
unique_together={('ManagerID', 'Date')},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='leaderdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-InvitedManagerCount'], name='zuzhang_ri__riqi_87169e_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='leaderdailystats',
|
||||||
|
index=models.Index(fields=['Date', '-TotalIncome'], name='zuzhang_ri__riqi_1d7e51_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='leaderdailystats',
|
||||||
|
unique_together={('LeaderID', 'Date')},
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='abnormaluserlog',
|
||||||
|
index=models.Index(fields=['account_id'], name='abnormal_us_account_425899_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='abnormaluserlog',
|
||||||
|
index=models.Index(fields=['ip_address'], name='abnormal_us_ip_addr_3d612b_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='abnormaluserlog',
|
||||||
|
index=models.Index(fields=['create_time'], name='abnormal_us_create__ee16da_idx'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='userrole',
|
||||||
|
unique_together={('account_id', 'role')},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='rolepermission',
|
||||||
|
unique_together={('role', 'permission')},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class Role(QModel):
|
|||||||
db_table = 'role'
|
db_table = 'role'
|
||||||
verbose_name = '角色'
|
verbose_name = '角色'
|
||||||
verbose_name_plural = '角色'
|
verbose_name_plural = '角色'
|
||||||
managed = False
|
managed = True
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['role_code']),
|
models.Index(fields=['role_code']),
|
||||||
]
|
]
|
||||||
@@ -40,7 +40,7 @@ class Permission(QModel):
|
|||||||
db_table = 'permission'
|
db_table = 'permission'
|
||||||
verbose_name = '权限'
|
verbose_name = '权限'
|
||||||
verbose_name_plural = '权限'
|
verbose_name_plural = '权限'
|
||||||
managed = False
|
managed = True
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['perm_code']),
|
models.Index(fields=['perm_code']),
|
||||||
]
|
]
|
||||||
@@ -61,8 +61,8 @@ class RolePermission(QModel):
|
|||||||
db_table = 'role_permission'
|
db_table = 'role_permission'
|
||||||
verbose_name = '角色权限关联'
|
verbose_name = '角色权限关联'
|
||||||
verbose_name_plural = '角色权限关联'
|
verbose_name_plural = '角色权限关联'
|
||||||
managed = False
|
managed = True
|
||||||
unique_together = [['role', 'permission']] # 防止重复授权
|
unique_together = [['role', 'permission']]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.role.role_name} -> {self.permission.perm_name}"
|
return f"{self.role.role_name} -> {self.permission.perm_name}"
|
||||||
@@ -72,7 +72,7 @@ class RolePermission(QModel):
|
|||||||
class UserRole(QModel):
|
class UserRole(QModel):
|
||||||
"""用户角色关联表"""
|
"""用户角色关联表"""
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID') # 对应 UserMain 的 phone 字段
|
account_id = models.CharField(max_length=11, db_index=True, verbose_name='账号ID')
|
||||||
role = models.ForeignKey(Role, on_delete=models.CASCADE, db_index=True, verbose_name='角色')
|
role = models.ForeignKey(Role, on_delete=models.CASCADE, db_index=True, verbose_name='角色')
|
||||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||||
@@ -81,37 +81,28 @@ class UserRole(QModel):
|
|||||||
db_table = 'user_role'
|
db_table = 'user_role'
|
||||||
verbose_name = '用户角色关联'
|
verbose_name = '用户角色关联'
|
||||||
verbose_name_plural = '用户角色关联'
|
verbose_name_plural = '用户角色关联'
|
||||||
managed = False
|
managed = True
|
||||||
unique_together = [['account_id', 'role']] # 同一用户同一角色只能有一条
|
unique_together = [['account_id', 'role']]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.account_id} -> {self.role.role_name}"
|
return f"{self.account_id} -> {self.role.role_name}"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 5. 异常用户日志 ====================
|
||||||
class AbnormalUserLog(QModel):
|
class AbnormalUserLog(QModel):
|
||||||
"""记录恶意尝试越权操作的用户信息"""
|
"""记录恶意尝试越权操作的用户信息"""
|
||||||
# 账号信息
|
|
||||||
account_id = models.CharField(max_length=20, db_index=True, verbose_name='账号ID(phone)')
|
account_id = models.CharField(max_length=20, db_index=True, verbose_name='账号ID(phone)')
|
||||||
|
|
||||||
# 网络信息
|
|
||||||
ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP地址')
|
ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP地址')
|
||||||
real_ip = models.GenericIPAddressField(null=True, blank=True, verbose_name='真实IP(穿透代理后)')
|
real_ip = models.GenericIPAddressField(null=True, blank=True, verbose_name='真实IP(穿透代理后)')
|
||||||
x_forwarded_for = models.CharField(max_length=255, blank=True, default='', verbose_name='X-Forwarded-For')
|
x_forwarded_for = models.CharField(max_length=255, blank=True, default='', verbose_name='X-Forwarded-For')
|
||||||
user_agent = models.CharField(max_length=500, blank=True, default='', verbose_name='User-Agent')
|
user_agent = models.CharField(max_length=500, blank=True, default='', verbose_name='User-Agent')
|
||||||
|
|
||||||
# 地理位置信息(需借助第三方API,这里只存储请求时的IP,后续可离线解析)
|
|
||||||
# 但我们可以存储更多请求细节
|
|
||||||
request_method = models.CharField(max_length=10, blank=True, default='', verbose_name='请求方法')
|
request_method = models.CharField(max_length=10, blank=True, default='', verbose_name='请求方法')
|
||||||
request_path = models.CharField(max_length=500, blank=True, default='', verbose_name='请求路径')
|
request_path = models.CharField(max_length=500, blank=True, default='', verbose_name='请求路径')
|
||||||
request_params = models.TextField(blank=True, default='', verbose_name='请求参数(GET/POST)')
|
request_params = models.TextField(blank=True, default='', verbose_name='请求参数(GET/POST)')
|
||||||
request_body = models.TextField(blank=True, default='', verbose_name='请求体(原始)')
|
request_body = models.TextField(blank=True, default='', verbose_name='请求体(原始)')
|
||||||
request_headers = models.TextField(blank=True, default='', verbose_name='请求头(JSON格式)')
|
request_headers = models.TextField(blank=True, default='', verbose_name='请求头(JSON格式)')
|
||||||
|
|
||||||
# 攻击特征
|
|
||||||
attempt_type = models.CharField(max_length=50, blank=True, default='', verbose_name='攻击类型')
|
attempt_type = models.CharField(max_length=50, blank=True, default='', verbose_name='攻击类型')
|
||||||
detail = models.TextField(blank=True, default='', verbose_name='详细描述')
|
detail = models.TextField(blank=True, default='', verbose_name='详细描述')
|
||||||
|
|
||||||
# 时间戳
|
|
||||||
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -128,148 +119,206 @@ class AbnormalUserLog(QModel):
|
|||||||
return f"{self.account_id} @ {self.ip_address} - {self.create_time}"
|
return f"{self.account_id} @ {self.ip_address} - {self.create_time}"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 6. 商家每日统计 ====================
|
||||||
|
class MerchantDailyStats(QModel):
|
||||||
|
"""商家每日统计表(原 ShangjiaRiTongji)"""
|
||||||
class ShangjiaRiTongji(QModel):
|
MerchantID = models.CharField(
|
||||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='商家用户ID')
|
max_length=7, db_index=True, db_column='yonghuid', verbose_name='商家用户ID')
|
||||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
Date = models.DateField(
|
||||||
|
db_index=True, db_column='riqi', verbose_name='日期')
|
||||||
|
|
||||||
# 派发统计
|
# 派发统计
|
||||||
paifa_dingdan_shu = models.IntegerField(default=0, verbose_name='派发订单总数')
|
AssignedOrderCount = models.IntegerField(
|
||||||
paifa_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='派发订单总金额')
|
default=0, db_column='paifa_dingdan_shu', verbose_name='派发订单总数')
|
||||||
|
AssignedAmount = models.DecimalField(
|
||||||
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
|
db_column='paifa_jine', verbose_name='派发订单总金额')
|
||||||
|
|
||||||
# 结算统计(成交)
|
# 结算统计
|
||||||
jiesuan_dingdan_shu = models.IntegerField(default=0, verbose_name='结算订单总数')
|
SettledOrderCount = models.IntegerField(
|
||||||
jiesuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='结算总金额')
|
default=0, db_column='jiesuan_dingdan_shu', verbose_name='结算订单总数')
|
||||||
|
SettledAmount = models.DecimalField(
|
||||||
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
|
db_column='jiesuan_jine', verbose_name='结算总金额')
|
||||||
|
|
||||||
# 退款统计(可选)
|
# 退款统计
|
||||||
tuikuan_dingdan_shu = models.IntegerField(default=0, verbose_name='退款订单总数')
|
RefundOrderCount = models.IntegerField(
|
||||||
tuikuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总金额')
|
default=0, db_column='tuikuan_dingdan_shu', verbose_name='退款订单总数')
|
||||||
|
RefundAmount = models.DecimalField(
|
||||||
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
|
db_column='tuikuan_jine', verbose_name='退款总金额')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'shangjia_ri_tongji'
|
db_table = 'shangjia_ri_tongji'
|
||||||
verbose_name = '商家每日统计'
|
verbose_name = '商家每日统计'
|
||||||
unique_together = [['yonghuid', 'riqi']] # 每人每天一条记录
|
unique_together = [['MerchantID', 'Date']]
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['riqi', '-paifa_jine']),
|
models.Index(fields=['Date', '-AssignedAmount']),
|
||||||
models.Index(fields=['riqi', '-jiesuan_jine']),
|
models.Index(fields=['Date', '-SettledAmount']),
|
||||||
models.Index(fields=['riqi', '-paifa_dingdan_shu']),
|
models.Index(fields=['Date', '-AssignedOrderCount']),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"商家{self.MerchantID} {self.Date}"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 7. 打手每日统计 ====================
|
||||||
|
class PlayerDailyStats(QModel):
|
||||||
class DashouRiTongji(QModel):
|
"""打手每日统计表(原 DashouRiTongji)"""
|
||||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='打手用户ID')
|
PlayerID = models.CharField(
|
||||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
max_length=7, db_index=True, db_column='yonghuid', verbose_name='打手用户ID')
|
||||||
|
Date = models.DateField(
|
||||||
|
db_index=True, db_column='riqi', verbose_name='日期')
|
||||||
|
|
||||||
# 接单统计
|
# 接单统计
|
||||||
jiedan_zongliang = models.IntegerField(default=0, verbose_name='接单总量')
|
AcceptedOrderTotal = models.IntegerField(
|
||||||
chengjiao_zongliang = models.IntegerField(default=0, verbose_name='成交单总量')
|
default=0, db_column='jiedan_zongliang', verbose_name='接单总量')
|
||||||
|
CompletedOrderTotal = models.IntegerField(
|
||||||
|
default=0, db_column='chengjiao_zongliang', verbose_name='成交单总量')
|
||||||
|
|
||||||
# 金额统计
|
# 金额统计
|
||||||
jiedan_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='接单总金额')
|
AcceptedAmount = models.DecimalField(
|
||||||
chengjiao_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='成交总金额')
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
|
db_column='jiedan_zonge', verbose_name='接单总金额')
|
||||||
|
CompletedAmount = models.DecimalField(
|
||||||
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
|
db_column='chengjiao_zonge', verbose_name='成交总金额')
|
||||||
|
|
||||||
# 退款统计
|
# 退款统计
|
||||||
tuikuan_liang = models.IntegerField(default=0, verbose_name='退款订单量')
|
RefundCount = models.IntegerField(
|
||||||
tuikuan_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='退款总金额')
|
default=0, db_column='tuikuan_liang', verbose_name='退款订单量')
|
||||||
|
RefundAmount = models.DecimalField(
|
||||||
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
|
db_column='tuikuan_jine', verbose_name='退款总金额')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'dashou_ri_tongji'
|
db_table = 'dashou_ri_tongji'
|
||||||
verbose_name = '打手每日统计'
|
verbose_name = '打手每日统计'
|
||||||
unique_together = [['yonghuid', 'riqi']]
|
unique_together = [['PlayerID', 'Date']]
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['riqi', '-jiedan_zongliang']),
|
models.Index(fields=['Date', '-AcceptedOrderTotal']),
|
||||||
models.Index(fields=['riqi', '-chengjiao_zongliang']),
|
models.Index(fields=['Date', '-CompletedOrderTotal']),
|
||||||
models.Index(fields=['riqi', '-jiedan_zonge']),
|
models.Index(fields=['Date', '-AcceptedAmount']),
|
||||||
models.Index(fields=['riqi', '-chengjiao_zonge']),
|
models.Index(fields=['Date', '-CompletedAmount']),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"打手{self.PlayerID} {self.Date}"
|
||||||
|
|
||||||
|
|
||||||
class ZuzhangRiTongji(QModel):
|
# ==================== 8. 组长每日统计 ====================
|
||||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='组长用户ID')
|
class LeaderDailyStats(QModel):
|
||||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
"""组长每日统计表(原 ZuzhangRiTongji)"""
|
||||||
|
LeaderID = models.CharField(
|
||||||
|
max_length=7, db_index=True, db_column='yonghuid', verbose_name='组长用户ID')
|
||||||
|
Date = models.DateField(
|
||||||
|
db_index=True, db_column='riqi', verbose_name='日期')
|
||||||
|
|
||||||
yaoqing_guanshi_shu = models.IntegerField(default=0, verbose_name='邀请管事总数')
|
InvitedManagerCount = models.IntegerField(
|
||||||
shouru_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='收入总额(分佣)')
|
default=0, db_column='yaoqing_guanshi_shu', verbose_name='邀请管事总数')
|
||||||
|
TotalIncome = models.DecimalField(
|
||||||
# 可选明细
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
fenyong_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='分佣金额')
|
db_column='shouru_zonge', verbose_name='收入总额(分佣)')
|
||||||
|
CommissionAmount = models.DecimalField(
|
||||||
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
|
db_column='fenyong_jine', verbose_name='分佣金额')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'zuzhang_ri_tongji'
|
db_table = 'zuzhang_ri_tongji'
|
||||||
verbose_name = '组长每日统计'
|
verbose_name = '组长每日统计'
|
||||||
unique_together = [['yonghuid', 'riqi']]
|
unique_together = [['LeaderID', 'Date']]
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['riqi', '-yaoqing_guanshi_shu']),
|
models.Index(fields=['Date', '-InvitedManagerCount']),
|
||||||
models.Index(fields=['riqi', '-shouru_zonge']),
|
models.Index(fields=['Date', '-TotalIncome']),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"组长{self.LeaderID} {self.Date}"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 9. 管事每日统计 ====================
|
||||||
|
class ManagerDailyStats(QModel):
|
||||||
|
"""管事每日统计表(原 GuanshiRiTongji)"""
|
||||||
|
ManagerID = models.CharField(
|
||||||
|
max_length=7, db_index=True, db_column='yonghuid', verbose_name='管事用户ID')
|
||||||
|
Date = models.DateField(
|
||||||
|
db_index=True, db_column='riqi', verbose_name='日期')
|
||||||
|
|
||||||
class GuanshiRiTongji(QModel):
|
InvitedPlayerCount = models.IntegerField(
|
||||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='管事用户ID')
|
default=0, db_column='yaoqing_dashou_shu', verbose_name='邀请打手总数')
|
||||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
RechargedPlayerCount = models.IntegerField(
|
||||||
|
default=0, db_column='chongzhi_dashou_shu', verbose_name='充值打手数量')
|
||||||
yaoqing_dashou_shu = models.IntegerField(default=0, verbose_name='邀请打手总数')
|
TotalIncome = models.DecimalField(
|
||||||
chongzhi_dashou_shu = models.IntegerField(default=0, verbose_name='充值打手数量')
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
shouru_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='收入总额')
|
db_column='shouru_zonge', verbose_name='收入总额')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'guanshi_ri_tongji'
|
db_table = 'guanshi_ri_tongji'
|
||||||
verbose_name = '管事每日统计'
|
verbose_name = '管事每日统计'
|
||||||
unique_together = [['yonghuid', 'riqi']]
|
unique_together = [['ManagerID', 'Date']]
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['riqi', '-yaoqing_dashou_shu']),
|
models.Index(fields=['Date', '-InvitedPlayerCount']),
|
||||||
models.Index(fields=['riqi', '-chongzhi_dashou_shu']),
|
models.Index(fields=['Date', '-RechargedPlayerCount']),
|
||||||
models.Index(fields=['riqi', '-shouru_zonge']),
|
models.Index(fields=['Date', '-TotalIncome']),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"管事{self.ManagerID} {self.Date}"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 10. 管事续费每日统计 ====================
|
||||||
|
class ManagerRenewalDailyStats(QModel):
|
||||||
|
"""管事续费每日统计表(原 GuanshiXufeiRiTongji)"""
|
||||||
|
ManagerID = models.CharField(
|
||||||
|
max_length=7, db_index=True, db_column='yonghuid', verbose_name='管事用户ID')
|
||||||
|
Date = models.DateField(
|
||||||
|
db_index=True, db_column='riqi', verbose_name='日期')
|
||||||
|
|
||||||
class GuanshiXufeiRiTongji(QModel):
|
RenewalTotal = models.IntegerField(
|
||||||
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='管事用户ID')
|
default=0, db_column='xufei_zongshu', verbose_name='今日续费总数')
|
||||||
riqi = models.DateField(db_index=True, verbose_name='日期') # 年月日
|
RenewalRevenue = models.DecimalField(
|
||||||
|
max_digits=12, decimal_places=2, default=0.00,
|
||||||
xufei_zongshu = models.IntegerField(default=0, verbose_name='今日续费总数') # 续费订单数(或续费打手数)
|
db_column='xufei_shouyi', verbose_name='续费收益金额')
|
||||||
xufei_shouyi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='续费收益金额')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'guanshi_xufei_ri_tongji'
|
db_table = 'guanshi_xufei_ri_tongji'
|
||||||
verbose_name = '管事续费每日统计'
|
verbose_name = '管事续费每日统计'
|
||||||
unique_together = [['yonghuid', 'riqi']]
|
unique_together = [['ManagerID', 'Date']]
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['riqi', '-xufei_zongshu']),
|
models.Index(fields=['Date', '-RenewalTotal']),
|
||||||
models.Index(fields=['riqi', '-xufei_shouyi']),
|
models.Index(fields=['Date', '-RenewalRevenue']),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"管事续费{self.ManagerID} {self.Date}"
|
||||||
|
|
||||||
|
|
||||||
# models.py 中添加
|
# ==================== 11. 提现每日统计 ====================
|
||||||
|
class WithdrawalDailyStats(QModel):
|
||||||
class TixianRiTongji(QModel):
|
"""每日提现统计表(原 TixianRiTongji),按身份统计当天提现总额"""
|
||||||
"""
|
Date = models.DateField(
|
||||||
每日提现统计表(按身份统计当天提现总额)
|
db_index=True, db_column='riqi', verbose_name='日期')
|
||||||
"""
|
WithdrawalType = models.IntegerField(
|
||||||
riqi = models.DateField(db_index=True, verbose_name='日期')
|
|
||||||
leixing = models.IntegerField(
|
|
||||||
choices=[(1, '打手'), (2, '管事'), (3, '组长'),
|
choices=[(1, '打手'), (2, '管事'), (3, '组长'),
|
||||||
(4, '考核官'), (5, '打手押金'), (6, '商家余额')],
|
(4, '考核官'), (5, '打手押金'), (6, '商家余额')],
|
||||||
verbose_name='用户类型'
|
db_column='leixing', verbose_name='用户类型')
|
||||||
)
|
total_amount = models.DecimalField(
|
||||||
total_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='当天提现总额(元)')
|
max_digits=12, decimal_places=2, default=0.00, verbose_name='当天提现总额(元)')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'tixian_ri_tongji'
|
db_table = 'tixian_ri_tongji'
|
||||||
verbose_name = '每日提现统计'
|
verbose_name = '每日提现统计'
|
||||||
unique_together = [['riqi', 'leixing']] # 每天每个身份一条记录
|
unique_together = [['Date', 'WithdrawalType']]
|
||||||
indexes = [models.Index(fields=['riqi', 'leixing'])]
|
indexes = [models.Index(fields=['Date', 'WithdrawalType'])]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.riqi} - {self.get_leixing_display()} 提现总额: {self.total_amount}"
|
return f"{self.Date} - {self.get_WithdrawalType_display()} 提现总额: {self.total_amount}"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 兼容别名(供逐步迁移) ====================
|
||||||
|
ShangjiaRiTongji = MerchantDailyStats
|
||||||
|
DashouRiTongji = PlayerDailyStats
|
||||||
|
ZuzhangRiTongji = LeaderDailyStats
|
||||||
|
GuanshiRiTongji = ManagerDailyStats
|
||||||
|
GuanshiXufeiRiTongji = ManagerRenewalDailyStats
|
||||||
|
TixianRiTongji = WithdrawalDailyStats
|
||||||
|
|||||||
231
backend/utils.py
231
backend/utils.py
@@ -5,8 +5,8 @@ from functools import lru_cache
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from .models import AbnormalUserLog, UserRole, Permission, DashouRiTongji, ShangjiaRiTongji, \
|
from .models import AbnormalUserLog, UserRole, Permission, PlayerDailyStats, MerchantDailyStats, \
|
||||||
GuanshiXufeiRiTongji, GuanshiRiTongji, ZuzhangRiTongji, TixianRiTongji
|
ManagerRenewalDailyStats, ManagerDailyStats, LeaderDailyStats, WithdrawalDailyStats
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
@@ -129,36 +129,36 @@ def update_dashou_daily_by_action(yonghuid, amount, action):
|
|||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
stat, created = DashouRiTongji.objects.select_for_update().get_or_create(
|
stat, created = PlayerDailyStats.objects.select_for_update().get_or_create(
|
||||||
yonghuid=yonghuid,
|
PlayerID=yonghuid,
|
||||||
riqi=today,
|
Date=today,
|
||||||
defaults={
|
defaults={
|
||||||
'yonghuid': yonghuid,
|
'PlayerID': yonghuid,
|
||||||
'riqi': today,
|
'Date': today,
|
||||||
'jiedan_zongliang': 0,
|
'AcceptedOrderTotal': 0,
|
||||||
'jiedan_zonge': Decimal('0.00'),
|
'AcceptedAmount': Decimal('0.00'),
|
||||||
'chengjiao_zongliang': 0,
|
'CompletedOrderTotal': 0,
|
||||||
'chengjiao_zonge': Decimal('0.00'),
|
'CompletedAmount': Decimal('0.00'),
|
||||||
'tuikuan_liang': 0,
|
'RefundCount': 0,
|
||||||
'tuikuan_jine': Decimal('0.00'),
|
'RefundAmount': Decimal('0.00'),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
update_fields = {}
|
update_fields = {}
|
||||||
if action == 1: # 接单
|
if action == 1: # 接单
|
||||||
update_fields['jiedan_zongliang'] = F('jiedan_zongliang') + 1
|
update_fields['AcceptedOrderTotal'] = F('AcceptedOrderTotal') + 1
|
||||||
update_fields['jiedan_zonge'] = F('jiedan_zonge') + amount
|
update_fields['AcceptedAmount'] = F('AcceptedAmount') + amount
|
||||||
elif action == 2: # 成交
|
elif action == 2: # 成交
|
||||||
update_fields['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1
|
update_fields['CompletedOrderTotal'] = F('CompletedOrderTotal') + 1
|
||||||
update_fields['chengjiao_zonge'] = F('chengjiao_zonge') + amount
|
update_fields['CompletedAmount'] = F('CompletedAmount') + amount
|
||||||
elif action == 3: # 退款
|
elif action == 3: # 退款
|
||||||
update_fields['tuikuan_liang'] = F('tuikuan_liang') + 1
|
update_fields['RefundCount'] = F('RefundCount') + 1
|
||||||
update_fields['tuikuan_jine'] = F('tuikuan_jine') + amount
|
update_fields['RefundAmount'] = F('RefundAmount') + amount
|
||||||
else:
|
else:
|
||||||
return # 无效行为
|
return # 无效行为
|
||||||
|
|
||||||
if update_fields:
|
if update_fields:
|
||||||
DashouRiTongji.query.filter(id=stat.id).update(**update_fields)
|
PlayerDailyStats.query.filter(id=stat.id).update(**update_fields)
|
||||||
|
|
||||||
|
|
||||||
def update_shangjia_daily(yonghuid, amount, action):
|
def update_shangjia_daily(yonghuid, amount, action):
|
||||||
@@ -183,38 +183,38 @@ def update_shangjia_daily(yonghuid, amount, action):
|
|||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# 获取或创建当天统计记录,同时加锁防止并发
|
# 获取或创建当天统计记录,同时加锁防止并发
|
||||||
stat, created = ShangjiaRiTongji.objects.select_for_update().get_or_create(
|
stat, created = MerchantDailyStats.objects.select_for_update().get_or_create(
|
||||||
yonghuid=yonghuid,
|
MerchantID=yonghuid,
|
||||||
riqi=today,
|
Date=today,
|
||||||
defaults={
|
defaults={
|
||||||
'yonghuid': yonghuid,
|
'MerchantID': yonghuid,
|
||||||
'riqi': today,
|
'Date': today,
|
||||||
'paifa_dingdan_shu': 0,
|
'AssignedOrderCount': 0,
|
||||||
'paifa_jine': Decimal('0.00'),
|
'AssignedAmount': Decimal('0.00'),
|
||||||
'jiesuan_dingdan_shu': 0,
|
'SettledOrderCount': 0,
|
||||||
'jiesuan_jine': Decimal('0.00'),
|
'SettledAmount': Decimal('0.00'),
|
||||||
'tuikuan_dingdan_shu': 0,
|
'RefundOrderCount': 0,
|
||||||
'tuikuan_jine': Decimal('0.00'),
|
'RefundAmount': Decimal('0.00'),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# 构建本次更新的字段,使用 F 表达式进行原子累加
|
# 构建本次更新的字段,使用 F 表达式进行原子累加
|
||||||
update_fields = {}
|
update_fields = {}
|
||||||
if action == 1: # 派发
|
if action == 1: # 派发
|
||||||
update_fields['paifa_dingdan_shu'] = F('paifa_dingdan_shu') + 1
|
update_fields['AssignedOrderCount'] = F('AssignedOrderCount') + 1
|
||||||
update_fields['paifa_jine'] = F('paifa_jine') + amount
|
update_fields['AssignedAmount'] = F('AssignedAmount') + amount
|
||||||
elif action == 2: # 结算(成交)
|
elif action == 2: # 结算(成交)
|
||||||
update_fields['jiesuan_dingdan_shu'] = F('jiesuan_dingdan_shu') + 1
|
update_fields['SettledOrderCount'] = F('SettledOrderCount') + 1
|
||||||
update_fields['jiesuan_jine'] = F('jiesuan_jine') + amount
|
update_fields['SettledAmount'] = F('SettledAmount') + amount
|
||||||
elif action == 3: # 退款
|
elif action == 3: # 退款
|
||||||
update_fields['tuikuan_dingdan_shu'] = F('tuikuan_dingdan_shu') + 1
|
update_fields['RefundOrderCount'] = F('RefundOrderCount') + 1
|
||||||
update_fields['tuikuan_jine'] = F('tuikuan_jine') + amount
|
update_fields['RefundAmount'] = F('RefundAmount') + amount
|
||||||
else:
|
else:
|
||||||
# 无效的行为类型,直接返回
|
# 无效的行为类型,直接返回
|
||||||
return
|
return
|
||||||
|
|
||||||
# 执行原子更新
|
# 执行原子更新
|
||||||
ShangjiaRiTongji.query.filter(id=stat.id).update(**update_fields)
|
MerchantDailyStats.query.filter(id=stat.id).update(**update_fields)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -226,26 +226,26 @@ def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
|
|||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
stat, created = GuanshiRiTongji.objects.select_for_update().get_or_create(
|
stat, created = ManagerDailyStats.objects.select_for_update().get_or_create(
|
||||||
yonghuid=yonghuid,
|
ManagerID=yonghuid,
|
||||||
riqi=today,
|
Date=today,
|
||||||
defaults={
|
defaults={
|
||||||
'yonghuid': yonghuid,
|
'ManagerID': yonghuid,
|
||||||
'riqi': today,
|
'Date': today,
|
||||||
'yaoqing_dashou_shu': 0,
|
'InvitedPlayerCount': 0,
|
||||||
'chongzhi_dashou_shu': 0,
|
'RechargedPlayerCount': 0,
|
||||||
'shouru_zonge': Decimal('0.00'),
|
'TotalIncome': Decimal('0.00'),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if action == 1:
|
if action == 1:
|
||||||
# 邀请打手:无论新创建还是已存在,直接 +1
|
# 邀请打手:无论新创建还是已存在,直接 +1
|
||||||
if created:
|
if created:
|
||||||
stat.yaoqing_dashou_shu = 1
|
stat.InvitedPlayerCount = 1
|
||||||
stat.save()
|
stat.save()
|
||||||
else:
|
else:
|
||||||
GuanshiRiTongji.query.filter(id=stat.id).update(
|
ManagerDailyStats.query.filter(id=stat.id).update(
|
||||||
yaoqing_dashou_shu=F('yaoqing_dashou_shu') + 1
|
InvitedPlayerCount=F('InvitedPlayerCount') + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
elif action == 2:
|
elif action == 2:
|
||||||
@@ -253,13 +253,13 @@ def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
|
|||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
return
|
return
|
||||||
if created:
|
if created:
|
||||||
stat.chongzhi_dashou_shu = 1
|
stat.RechargedPlayerCount = 1
|
||||||
stat.shouru_zonge = amount
|
stat.TotalIncome = amount
|
||||||
stat.save()
|
stat.save()
|
||||||
else:
|
else:
|
||||||
GuanshiRiTongji.query.filter(id=stat.id).update(
|
ManagerDailyStats.query.filter(id=stat.id).update(
|
||||||
chongzhi_dashou_shu=F('chongzhi_dashou_shu') + 1,
|
RechargedPlayerCount=F('RechargedPlayerCount') + 1,
|
||||||
shouru_zonge=F('shouru_zonge') + amount
|
TotalIncome=F('TotalIncome') + amount
|
||||||
)
|
)
|
||||||
|
|
||||||
elif action in (3, 4):
|
elif action in (3, 4):
|
||||||
@@ -267,70 +267,13 @@ def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
|
|||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
return
|
return
|
||||||
if created:
|
if created:
|
||||||
stat.shouru_zonge = amount
|
stat.TotalIncome = amount
|
||||||
stat.save()
|
stat.save()
|
||||||
else:
|
else:
|
||||||
GuanshiRiTongji.query.filter(id=stat.id).update(
|
ManagerDailyStats.query.filter(id=stat.id).update(
|
||||||
shouru_zonge=F('shouru_zonge') + amount
|
TotalIncome=F('TotalIncome') + amount
|
||||||
)
|
)
|
||||||
|
|
||||||
'''def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
|
|
||||||
"""
|
|
||||||
根据操作行为更新管事每日统计(邀请打手、充值会员)
|
|
||||||
|
|
||||||
参数:
|
|
||||||
yonghuid: 管事用户ID
|
|
||||||
action: 操作类型
|
|
||||||
1 = 邀请打手(次数自动+1,无需传其他参数)
|
|
||||||
2 = 充值会员(需要传 amount 参数)
|
|
||||||
amount: 充值金额(Decimal),仅在 action=2 时有效
|
|
||||||
|
|
||||||
说明:
|
|
||||||
- 每次调用只代表一次操作,不会批量处理。
|
|
||||||
- 使用 select_for_update + F() 保证原子性和并发安全。
|
|
||||||
"""
|
|
||||||
today = date.today()
|
|
||||||
|
|
||||||
# 邀请打手:次数+1,金额不变
|
|
||||||
if action == 1:
|
|
||||||
count_field = 'yaoqing_dashou_shu'
|
|
||||||
amount = Decimal('0.00')
|
|
||||||
|
|
||||||
# 充值会员:次数+1,金额累加
|
|
||||||
elif action == 2:
|
|
||||||
amount = Decimal(str(amount)) if amount else Decimal('0.00')
|
|
||||||
if amount <= 0:
|
|
||||||
return
|
|
||||||
count_field = 'chongzhi_dashou_shu'
|
|
||||||
|
|
||||||
else:
|
|
||||||
return # 无效行为,直接返回
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
stat, created = GuanshiRiTongji.objects.select_for_update().get_or_create(
|
|
||||||
yonghuid=yonghuid,
|
|
||||||
riqi=today,
|
|
||||||
defaults={
|
|
||||||
'yonghuid': yonghuid,
|
|
||||||
'riqi': today,
|
|
||||||
'yaoqing_dashou_shu': 0,
|
|
||||||
'chongzhi_dashou_shu': 0,
|
|
||||||
'shouru_zonge': Decimal('0.00'),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if created:
|
|
||||||
# 新记录直接赋初值
|
|
||||||
if action == 1:
|
|
||||||
stat.yaoqing_dashou_shu = 1
|
|
||||||
else:
|
|
||||||
# 已存在记录,使用 F 表达式原子累加次数
|
|
||||||
update_fields = {count_field: F(count_field) + 1}
|
|
||||||
|
|
||||||
if action == 2:
|
|
||||||
update_fields['shouru_zonge'] = F('shouru_zonge') + amount
|
|
||||||
|
|
||||||
GuanshiRiTongji.query.filter(id=stat.id).update(**update_fields)'''
|
|
||||||
|
|
||||||
def update_guanshi_xufei_daily(yonghuid, xufei_jine=Decimal('0.00')):
|
def update_guanshi_xufei_daily(yonghuid, xufei_jine=Decimal('0.00')):
|
||||||
"""
|
"""
|
||||||
@@ -339,20 +282,20 @@ def update_guanshi_xufei_daily(yonghuid, xufei_jine=Decimal('0.00')):
|
|||||||
"""
|
"""
|
||||||
today = date.today()
|
today = date.today()
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
stat, created = GuanshiXufeiRiTongji.objects.select_for_update().get_or_create(
|
stat, created = ManagerRenewalDailyStats.objects.select_for_update().get_or_create(
|
||||||
yonghuid=yonghuid,
|
ManagerID=yonghuid,
|
||||||
riqi=today,
|
Date=today,
|
||||||
defaults={
|
defaults={
|
||||||
'yonghuid': yonghuid,
|
'ManagerID': yonghuid,
|
||||||
'riqi': today,
|
'Date': today,
|
||||||
'xufei_zongshu': 1,
|
'RenewalTotal': 1,
|
||||||
'xufei_shouyi': xufei_jine,
|
'RenewalRevenue': xufei_jine,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if not created:
|
if not created:
|
||||||
GuanshiXufeiRiTongji.query.filter(id=stat.id).update(
|
ManagerRenewalDailyStats.query.filter(id=stat.id).update(
|
||||||
xufei_zongshu=F('xufei_zongshu') + 1,
|
RenewalTotal=F('RenewalTotal') + 1,
|
||||||
xufei_shouyi=F('xufei_shouyi') + xufei_jine
|
RenewalRevenue=F('RenewalRevenue') + xufei_jine
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -370,39 +313,39 @@ def update_zuzhang_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
|
|||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
stat, created = ZuzhangRiTongji.objects.select_for_update().get_or_create(
|
stat, created = LeaderDailyStats.objects.select_for_update().get_or_create(
|
||||||
yonghuid=yonghuid,
|
LeaderID=yonghuid,
|
||||||
riqi=today,
|
Date=today,
|
||||||
defaults={
|
defaults={
|
||||||
'yonghuid': yonghuid,
|
'LeaderID': yonghuid,
|
||||||
'riqi': today,
|
'Date': today,
|
||||||
'yaoqing_guanshi_shu': 0,
|
'InvitedManagerCount': 0,
|
||||||
'shouru_zonge': Decimal('0.00'),
|
'TotalIncome': Decimal('0.00'),
|
||||||
'fenyong_jine': Decimal('0.00'),
|
'CommissionAmount': Decimal('0.00'),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
# 新记录直接设置初始值
|
# 新记录直接设置初始值
|
||||||
if action == 1:
|
if action == 1:
|
||||||
stat.yaoqing_guanshi_shu = 1
|
stat.InvitedManagerCount = 1
|
||||||
elif action == 2:
|
elif action == 2:
|
||||||
amount = Decimal(str(amount)) if amount else Decimal('0.00')
|
amount = Decimal(str(amount)) if amount else Decimal('0.00')
|
||||||
stat.shouru_zonge = amount
|
stat.TotalIncome = amount
|
||||||
stat.fenyong_jine = amount
|
stat.CommissionAmount = amount
|
||||||
stat.save()
|
stat.save()
|
||||||
else:
|
else:
|
||||||
# 已存在记录,使用 F 表达式原子累加
|
# 已存在记录,使用 F 表达式原子累加
|
||||||
if action == 1:
|
if action == 1:
|
||||||
ZuzhangRiTongji.query.filter(id=stat.id).update(
|
LeaderDailyStats.query.filter(id=stat.id).update(
|
||||||
yaoqing_guanshi_shu=F('yaoqing_guanshi_shu') + 1
|
InvitedManagerCount=F('InvitedManagerCount') + 1
|
||||||
)
|
)
|
||||||
elif action == 2:
|
elif action == 2:
|
||||||
amount = Decimal(str(amount)) if amount else Decimal('0.00')
|
amount = Decimal(str(amount)) if amount else Decimal('0.00')
|
||||||
if amount > 0:
|
if amount > 0:
|
||||||
ZuzhangRiTongji.query.filter(id=stat.id).update(
|
LeaderDailyStats.query.filter(id=stat.id).update(
|
||||||
shouru_zonge=F('shouru_zonge') + amount,
|
TotalIncome=F('TotalIncome') + amount,
|
||||||
fenyong_jine=F('fenyong_jine') + amount
|
CommissionAmount=F('CommissionAmount') + amount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -415,13 +358,13 @@ def update_tixian_daily_stat(leixing, amount):
|
|||||||
"""
|
"""
|
||||||
today = date.today()
|
today = date.today()
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
stat, created = TixianRiTongji.objects.select_for_update().get_or_create(
|
stat, created = WithdrawalDailyStats.objects.select_for_update().get_or_create(
|
||||||
riqi=today,
|
Date=today,
|
||||||
leixing=leixing,
|
WithdrawalType=leixing,
|
||||||
defaults={'total_amount': amount}
|
defaults={'total_amount': amount}
|
||||||
)
|
)
|
||||||
if not created:
|
if not created:
|
||||||
TixianRiTongji.query.filter(id=stat.id).update(
|
WithdrawalDailyStats.query.filter(id=stat.id).update(
|
||||||
total_amount=F('total_amount') + amount
|
total_amount=F('total_amount') + amount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ from .utils import (
|
|||||||
|
|
||||||
# models 集中导入
|
# models 集中导入
|
||||||
## backend
|
## backend
|
||||||
from backend.models import Role, Permission, RolePermission, UserRole, TixianRiTongji
|
from backend.models import Role, Permission, RolePermission, UserRole, WithdrawalDailyStats
|
||||||
|
|
||||||
## users
|
## users
|
||||||
from users.models import (
|
from users.models import (
|
||||||
@@ -3329,10 +3329,10 @@ class GetWithdrawSettingsView(APIView):
|
|||||||
# 4. 当日已提现总额(按提现类型 leixing 1~6)
|
# 4. 当日已提现总额(按提现类型 leixing 1~6)
|
||||||
today = date.today()
|
today = date.today()
|
||||||
today_totals = {i: 0.0 for i in range(1, 7)}
|
today_totals = {i: 0.0 for i in range(1, 7)}
|
||||||
records = TixianRiTongji.query.filter(riqi=today)
|
records = WithdrawalDailyStats.query.filter(Date=today)
|
||||||
for rec in records:
|
for rec in records:
|
||||||
if rec.leixing in today_totals:
|
if rec.WithdrawalType in today_totals:
|
||||||
today_totals[rec.leixing] = float(rec.total_amount)
|
today_totals[rec.WithdrawalType] = float(rec.total_amount)
|
||||||
|
|
||||||
# 🆕 获取提现模式(自动1 / 手动2),默认2
|
# 🆕 获取提现模式(自动1 / 手动2),默认2
|
||||||
config = WithdrawConfig.query.filter(id=1).first()
|
config = WithdrawConfig.query.filter(id=1).first()
|
||||||
@@ -3358,7 +3358,7 @@ class UpdateWithdrawSettingsView(APIView):
|
|||||||
- 修改管事相关需要5500b
|
- 修改管事相关需要5500b
|
||||||
- 修改组长相关需要5500c
|
- 修改组长相关需要5500c
|
||||||
注意:修改每日总限额时,会联动影响“当日已提现总额”的显示(由前端实时计算),
|
注意:修改每日总限额时,会联动影响“当日已提现总额”的显示(由前端实时计算),
|
||||||
后端不直接修改 TixianRiTongji,该表由提现流程自动累加。
|
后端不直接修改 WithdrawalDailyStats,该表由提现流程自动累加。
|
||||||
POST /houtai/htxgtxsz
|
POST /houtai/htxgtxsz
|
||||||
"""
|
"""
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
@@ -3491,7 +3491,7 @@ class UpdateWithdrawSettingsView(APIView):
|
|||||||
defaults={'mode': int(withdraw_mode)}
|
defaults={'mode': int(withdraw_mode)}
|
||||||
)
|
)
|
||||||
|
|
||||||
# 注意:TixianRiTongji 表由提现流程自动更新,此处不直接修改
|
# 注意:WithdrawalDailyStats 表由提现流程自动更新,此处不直接修改
|
||||||
return Response({'code': 0, 'msg': '设置修改成功'})
|
return Response({'code': 0, 'msg': '设置修改成功'})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,149 +1,240 @@
|
|||||||
|
"""FluentQuery 兼容层查询构建器
|
||||||
|
|
||||||
|
提供 SQLAlchemy 风格的链式 OOP 查询接口,包装 Django QuerySet。
|
||||||
|
|
||||||
|
核心类:
|
||||||
|
- ``FluentQuery`` — 链式查询构建器,由 ``Model.query`` 返回
|
||||||
|
- ``Session`` — 简易会话管理(add/commit/rollback)
|
||||||
|
- ``_CompatFunc`` — 兼容层聚合函数(func.sum / func.count 等)
|
||||||
|
- ``db`` — 全局数据库操作入口(db.session / db.or_ / db.and_)
|
||||||
|
|
||||||
|
典型用法::
|
||||||
|
|
||||||
|
from gvsdsdk.fluent import db, func, FQ
|
||||||
|
|
||||||
|
# 链式查询
|
||||||
|
orders = Dingdan.query.filter(zhuangtai=8).order_by('-create_time').to_list()
|
||||||
|
|
||||||
|
# 字段表达式
|
||||||
|
Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3]))
|
||||||
|
|
||||||
|
# 聚合
|
||||||
|
Dingdan.query.aggregate(total=Sum('jine'))
|
||||||
|
|
||||||
|
# values / annotate
|
||||||
|
Dingdan.query.values('zhuangtai').annotate(cnt=Count('id'))
|
||||||
|
|
||||||
|
# 分页
|
||||||
|
Dingdan.query.paginate(page=1, per_page=20)
|
||||||
|
|
||||||
|
# 兼容层聚合函数
|
||||||
|
func.sum(Dingdan.jine)
|
||||||
|
func.count(Dingdan.id)
|
||||||
|
func.year(Dingdan.create_time)
|
||||||
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import functools
|
from typing import Any, Dict, Generic, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union
|
||||||
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union, overload
|
|
||||||
|
|
||||||
from django.db import connection as default_db_connection, models, transaction
|
from django.db import connection as default_db_connection, models, transaction
|
||||||
from django.db.models import Q, F, Count, Sum, Avg, Min, Max, Subquery, OuterRef, Case, When, Value
|
from django.db.models import Q, F, Count, Sum, Avg, Min, Max, Subquery, OuterRef, Case, When, Value
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_local = threading.local()
|
_local = threading.local()
|
||||||
|
|
||||||
|
#: 模型类型变量,用于 FluentQuery[_M] 泛型参数
|
||||||
|
_M = TypeVar('_M', bound=models.Model)
|
||||||
|
|
||||||
|
|
||||||
def _get_field_expression_cls():
|
def _get_field_expression_cls():
|
||||||
"""延迟导入,避免 fluent ↔ model_base 循环依赖"""
|
"""延迟导入 FieldExpression,避免 fluent ↔ model_base 循环依赖。"""
|
||||||
from gvsdsdk.model_base import FieldExpression
|
from gvsdsdk.model_base import FieldExpression
|
||||||
return FieldExpression
|
return FieldExpression
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 兼容层聚合函数
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class _AggregateExpr:
|
class _AggregateExpr:
|
||||||
def __init__(self, model, aggregate):
|
"""聚合表达式包装,关联模型类和 Django Aggregate 对象。"""
|
||||||
|
|
||||||
|
def __init__(self, model: Optional[Type[models.Model]], aggregate: models.Aggregate) -> None:
|
||||||
self.model = model
|
self.model = model
|
||||||
self.aggregate = aggregate
|
self.aggregate = aggregate
|
||||||
|
|
||||||
|
|
||||||
class _ExtractExpr:
|
class _ExtractExpr:
|
||||||
def __init__(self, field_name, lookup_type):
|
"""日期提取表达式(year/month/day),用于 ``func.year(field) == 2024`` 等。"""
|
||||||
|
|
||||||
|
def __init__(self, field_name: str, lookup_type: str) -> None:
|
||||||
self.field_name = field_name
|
self.field_name = field_name
|
||||||
self.lookup_type = lookup_type
|
self.lookup_type = lookup_type
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other) -> Q: # type: ignore[override]
|
||||||
return Q(**{f'{self.field_name}__{self.lookup_type}': other})
|
return Q(**{f'{self.field_name}__{self.lookup_type}': other})
|
||||||
|
|
||||||
def __and__(self, other):
|
def __and__(self, other) -> Q:
|
||||||
if isinstance(other, Q):
|
if isinstance(other, Q):
|
||||||
return Q(**{f'{self.field_name}__{self.lookup_type}': None}) & other
|
return Q(**{f'{self.field_name}__{self.lookup_type}': None}) & other
|
||||||
return self.__eq__(other)
|
return self.__eq__(other)
|
||||||
|
|
||||||
|
|
||||||
class _CompatFunc:
|
class _CompatFunc:
|
||||||
def sum(self, field_expr):
|
"""兼容层聚合函数集合,提供 SQLAlchemy 风格的 ``func.sum()`` / ``func.count()`` 等。
|
||||||
if hasattr(field_expr, '_django_name'):
|
|
||||||
agg = Sum(field_expr._django_name)
|
用法::
|
||||||
elif hasattr(field_expr, 'field_name'):
|
|
||||||
agg = Sum(field_expr.field_name)
|
from gvsdsdk.fluent import func
|
||||||
else:
|
|
||||||
agg = Sum(str(field_expr))
|
func.sum(Model.jine) → Sum('jine')
|
||||||
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
|
func.count(Model.id) → Count('id')
|
||||||
|
func.max(Model.jine) → Max('jine')
|
||||||
|
func.min(Model.jine) → Min('jine')
|
||||||
|
func.avg(Model.jine) → Avg('jine')
|
||||||
|
func.year(Model.riqi) → _ExtractExpr('riqi', 'year')
|
||||||
|
func.month(Model.riqi) → _ExtractExpr('riqi', 'month')
|
||||||
|
func.day(Model.riqi) → _ExtractExpr('riqi', 'day')
|
||||||
|
"""
|
||||||
|
|
||||||
|
def sum(self, field_expr) -> Union[_AggregateExpr, Sum]:
|
||||||
|
"""求和聚合。"""
|
||||||
|
name = self._resolve_name(field_expr)
|
||||||
|
agg = Sum(name)
|
||||||
|
model = getattr(field_expr, 'model_class', None)
|
||||||
return _AggregateExpr(model, agg) if model else agg
|
return _AggregateExpr(model, agg) if model else agg
|
||||||
|
|
||||||
def count(self, field_expr=None):
|
def count(self, field_expr=None) -> Union[_AggregateExpr, Count]:
|
||||||
|
"""计数聚合,无参数时等价于 COUNT(*)。"""
|
||||||
if field_expr is None:
|
if field_expr is None:
|
||||||
return Count('*')
|
return Count('*')
|
||||||
if hasattr(field_expr, '_django_name'):
|
name = self._resolve_name(field_expr)
|
||||||
agg = Count(field_expr._django_name)
|
agg = Count(name)
|
||||||
else:
|
model = getattr(field_expr, 'model_class', None)
|
||||||
agg = Count(str(field_expr))
|
|
||||||
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
|
|
||||||
return _AggregateExpr(model, agg) if model else agg
|
return _AggregateExpr(model, agg) if model else agg
|
||||||
|
|
||||||
def max(self, field_expr):
|
def max(self, field_expr) -> Union[_AggregateExpr, Max]:
|
||||||
if hasattr(field_expr, '_django_name'):
|
"""最大值聚合。"""
|
||||||
agg = Max(field_expr._django_name)
|
name = self._resolve_name(field_expr)
|
||||||
else:
|
agg = Max(name)
|
||||||
agg = Max(str(field_expr))
|
model = getattr(field_expr, 'model_class', None)
|
||||||
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
|
|
||||||
return _AggregateExpr(model, agg) if model else agg
|
return _AggregateExpr(model, agg) if model else agg
|
||||||
|
|
||||||
def min(self, field_expr):
|
def min(self, field_expr) -> Union[_AggregateExpr, Min]:
|
||||||
if hasattr(field_expr, '_django_name'):
|
"""最小值聚合。"""
|
||||||
agg = Min(field_expr._django_name)
|
name = self._resolve_name(field_expr)
|
||||||
else:
|
agg = Min(name)
|
||||||
agg = Min(str(field_expr))
|
model = getattr(field_expr, 'model_class', None)
|
||||||
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
|
|
||||||
return _AggregateExpr(model, agg) if model else agg
|
return _AggregateExpr(model, agg) if model else agg
|
||||||
|
|
||||||
def avg(self, field_expr):
|
def avg(self, field_expr) -> Union[_AggregateExpr, Avg]:
|
||||||
if hasattr(field_expr, '_django_name'):
|
"""平均值聚合。"""
|
||||||
agg = Avg(field_expr._django_name)
|
name = self._resolve_name(field_expr)
|
||||||
else:
|
agg = Avg(name)
|
||||||
agg = Avg(str(field_expr))
|
model = getattr(field_expr, 'model_class', None)
|
||||||
model = getattr(field_expr, 'model_class', None) if hasattr(field_expr, 'model_class') else None
|
|
||||||
return _AggregateExpr(model, agg) if model else agg
|
return _AggregateExpr(model, agg) if model else agg
|
||||||
|
|
||||||
def year(self, field_expr):
|
def year(self, field_expr) -> _ExtractExpr:
|
||||||
if hasattr(field_expr, '_django_name'):
|
"""提取年份,用于 ``func.year(Model.date) == 2024``。"""
|
||||||
return _ExtractExpr(field_expr._django_name, 'year')
|
return _ExtractExpr(self._resolve_name(field_expr), 'year')
|
||||||
return _ExtractExpr(str(field_expr), 'year')
|
|
||||||
|
|
||||||
def month(self, field_expr):
|
def month(self, field_expr) -> _ExtractExpr:
|
||||||
if hasattr(field_expr, '_django_name'):
|
"""提取月份。"""
|
||||||
return _ExtractExpr(field_expr._django_name, 'month')
|
return _ExtractExpr(self._resolve_name(field_expr), 'month')
|
||||||
return _ExtractExpr(str(field_expr), 'month')
|
|
||||||
|
|
||||||
def day(self, field_expr):
|
def day(self, field_expr) -> _ExtractExpr:
|
||||||
|
"""提取日期。"""
|
||||||
|
return _ExtractExpr(self._resolve_name(field_expr), 'day')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_name(field_expr) -> str:
|
||||||
|
"""从 FieldExpression 或字符串解析 Django 字段名。"""
|
||||||
if hasattr(field_expr, '_django_name'):
|
if hasattr(field_expr, '_django_name'):
|
||||||
return _ExtractExpr(field_expr._django_name, 'day')
|
return field_expr._django_name
|
||||||
return _ExtractExpr(str(field_expr), 'day')
|
if hasattr(field_expr, 'field_name'):
|
||||||
|
return field_expr.field_name
|
||||||
|
return str(field_expr)
|
||||||
|
|
||||||
|
|
||||||
func = _CompatFunc()
|
func = _CompatFunc()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# DELETE 语句构建器
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class _DeleteStmt:
|
class _DeleteStmt:
|
||||||
def __init__(self, model_class, conditions=None):
|
"""DELETE 语句构建器,支持 ``Model.query.filter(...).delete()`` 风格。"""
|
||||||
|
|
||||||
|
def __init__(self, model_class: Type[models.Model], conditions: Optional[Q] = None) -> None:
|
||||||
self.model_class = model_class
|
self.model_class = model_class
|
||||||
self.conditions = conditions or Q()
|
self.conditions = conditions or Q()
|
||||||
|
|
||||||
def where(self, *args, **kwargs):
|
def where(self, *args: Q, **kwargs: Any) -> _DeleteStmt:
|
||||||
|
"""添加 WHERE 条件。"""
|
||||||
new_conditions = Q()
|
new_conditions = Q()
|
||||||
for a in args:
|
for a in args:
|
||||||
if isinstance(a, Q):
|
if isinstance(a, Q):
|
||||||
new_conditions &= a
|
new_conditions &= a
|
||||||
|
if kwargs:
|
||||||
|
new_conditions &= Q(**kwargs)
|
||||||
self.conditions = new_conditions
|
self.conditions = new_conditions
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def execute(self):
|
def execute(self) -> Tuple[int, Dict[str, int]]:
|
||||||
|
"""执行删除,返回 (删除数, {表名: 删除数})。"""
|
||||||
if self.model_class and self.conditions:
|
if self.model_class and self.conditions:
|
||||||
self.model_class.objects.filter(self.conditions).delete()
|
return self.model_class.objects.filter(self.conditions).delete()
|
||||||
elif self.model_class:
|
elif self.model_class:
|
||||||
self.model_class.objects.all().delete()
|
return self.model_class.objects.all().delete()
|
||||||
|
return (0, {})
|
||||||
|
|
||||||
|
|
||||||
def _get_session():
|
# ---------------------------------------------------------------------------
|
||||||
|
# 会话与缓存
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _get_session() -> Session:
|
||||||
|
"""获取当前线程的 Session 实例。"""
|
||||||
if not hasattr(_local, 'db_session'):
|
if not hasattr(_local, 'db_session'):
|
||||||
_local.db_session = Session()
|
_local.db_session = Session()
|
||||||
return _local.db_session
|
return _local.db_session
|
||||||
|
|
||||||
|
|
||||||
def _get_cache():
|
def _get_cache() -> Dict:
|
||||||
|
"""获取当前线程的查询缓存。"""
|
||||||
if not hasattr(_local, 'query_cache'):
|
if not hasattr(_local, 'query_cache'):
|
||||||
_local.query_cache = {}
|
_local.query_cache = {}
|
||||||
return _local.query_cache
|
return _local.query_cache
|
||||||
|
|
||||||
|
|
||||||
def _invalidate_cache():
|
def _invalidate_cache() -> None:
|
||||||
|
"""清除当前线程的查询缓存。"""
|
||||||
if hasattr(_local, 'query_cache'):
|
if hasattr(_local, 'query_cache'):
|
||||||
_local.query_cache.clear()
|
_local.query_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 关联字段访问器
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class RelationAccessor:
|
class RelationAccessor:
|
||||||
def __init__(self, model_class, prefix=''):
|
"""关联字段访问器,支持 ``Model.relation.field`` 跨表字段表达式。
|
||||||
|
|
||||||
|
用法::
|
||||||
|
|
||||||
|
# 自动解析关联字段
|
||||||
|
Dingdan.query.filter(Dingdan.shangjia.nicheng == '测试')
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model_class: Type[models.Model], prefix: str = '') -> None:
|
||||||
self._model = model_class
|
self._model = model_class
|
||||||
self._prefix = prefix
|
self._prefix = prefix
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name: str):
|
||||||
if name.startswith('_'):
|
if name.startswith('_'):
|
||||||
return object.__getattribute__(self, name)
|
return object.__getattribute__(self, name)
|
||||||
try:
|
try:
|
||||||
@@ -163,18 +254,26 @@ class RelationAccessor:
|
|||||||
return RelationAccessor(rel.related_model, full_prefix)
|
return RelationAccessor(rel.related_model, full_prefix)
|
||||||
return _get_field_expression_cls()(self._model, name)
|
return _get_field_expression_cls()(self._model, name)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'RelationAccessor({self._model.__name__}, prefix={self._prefix!r})'
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Values 结果集
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class _ValuesResult:
|
class _ValuesResult:
|
||||||
|
"""包装 Django ValuesQuerySet,支持链式调用。
|
||||||
|
|
||||||
|
底层使用 Django ``.values()`` 返回字典,保持与 Django 行为一致。
|
||||||
|
额外提供 ``.all()`` 返回字典列表,``.first()`` 返回字典或 None,
|
||||||
|
``.scalar()`` 返回单字段场景的标量值。
|
||||||
"""
|
"""
|
||||||
包装 Django ValuesQuerySet,支持链式调用。
|
|
||||||
底层使用 Django .values() 返回字典,保持与 Django 行为一致。
|
|
||||||
额外提供 .all() 返回字典列表,.first() 返回字典或 None,
|
|
||||||
.scalar() 返回单字段场景的标量值。
|
|
||||||
"""
|
|
||||||
def __init__(self, qs: models.QuerySet, fields: List[str], original_fields: Tuple) -> None:
|
def __init__(self, qs: models.QuerySet, fields: List[str], original_fields: Tuple) -> None:
|
||||||
self._qs: models.QuerySet = qs # Django ValuesQuerySet(返回字典)
|
self._qs: models.QuerySet = qs
|
||||||
self._fields: List[str] = fields # 转换后的字段名列表(传给 Django .values() 的)
|
self._fields: List[str] = fields
|
||||||
self._original_fields: Tuple = original_fields # 原始传入的字段(用于判断单字段场景)
|
self._original_fields: Tuple = original_fields
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def query(self) -> Any:
|
def query(self) -> Any:
|
||||||
@@ -202,12 +301,15 @@ class _ValuesResult:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def first(self) -> Optional[Dict[str, Any]]:
|
def first(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""返回第一条记录(字典)或 None。"""
|
||||||
return self._qs.first()
|
return self._qs.first()
|
||||||
|
|
||||||
def all(self) -> List[Dict[str, Any]]:
|
def all(self) -> List[Dict[str, Any]]:
|
||||||
|
"""返回所有记录的字典列表。"""
|
||||||
return list(self._qs)
|
return list(self._qs)
|
||||||
|
|
||||||
def scalar(self) -> Any:
|
def scalar(self) -> Any:
|
||||||
|
"""返回单字段场景的标量值。"""
|
||||||
row = self._qs.first()
|
row = self._qs.first()
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
@@ -237,11 +339,18 @@ class _ValuesResult:
|
|||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
return self._qs.exists()
|
return self._qs.exists()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'_ValuesResult(fields={self._fields}, count={self.count()})'
|
||||||
|
|
||||||
class FluentQuery:
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# FluentQuery 核心查询构建器
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class FluentQuery(Generic[_M]):
|
||||||
"""QModel 兼容层查询构建器,提供 SQLAlchemy 风格的链式 OOP 查询接口。
|
"""QModel 兼容层查询构建器,提供 SQLAlchemy 风格的链式 OOP 查询接口。
|
||||||
|
|
||||||
用法示例::
|
由 ``Model.query`` 返回,支持链式调用::
|
||||||
|
|
||||||
# 基础查询
|
# 基础查询
|
||||||
Dingdan.query.filter(zhuangtai=8).all()
|
Dingdan.query.filter(zhuangtai=8).all()
|
||||||
@@ -249,7 +358,7 @@ class FluentQuery:
|
|||||||
# 链式过滤
|
# 链式过滤
|
||||||
Dingdan.query.filter(jine__gt=100).order_by('-create_time').limit(10)
|
Dingdan.query.filter(jine__gt=100).order_by('-create_time').limit(10)
|
||||||
|
|
||||||
# 字段表达式(通过 Model.field 自动获取 FieldExpression)
|
# 字段表达式
|
||||||
Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3]))
|
Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3]))
|
||||||
|
|
||||||
# 聚合
|
# 聚合
|
||||||
@@ -260,10 +369,12 @@ class FluentQuery:
|
|||||||
|
|
||||||
# 分页
|
# 分页
|
||||||
Dingdan.query.paginate(page=1, per_page=20)
|
Dingdan.query.paginate(page=1, per_page=20)
|
||||||
|
|
||||||
|
泛型参数 ``_M`` 携带模型类型,使 IDE 能推断链式调用返回的模型实例类型。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, model_class: Type[models.Model]) -> None:
|
def __init__(self, model_class: Type[_M]) -> None:
|
||||||
self._model: Type[models.Model] = model_class
|
self._model: Type[_M] = model_class
|
||||||
self._qs: models.QuerySet = model_class.objects.all()
|
self._qs: models.QuerySet = model_class.objects.all()
|
||||||
self._joined_models: List[Type[models.Model]] = []
|
self._joined_models: List[Type[models.Model]] = []
|
||||||
self._cache_enabled: bool = True
|
self._cache_enabled: bool = True
|
||||||
@@ -273,7 +384,13 @@ class FluentQuery:
|
|||||||
"""暴露底层 Django QuerySet 的 query 属性,支持 Subquery() 等场景。"""
|
"""暴露底层 Django QuerySet 的 query 属性,支持 Subquery() 等场景。"""
|
||||||
return self._qs.query
|
return self._qs.query
|
||||||
|
|
||||||
def _clone(self) -> FluentQuery:
|
@property
|
||||||
|
def model(self) -> Type[_M]:
|
||||||
|
"""返回关联的模型类。"""
|
||||||
|
return self._model
|
||||||
|
|
||||||
|
def _clone(self) -> FluentQuery[_M]:
|
||||||
|
"""克隆当前查询状态。"""
|
||||||
q = FluentQuery(self._model)
|
q = FluentQuery(self._model)
|
||||||
q._qs = self._qs
|
q._qs = self._qs
|
||||||
q._joined_models = list(self._joined_models)
|
q._joined_models = list(self._joined_models)
|
||||||
@@ -281,6 +398,7 @@ class FluentQuery:
|
|||||||
return q
|
return q
|
||||||
|
|
||||||
def _cache_key(self, method: str) -> Tuple[Any, str, Any]:
|
def _cache_key(self, method: str) -> Tuple[Any, str, Any]:
|
||||||
|
"""生成缓存键。"""
|
||||||
try:
|
try:
|
||||||
query_str = str(self._qs.query)
|
query_str = str(self._qs.query)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -288,6 +406,7 @@ class FluentQuery:
|
|||||||
return (self._model, method, query_str)
|
return (self._model, method, query_str)
|
||||||
|
|
||||||
def _remap_q(self, q: Q) -> Q:
|
def _remap_q(self, q: Q) -> Q:
|
||||||
|
"""重映射 Q 对象中的字段名(用于 join 跨表查询)。"""
|
||||||
new_children: List[Any] = []
|
new_children: List[Any] = []
|
||||||
for child in q.children:
|
for child in q.children:
|
||||||
if isinstance(child, Q):
|
if isinstance(child, Q):
|
||||||
@@ -306,6 +425,7 @@ class FluentQuery:
|
|||||||
return q
|
return q
|
||||||
|
|
||||||
def _remap_key(self, key: str) -> str:
|
def _remap_key(self, key: str) -> str:
|
||||||
|
"""重映射字段名(用于 join 跨表查询自动添加关联前缀)。"""
|
||||||
parts = key.split('__')
|
parts = key.split('__')
|
||||||
field_name = parts[0]
|
field_name = parts[0]
|
||||||
try:
|
try:
|
||||||
@@ -330,7 +450,9 @@ class FluentQuery:
|
|||||||
pass
|
pass
|
||||||
return key
|
return key
|
||||||
|
|
||||||
def filter(self, *args: Any, **kwargs: Any) -> FluentQuery:
|
# ---- 过滤 ----
|
||||||
|
|
||||||
|
def filter(self, *args: Any, **kwargs: Any) -> FluentQuery[_M]:
|
||||||
"""过滤查询,支持 Q 对象和关键字参数,返回 self 以支持链式调用。"""
|
"""过滤查询,支持 Q 对象和关键字参数,返回 self 以支持链式调用。"""
|
||||||
converted_args: List[Any] = []
|
converted_args: List[Any] = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
@@ -341,7 +463,7 @@ class FluentQuery:
|
|||||||
self._qs = self._qs.filter(*converted_args, **kwargs)
|
self._qs = self._qs.filter(*converted_args, **kwargs)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def exclude(self, *args: Any, **kwargs: Any) -> FluentQuery:
|
def exclude(self, *args: Any, **kwargs: Any) -> FluentQuery[_M]:
|
||||||
"""排除查询,返回 self 以支持链式调用。"""
|
"""排除查询,返回 self 以支持链式调用。"""
|
||||||
converted_args: List[Any] = []
|
converted_args: List[Any] = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
@@ -352,7 +474,9 @@ class FluentQuery:
|
|||||||
self._qs = self._qs.exclude(*converted_args, **kwargs)
|
self._qs = self._qs.exclude(*converted_args, **kwargs)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def order_by(self, *args: Any) -> FluentQuery:
|
# ---- 排序 ----
|
||||||
|
|
||||||
|
def order_by(self, *args: Any) -> FluentQuery[_M]:
|
||||||
"""排序,支持字符串和 FieldExpression,返回 self 以支持链式调用。"""
|
"""排序,支持字符串和 FieldExpression,返回 self 以支持链式调用。"""
|
||||||
converted: List[str] = []
|
converted: List[str] = []
|
||||||
for a in args:
|
for a in args:
|
||||||
@@ -365,7 +489,9 @@ class FluentQuery:
|
|||||||
self._qs = self._qs.order_by(*converted)
|
self._qs = self._qs.order_by(*converted)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def first(self) -> Optional[models.Model]:
|
# ---- 获取记录 ----
|
||||||
|
|
||||||
|
def first(self) -> Optional[_M]:
|
||||||
"""返回第一条记录或 None。"""
|
"""返回第一条记录或 None。"""
|
||||||
if self._cache_enabled:
|
if self._cache_enabled:
|
||||||
cache = _get_cache()
|
cache = _get_cache()
|
||||||
@@ -379,13 +505,26 @@ class FluentQuery:
|
|||||||
_get_cache()[self._cache_key('first')] = obj
|
_get_cache()[self._cache_key('first')] = obj
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def all(self) -> FluentQuery:
|
def last(self) -> Optional[_M]:
|
||||||
|
"""返回最后一条记录或 None。"""
|
||||||
|
return self._qs.last()
|
||||||
|
|
||||||
|
def get(self, *args: Any, **kwargs: Any) -> _M:
|
||||||
|
"""获取唯一匹配记录,不存在或多个时抛异常。"""
|
||||||
|
if args:
|
||||||
|
obj = self._qs.filter(*args).get(**kwargs)
|
||||||
|
else:
|
||||||
|
obj = self._qs.get(**kwargs)
|
||||||
|
_get_session().track(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def all(self) -> FluentQuery[_M]:
|
||||||
"""返回 self 以支持链式调用(与 Django QuerySet.all() 一致)。
|
"""返回 self 以支持链式调用(与 Django QuerySet.all() 一致)。
|
||||||
如需获取列表,使用 .to_list() 或 list()。"""
|
如需获取列表,使用 ``.to_list()`` 或 ``list()``。"""
|
||||||
self._qs = self._qs.all()
|
self._qs = self._qs.all()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def to_list(self) -> List[models.Model]:
|
def to_list(self) -> List[_M]:
|
||||||
"""执行查询并返回模型实例列表。"""
|
"""执行查询并返回模型实例列表。"""
|
||||||
if self._cache_enabled:
|
if self._cache_enabled:
|
||||||
cache = _get_cache()
|
cache = _get_cache()
|
||||||
@@ -412,93 +551,37 @@ class FluentQuery:
|
|||||||
_get_cache()[self._cache_key('count')] = result
|
_get_cache()[self._cache_key('count')] = result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get(self, *args: Any, **kwargs: Any) -> models.Model:
|
def exists(self) -> bool:
|
||||||
"""获取唯一匹配记录,不存在或多个时抛异常。"""
|
"""判断是否存在匹配记录。"""
|
||||||
if args:
|
return self._qs.exists()
|
||||||
obj = self._qs.filter(*args).get(**kwargs)
|
|
||||||
else:
|
|
||||||
obj = self._qs.get(**kwargs)
|
|
||||||
_get_session().track(obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def create(self, **kwargs: Any) -> models.Model:
|
# ---- 创建/更新/删除 ----
|
||||||
|
|
||||||
|
def create(self, **kwargs: Any) -> _M:
|
||||||
"""创建并保存新记录。"""
|
"""创建并保存新记录。"""
|
||||||
obj = self._model.objects.create(**kwargs)
|
obj = self._model.objects.create(**kwargs)
|
||||||
_invalidate_cache()
|
_invalidate_cache()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_or_create(self, defaults: Optional[Dict[str, Any]] = None, **kwargs: Any) -> Tuple[models.Model, bool]:
|
def get_or_create(self, defaults: Optional[Dict[str, Any]] = None,
|
||||||
"""查询或创建记录,返回 (obj, created) 元组。"""
|
**kwargs: Any) -> Tuple[_M, bool]:
|
||||||
|
"""查询或创建记录,返回 ``(obj, created)`` 元组。"""
|
||||||
obj, created = self._model.objects.get_or_create(defaults=defaults, **kwargs)
|
obj, created = self._model.objects.get_or_create(defaults=defaults, **kwargs)
|
||||||
if created:
|
if created:
|
||||||
_invalidate_cache()
|
_invalidate_cache()
|
||||||
_get_session().track(obj)
|
_get_session().track(obj)
|
||||||
return obj, created
|
return obj, created
|
||||||
|
|
||||||
def update_or_create(self, defaults: Optional[Dict[str, Any]] = None, **kwargs: Any) -> Tuple[models.Model, bool]:
|
def update_or_create(self, defaults: Optional[Dict[str, Any]] = None,
|
||||||
"""更新或创建记录,返回 (obj, created) 元组。"""
|
**kwargs: Any) -> Tuple[_M, bool]:
|
||||||
|
"""更新或创建记录,返回 ``(obj, created)`` 元组。"""
|
||||||
obj, created = self._model.objects.update_or_create(defaults=defaults, **kwargs)
|
obj, created = self._model.objects.update_or_create(defaults=defaults, **kwargs)
|
||||||
_invalidate_cache()
|
_invalidate_cache()
|
||||||
_get_session().track(obj)
|
_get_session().track(obj)
|
||||||
return obj, created
|
return obj, created
|
||||||
|
|
||||||
def none(self) -> FluentQuery:
|
def bulk_create(self, objs: List[_M], batch_size: Optional[int] = None,
|
||||||
"""返回空结果集,返回 self 以支持链式调用。"""
|
ignore_conflicts: bool = False) -> List[_M]:
|
||||||
self._qs = self._qs.none()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def last(self) -> Optional[models.Model]:
|
|
||||||
"""返回最后一条记录或 None。"""
|
|
||||||
return self._qs.last()
|
|
||||||
|
|
||||||
def limit(self, n: int) -> FluentQuery:
|
|
||||||
"""限制返回记录数,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs[:n]
|
|
||||||
return self
|
|
||||||
|
|
||||||
def offset(self, n: int) -> FluentQuery:
|
|
||||||
"""跳过前 n 条记录,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs[n:]
|
|
||||||
return self
|
|
||||||
|
|
||||||
def join(self, *args: Type[models.Model]) -> FluentQuery:
|
|
||||||
"""关联模型,用于跨表查询自动映射字段,返回 self 以支持链式调用。"""
|
|
||||||
for arg in args:
|
|
||||||
if isinstance(arg, type) and issubclass(arg, models.Model):
|
|
||||||
if arg not in self._joined_models:
|
|
||||||
self._joined_models.append(arg)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def select_related(self, *fields: str) -> FluentQuery:
|
|
||||||
"""外键关联查询优化,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs.select_related(*fields)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def prefetch_related(self, *fields: str) -> FluentQuery:
|
|
||||||
"""多对多/反向关联查询优化,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs.prefetch_related(*fields)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def only(self, *fields: str) -> FluentQuery:
|
|
||||||
"""只加载指定字段,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs.only(*fields)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def defer(self, *fields: str) -> FluentQuery:
|
|
||||||
"""延迟加载指定字段,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs.defer(*fields)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def select_for_update(self, nowait: bool = False, skip_locked: bool = False,
|
|
||||||
of: Tuple = (), no_key: bool = False) -> FluentQuery:
|
|
||||||
"""行级锁,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs.select_for_update(
|
|
||||||
nowait=nowait, skip_locked=skip_locked, of=of, no_key=no_key
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def bulk_create(self, objs: List[models.Model], batch_size: Optional[int] = None,
|
|
||||||
ignore_conflicts: bool = False) -> List[models.Model]:
|
|
||||||
"""批量创建记录。"""
|
"""批量创建记录。"""
|
||||||
result = self._model.objects.bulk_create(
|
result = self._model.objects.bulk_create(
|
||||||
objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts
|
objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts
|
||||||
@@ -506,7 +589,100 @@ class FluentQuery:
|
|||||||
_invalidate_cache()
|
_invalidate_cache()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def annotate(self, **kwargs: Any) -> FluentQuery:
|
def bulk_update(self, objs: List[_M], fields: List[str],
|
||||||
|
batch_size: Optional[int] = None) -> int:
|
||||||
|
"""批量更新记录,返回受影响行数。"""
|
||||||
|
result = self._model.objects.bulk_update(objs, fields, batch_size=batch_size)
|
||||||
|
_invalidate_cache()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def update(self, **kwargs: Any) -> int:
|
||||||
|
"""批量更新匹配记录,返回受影响行数。"""
|
||||||
|
result = self._qs.update(**kwargs)
|
||||||
|
_invalidate_cache()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def delete(self) -> Tuple[int, Dict[str, int]]:
|
||||||
|
"""批量删除匹配记录,返回 ``(删除数, {表名: 删除数})``。"""
|
||||||
|
result = self._qs.delete()
|
||||||
|
_invalidate_cache()
|
||||||
|
return result
|
||||||
|
|
||||||
|
# ---- 查询修饰 ----
|
||||||
|
|
||||||
|
def none(self) -> FluentQuery[_M]:
|
||||||
|
"""返回空结果集,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs.none()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def limit(self, n: int) -> FluentQuery[_M]:
|
||||||
|
"""限制返回记录数,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs[:n]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def offset(self, n: int) -> FluentQuery[_M]:
|
||||||
|
"""跳过前 n 条记录,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs[n:]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def join(self, *args: Type[models.Model]) -> FluentQuery[_M]:
|
||||||
|
"""关联模型,用于跨表查询自动映射字段,返回 self 以支持链式调用。"""
|
||||||
|
for arg in args:
|
||||||
|
if isinstance(arg, type) and issubclass(arg, models.Model):
|
||||||
|
if arg not in self._joined_models:
|
||||||
|
self._joined_models.append(arg)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select_related(self, *fields: str) -> FluentQuery[_M]:
|
||||||
|
"""外键关联查询优化,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs.select_related(*fields)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def prefetch_related(self, *fields: str) -> FluentQuery[_M]:
|
||||||
|
"""多对多/反向关联查询优化,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs.prefetch_related(*fields)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def only(self, *fields: str) -> FluentQuery[_M]:
|
||||||
|
"""只加载指定字段,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs.only(*fields)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def defer(self, *fields: str) -> FluentQuery[_M]:
|
||||||
|
"""延迟加载指定字段,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs.defer(*fields)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select_for_update(self, nowait: bool = False, skip_locked: bool = False,
|
||||||
|
of: Tuple = (), no_key: bool = False) -> FluentQuery[_M]:
|
||||||
|
"""行级锁,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs.select_for_update(
|
||||||
|
nowait=nowait, skip_locked=skip_locked, of=of, no_key=no_key
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def distinct(self) -> FluentQuery[_M]:
|
||||||
|
"""去重查询,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs.distinct()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def using(self, alias: str) -> FluentQuery[_M]:
|
||||||
|
"""指定数据库别名,返回 self 以支持链式调用。"""
|
||||||
|
self._qs = self._qs.using(alias)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def no_cache(self) -> FluentQuery[_M]:
|
||||||
|
"""禁用查询缓存,返回 self 以支持链式调用。"""
|
||||||
|
self._cache_enabled = False
|
||||||
|
return self
|
||||||
|
|
||||||
|
def params(self, **kwargs: Any) -> FluentQuery[_M]:
|
||||||
|
"""参数占位(SQLAlchemy 兼容),返回 self 以支持链式调用。"""
|
||||||
|
return self
|
||||||
|
|
||||||
|
# ---- 聚合与注解 ----
|
||||||
|
|
||||||
|
def annotate(self, **kwargs: Any) -> FluentQuery[_M]:
|
||||||
"""注解查询,添加聚合/计算字段,返回 self 以支持链式调用。"""
|
"""注解查询,添加聚合/计算字段,返回 self 以支持链式调用。"""
|
||||||
self._qs = self._qs.annotate(**kwargs)
|
self._qs = self._qs.annotate(**kwargs)
|
||||||
return self
|
return self
|
||||||
@@ -518,7 +694,6 @@ class FluentQuery:
|
|||||||
def values(self, *fields: Union[str, _AggregateExpr, models.Expression]) -> _ValuesResult:
|
def values(self, *fields: Union[str, _AggregateExpr, models.Expression]) -> _ValuesResult:
|
||||||
"""返回指定字段的字典结果,支持链式调用。"""
|
"""返回指定字段的字典结果,支持链式调用。"""
|
||||||
if not fields:
|
if not fields:
|
||||||
# 无参数:返回 Django .values()(所有字段字典)
|
|
||||||
return _ValuesResult(self._qs.values(), [], fields)
|
return _ValuesResult(self._qs.values(), [], fields)
|
||||||
|
|
||||||
annotations: Dict[str, models.Aggregate] = {}
|
annotations: Dict[str, models.Aggregate] = {}
|
||||||
@@ -538,7 +713,6 @@ class FluentQuery:
|
|||||||
qs = self._qs
|
qs = self._qs
|
||||||
if annotations:
|
if annotations:
|
||||||
qs = qs.annotate(**annotations)
|
qs = qs.annotate(**annotations)
|
||||||
# 使用 Django 原生 .values() 返回字典
|
|
||||||
qs = qs.values(*converted)
|
qs = qs.values(*converted)
|
||||||
return _ValuesResult(qs, converted, fields)
|
return _ValuesResult(qs, converted, fields)
|
||||||
|
|
||||||
@@ -546,38 +720,16 @@ class FluentQuery:
|
|||||||
"""values() 的别名,SQLAlchemy 风格。"""
|
"""values() 的别名,SQLAlchemy 风格。"""
|
||||||
return self.values(*fields)
|
return self.values(*fields)
|
||||||
|
|
||||||
def params(self, **kwargs: Any) -> FluentQuery:
|
|
||||||
"""参数占位(SQLAlchemy 兼容),返回 self 以支持链式调用。"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def values_list(self, *fields: str, flat: bool = False) -> Any:
|
def values_list(self, *fields: str, flat: bool = False) -> Any:
|
||||||
"""返回指定字段的元组列表。"""
|
"""返回指定字段的元组列表。"""
|
||||||
return self._qs.values_list(*fields, flat=flat)
|
return self._qs.values_list(*fields, flat=flat)
|
||||||
|
|
||||||
def update(self, **kwargs: Any) -> int:
|
# ---- 分页 ----
|
||||||
"""批量更新匹配记录,返回受影响行数。"""
|
|
||||||
result = self._qs.update(**kwargs)
|
|
||||||
_invalidate_cache()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def delete(self) -> Tuple[int, Dict[str, int]]:
|
def paginate(self, page: int = 1, per_page: int = 20,
|
||||||
"""批量删除匹配记录,返回 (删除数, {表名: 删除数})。"""
|
error_out: bool = False) -> Any:
|
||||||
result = self._qs.delete()
|
|
||||||
_invalidate_cache()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def exists(self) -> bool:
|
|
||||||
"""判断是否存在匹配记录。"""
|
|
||||||
return self._qs.exists()
|
|
||||||
|
|
||||||
def distinct(self) -> FluentQuery:
|
|
||||||
"""去重查询,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs.distinct()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def paginate(self, page: int = 1, per_page: int = 20, error_out: bool = False) -> Any:
|
|
||||||
"""分页查询,返回 Page 对象(含 items, total, pages 等属性)。"""
|
"""分页查询,返回 Page 对象(含 items, total, pages 等属性)。"""
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator, EmptyPage
|
||||||
if not self._qs.ordered:
|
if not self._qs.ordered:
|
||||||
meta = getattr(self._model, '_meta', None)
|
meta = getattr(self._model, '_meta', None)
|
||||||
if meta and meta.pk:
|
if meta and meta.pk:
|
||||||
@@ -585,7 +737,7 @@ class FluentQuery:
|
|||||||
paginator = Paginator(self._qs, per_page)
|
paginator = Paginator(self._qs, per_page)
|
||||||
try:
|
try:
|
||||||
page_obj = paginator.page(page)
|
page_obj = paginator.page(page)
|
||||||
except Exception:
|
except EmptyPage:
|
||||||
page_obj = paginator.page(1)
|
page_obj = paginator.page(1)
|
||||||
page_obj.total = paginator.count
|
page_obj.total = paginator.count
|
||||||
page_obj.items = page_obj.object_list
|
page_obj.items = page_obj.object_list
|
||||||
@@ -598,26 +750,17 @@ class FluentQuery:
|
|||||||
page_obj.prev_num = page_obj.previous_page_number() if page_obj.has_prev else None
|
page_obj.prev_num = page_obj.previous_page_number() if page_obj.has_prev else None
|
||||||
return page_obj
|
return page_obj
|
||||||
|
|
||||||
def no_cache(self) -> FluentQuery:
|
# ---- 魔术方法 ----
|
||||||
"""禁用查询缓存,返回 self 以支持链式调用。"""
|
|
||||||
self._cache_enabled = False
|
|
||||||
return self
|
|
||||||
|
|
||||||
def using(self, alias: str) -> FluentQuery:
|
|
||||||
"""指定数据库别名,返回 self 以支持链式调用。"""
|
|
||||||
self._qs = self._qs.using(alias)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __getitem__(self, key: Union[int, slice]) -> Any:
|
def __getitem__(self, key: Union[int, slice]) -> Any:
|
||||||
"""支持切片 [start:end] 和索引 [n]。切片返回 self,索引返回模型实例。"""
|
"""支持切片 ``[start:end]`` 和索引 ``[n]``。切片返回 self,索引返回模型实例。"""
|
||||||
if isinstance(key, slice):
|
if isinstance(key, slice):
|
||||||
self._qs = self._qs[key]
|
self._qs = self._qs[key]
|
||||||
return self
|
return self
|
||||||
else:
|
else:
|
||||||
# 单个索引,返回模型实例
|
|
||||||
return self._qs[key]
|
return self._qs[key]
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[models.Model]:
|
def __iter__(self) -> Iterator[_M]:
|
||||||
return iter(self._qs)
|
return iter(self._qs)
|
||||||
|
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
@@ -630,25 +773,43 @@ class FluentQuery:
|
|||||||
return f'FluentQuery({self._model.__name__})'
|
return f'FluentQuery({self._model.__name__})'
|
||||||
|
|
||||||
|
|
||||||
class Session:
|
# ---------------------------------------------------------------------------
|
||||||
def __init__(self):
|
# Session 会话管理
|
||||||
self._pending_adds = []
|
# ---------------------------------------------------------------------------
|
||||||
self._pending_deletes = []
|
|
||||||
self._tracked = set()
|
|
||||||
|
|
||||||
def add(self, obj):
|
class Session:
|
||||||
|
"""简易会话管理器,支持 add/delete/commit/rollback 操作。
|
||||||
|
|
||||||
|
用法::
|
||||||
|
|
||||||
|
session = db.session
|
||||||
|
session.add(obj)
|
||||||
|
session.commit()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._pending_adds: List[models.Model] = []
|
||||||
|
self._pending_deletes: List[models.Model] = []
|
||||||
|
self._tracked: set = set()
|
||||||
|
|
||||||
|
def add(self, obj: models.Model) -> None:
|
||||||
|
"""添加对象到待保存列表。"""
|
||||||
self._pending_adds.append(obj)
|
self._pending_adds.append(obj)
|
||||||
|
|
||||||
def delete(self, obj):
|
def delete(self, obj: models.Model) -> None:
|
||||||
|
"""添加对象到待删除列表。"""
|
||||||
self._pending_deletes.append(obj)
|
self._pending_deletes.append(obj)
|
||||||
self._tracked.discard(obj)
|
self._tracked.discard(obj)
|
||||||
|
|
||||||
def track(self, obj):
|
def track(self, obj: models.Model) -> None:
|
||||||
|
"""跟踪对象变更(commit 时自动 save)。"""
|
||||||
if isinstance(obj, models.Model):
|
if isinstance(obj, models.Model):
|
||||||
self._tracked.add(obj)
|
self._tracked.add(obj)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self) -> None:
|
||||||
delete_pks = {(obj.__class__, obj.pk) for obj in self._pending_deletes if obj.pk is not None}
|
"""提交所有待处理操作(删除 → 新增 → 更新)。"""
|
||||||
|
delete_pks = {(obj.__class__, obj.pk) for obj in self._pending_deletes
|
||||||
|
if obj.pk is not None}
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for obj in self._pending_deletes:
|
for obj in self._pending_deletes:
|
||||||
obj.delete()
|
obj.delete()
|
||||||
@@ -660,21 +821,23 @@ class Session:
|
|||||||
try:
|
try:
|
||||||
obj.save()
|
obj.save()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
logger.debug('Session.commit: skip tracked obj %s', obj, exc_info=True)
|
||||||
self._pending_adds.clear()
|
self._pending_adds.clear()
|
||||||
self._pending_deletes.clear()
|
self._pending_deletes.clear()
|
||||||
self._tracked.clear()
|
self._tracked.clear()
|
||||||
_invalidate_cache()
|
_invalidate_cache()
|
||||||
|
|
||||||
def rollback(self):
|
def rollback(self) -> None:
|
||||||
|
"""回滚所有待处理操作。"""
|
||||||
self._pending_adds.clear()
|
self._pending_adds.clear()
|
||||||
self._pending_deletes.clear()
|
self._pending_deletes.clear()
|
||||||
self._tracked.clear()
|
self._tracked.clear()
|
||||||
_invalidate_cache()
|
_invalidate_cache()
|
||||||
|
|
||||||
def query(self, *args):
|
def query(self, *args) -> Optional[FluentQuery[Any]]:
|
||||||
model = None
|
"""创建查询构建器,支持传入模型类或聚合表达式。"""
|
||||||
aggregates = []
|
model: Optional[Type[models.Model]] = None
|
||||||
|
aggregates: List = []
|
||||||
for a in args:
|
for a in args:
|
||||||
if isinstance(a, type) and issubclass(a, models.Model):
|
if isinstance(a, type) and issubclass(a, models.Model):
|
||||||
model = a
|
model = a
|
||||||
@@ -691,10 +854,12 @@ class Session:
|
|||||||
return fq.values(*args)
|
return fq.values(*args)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def flush(self):
|
def flush(self) -> None:
|
||||||
|
"""flush() 的别名,等同于 commit()。"""
|
||||||
self.commit()
|
self.commit()
|
||||||
|
|
||||||
def execute(self, stmt):
|
def execute(self, stmt) -> None:
|
||||||
|
"""执行语句(支持 _DeleteStmt 和原始 SQL 字符串)。"""
|
||||||
if isinstance(stmt, _DeleteStmt):
|
if isinstance(stmt, _DeleteStmt):
|
||||||
stmt.execute()
|
stmt.execute()
|
||||||
elif isinstance(stmt, str):
|
elif isinstance(stmt, str):
|
||||||
@@ -703,14 +868,31 @@ class Session:
|
|||||||
cursor.execute(stmt)
|
cursor.execute(stmt)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 全局入口
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class DB:
|
class DB:
|
||||||
|
"""全局数据库操作入口。
|
||||||
|
|
||||||
|
用法::
|
||||||
|
|
||||||
|
from gvsdsdk.fluent import db
|
||||||
|
|
||||||
|
session = db.session
|
||||||
|
q = db.or_(Model.field == 1, Model.field == 2)
|
||||||
|
q = db.and_(Model.field > 0, Model.field < 100)
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def session(self):
|
def session(self) -> Session:
|
||||||
|
"""获取当前线程的 Session 实例。"""
|
||||||
return _get_session()
|
return _get_session()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def or_(*args):
|
def or_(*args: Q) -> Q:
|
||||||
result = None
|
"""逻辑 OR,合并多个 Q 对象。"""
|
||||||
|
result: Optional[Q] = None
|
||||||
for q in args:
|
for q in args:
|
||||||
if isinstance(q, Q):
|
if isinstance(q, Q):
|
||||||
if result is None:
|
if result is None:
|
||||||
@@ -720,7 +902,8 @@ class DB:
|
|||||||
return result if result is not None else Q()
|
return result if result is not None else Q()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def and_(*args):
|
def and_(*args: Q) -> Q:
|
||||||
|
"""逻辑 AND,合并多个 Q 对象。"""
|
||||||
result = Q()
|
result = Q()
|
||||||
for q in args:
|
for q in args:
|
||||||
if isinstance(q, Q):
|
if isinstance(q, Q):
|
||||||
@@ -731,5 +914,6 @@ class DB:
|
|||||||
db = DB()
|
db = DB()
|
||||||
|
|
||||||
|
|
||||||
def FQ(model_class):
|
def FQ(model_class: Type[_M]) -> FluentQuery[_M]:
|
||||||
|
"""FluentQuery 工厂函数,等同于 ``Model.query``。"""
|
||||||
return FluentQuery(model_class)
|
return FluentQuery(model_class)
|
||||||
|
|||||||
@@ -1,58 +1,81 @@
|
|||||||
|
"""QModel 兼容层基类模块
|
||||||
|
|
||||||
|
提供 SQLAlchemy 风格的链式 OOP 查询接口,使 Django ORM 支持:
|
||||||
|
|
||||||
|
- ``Model.query.filter(...)`` — 链式 FluentQuery 查询
|
||||||
|
- ``Model.field_name`` — 类级别返回 FieldExpression(支持 ``==``, ``>``, ``in_()`` 等运算符)
|
||||||
|
- ``Model.field_name`` — 实例级别返回字段值(与原生 Django Model 一致)
|
||||||
|
- ``Model[condition]`` — 快捷 filter 语法
|
||||||
|
|
||||||
|
典型用法::
|
||||||
|
|
||||||
|
# 链式查询
|
||||||
|
orders = Dingdan.query.filter(zhuangtai=8).order_by('-create_time').to_list()
|
||||||
|
|
||||||
|
# 字段表达式
|
||||||
|
Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1, 2, 3]))
|
||||||
|
|
||||||
|
# 聚合
|
||||||
|
Dingdan.query.aggregate(total=Sum('jine'))
|
||||||
|
|
||||||
|
# 分页
|
||||||
|
Dingdan.query.paginate(page=1, per_page=20)
|
||||||
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import typing
|
import re
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
from typing import Any, Callable, Dict, Generic, Optional, Self, Tuple, Type, TypeVar, Union
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from .fluent import FluentQuery
|
from .fluent import FluentQuery
|
||||||
|
|
||||||
|
|
||||||
class classproperty:
|
# ---------------------------------------------------------------------------
|
||||||
def __init__(self, func):
|
# 工具函数
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#: classproperty 返回值类型变量
|
||||||
|
_RT = TypeVar('_RT')
|
||||||
|
|
||||||
|
#: QModel 子类类型变量(仅用于 __class_getitem__ 兼容)
|
||||||
|
_QM = TypeVar('_QM', bound='QModel')
|
||||||
|
|
||||||
|
|
||||||
|
class classproperty(Generic[_RT]):
|
||||||
|
"""类属性描述符,支持 ``Model.query`` 等类级别属性访问。
|
||||||
|
|
||||||
|
泛型参数 ``_RT`` 传播函数返回类型,使 Pylance 能穿透描述符推断
|
||||||
|
``Model.query`` 的返回类型为 ``FluentQuery[Self]``。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, func: Callable[[type], _RT]) -> None:
|
||||||
self.fget = func
|
self.fget = func
|
||||||
|
|
||||||
def __get__(self, obj, objtype=None):
|
def __get__(self, obj: Any, objtype: Optional[type] = None) -> _RT:
|
||||||
return self.fget(objtype if objtype is not None else type(obj))
|
return self.fget(objtype if objtype is not None else type(obj))
|
||||||
|
|
||||||
|
|
||||||
ABBREVIATIONS = {
|
ABBREVIATIONS: Dict[str, str] = {
|
||||||
'uuid': 'UUID',
|
'uuid': 'UUID', 'id': 'ID', 'ip': 'IP', 'url': 'URL', 'uri': 'URI',
|
||||||
'id': 'ID',
|
'api': 'API', 'html': 'HTML', 'css': 'CSS', 'js': 'JS', 'json': 'JSON',
|
||||||
'ip': 'IP',
|
'xml': 'XML', 'sql': 'SQL', 'http': 'HTTP', 'https': 'HTTPS',
|
||||||
'url': 'URL',
|
'smtp': 'SMTP', 'ssl': 'SSL', 'tls': 'TLS', 'ssh': 'SSH', 'ftp': 'FTP',
|
||||||
'uri': 'URI',
|
'2fa': '2FA', 'elt': 'ELT', 'sve': 'SVE', 't4': 'T4', 'mc': 'MC',
|
||||||
'api': 'API',
|
'crb': 'CRB', 'ccgo': 'CCGO', 'ugm': 'UGM', 'gerp': 'GERP',
|
||||||
'html': 'HTML',
|
'abac': 'ABAC', 'rbac': 'RBAC', 'smtm': 'SMTM', 'pwd': 'PWD',
|
||||||
'css': 'CSS',
|
|
||||||
'js': 'JS',
|
|
||||||
'json': 'JSON',
|
|
||||||
'xml': 'XML',
|
|
||||||
'sql': 'SQL',
|
|
||||||
'http': 'HTTP',
|
|
||||||
'https': 'HTTPS',
|
|
||||||
'smtp': 'SMTP',
|
|
||||||
'ssl': 'SSL',
|
|
||||||
'tls': 'TLS',
|
|
||||||
'ssh': 'SSH',
|
|
||||||
'ftp': 'FTP',
|
|
||||||
'2fa': '2FA',
|
|
||||||
'elt': 'ELT',
|
|
||||||
'sve': 'SVE',
|
|
||||||
't4': 'T4',
|
|
||||||
'mc': 'MC',
|
|
||||||
'crb': 'CRB',
|
|
||||||
'ccgo': 'CCGO',
|
|
||||||
'ugm': 'UGM',
|
|
||||||
'gerp': 'GERP',
|
|
||||||
'abac': 'ABAC',
|
|
||||||
'rbac': 'RBAC',
|
|
||||||
'smtm': 'SMTM',
|
|
||||||
'pwd': 'PWD',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def snake_to_pascal(name):
|
def snake_to_pascal(name: str) -> str:
|
||||||
|
"""将 snake_case 转换为 PascalCase,自动识别常见缩写。
|
||||||
|
|
||||||
|
>>> snake_to_pascal('user_uuid')
|
||||||
|
'UserUUID'
|
||||||
|
>>> snake_to_pascal('paifa_dingdan_shu')
|
||||||
|
'PaifaDingdanShu'
|
||||||
|
"""
|
||||||
if '_' not in name:
|
if '_' not in name:
|
||||||
return name[0].upper() + name[1:] if name else name
|
return name[0].upper() + name[1:] if name else name
|
||||||
parts = name.split('_')
|
parts = name.split('_')
|
||||||
@@ -66,46 +89,74 @@ def snake_to_pascal(name):
|
|||||||
return ''.join(result)
|
return ''.join(result)
|
||||||
|
|
||||||
|
|
||||||
def pascal_to_snake(name):
|
def pascal_to_snake(name: str) -> str:
|
||||||
import re
|
"""将 PascalCase 转换为 snake_case。
|
||||||
|
|
||||||
|
>>> pascal_to_snake('UserUUID')
|
||||||
|
'user_uuid'
|
||||||
|
"""
|
||||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||||
s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1)
|
s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1)
|
||||||
parts = s2.lower().split('_')
|
return s2.lower()
|
||||||
return '_'.join(parts)
|
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 字段表达式(类级别访问 Model.field 时返回)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class _InvertedFieldExpression:
|
class _InvertedFieldExpression:
|
||||||
def __init__(self, field_expr):
|
"""取反字段表达式,由 ``~Model.field`` 产生,支持 ``~field.startswith(...)`` 等否定查询。"""
|
||||||
|
|
||||||
|
def __init__(self, field_expr: FieldExpression) -> None:
|
||||||
self._field_expr = field_expr
|
self._field_expr = field_expr
|
||||||
|
|
||||||
def startswith(self, value):
|
def startswith(self, value: str) -> Q:
|
||||||
return ~Q(**{f'{self._field_expr._django_name}__startswith': value})
|
return ~Q(**{f'{self._field_expr._django_name}__startswith': value})
|
||||||
|
|
||||||
def contains(self, value):
|
def contains(self, value: str) -> Q:
|
||||||
return ~Q(**{f'{self._field_expr._django_name}__contains': value})
|
return ~Q(**{f'{self._field_expr._django_name}__contains': value})
|
||||||
|
|
||||||
def like(self, pattern):
|
def like(self, pattern: str) -> Q:
|
||||||
clean = pattern.strip('%')
|
clean = pattern.strip('%')
|
||||||
return ~Q(**{f'{self._field_expr._django_name}__icontains': clean})
|
return ~Q(**{f'{self._field_expr._django_name}__icontains': clean})
|
||||||
|
|
||||||
def in_(self, values):
|
def in_(self, values) -> Q:
|
||||||
return ~Q(**{f'{self._field_expr._django_name}__in': values})
|
return ~Q(**{f'{self._field_expr._django_name}__in': values})
|
||||||
|
|
||||||
def is_(self, other):
|
def is_(self, other) -> Q:
|
||||||
if other is None:
|
if other is None:
|
||||||
return ~Q(**{f'{self._field_expr._django_name}__isnull': True})
|
return ~Q(**{f'{self._field_expr._django_name}__isnull': True})
|
||||||
return ~Q(**{self._field_expr._django_name: other})
|
return ~Q(**{self._field_expr._django_name: other})
|
||||||
|
|
||||||
|
def between(self, low, high) -> Q:
|
||||||
|
return ~(Q(**{f'{self._field_expr._django_name}__gte': low})
|
||||||
|
& Q(**{f'{self._field_expr._django_name}__lte': high}))
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'~{self._field_expr!r}'
|
||||||
|
|
||||||
|
|
||||||
class FieldExpression:
|
class FieldExpression:
|
||||||
_resolve_cache = {}
|
"""字段表达式,由类级别 ``Model.field_name`` 访问产生。
|
||||||
|
|
||||||
def __init__(self, model_class, field_name):
|
支持通过 Python 运算符构建 Django Q 对象::
|
||||||
|
|
||||||
|
Model.field == value → Q(field=value)
|
||||||
|
Model.field > value → Q(field__gt=value)
|
||||||
|
Model.field.in_([1,2,3]) → Q(field__in=[1,2,3])
|
||||||
|
Model.field.between(a, b) → Q(field__gte=a) & Q(field__lte=b)
|
||||||
|
~Model.field → _InvertedFieldExpression(否定查询)
|
||||||
|
"""
|
||||||
|
|
||||||
|
_resolve_cache: Dict[Tuple[str, str], str] = {}
|
||||||
|
|
||||||
|
def __init__(self, model_class: Type, field_name: str) -> None:
|
||||||
self.model_class = model_class
|
self.model_class = model_class
|
||||||
self.field_name = field_name
|
self.field_name = field_name
|
||||||
self._django_name = self._resolve_django_name()
|
self._django_name: str = self._resolve_django_name()
|
||||||
|
|
||||||
def _resolve_django_name(self):
|
def _resolve_django_name(self) -> str:
|
||||||
|
"""将 Python 字段名解析为 Django ORM 查询使用的字段名。"""
|
||||||
cache_key = (self.model_class.__name__, self.field_name)
|
cache_key = (self.model_class.__name__, self.field_name)
|
||||||
if cache_key in FieldExpression._resolve_cache:
|
if cache_key in FieldExpression._resolve_cache:
|
||||||
return FieldExpression._resolve_cache[cache_key]
|
return FieldExpression._resolve_cache[cache_key]
|
||||||
@@ -117,243 +168,232 @@ class FieldExpression:
|
|||||||
FieldExpression._resolve_cache[cache_key] = self.field_name
|
FieldExpression._resolve_cache[cache_key] = self.field_name
|
||||||
return self.field_name
|
return self.field_name
|
||||||
|
|
||||||
def __eq__(self, other):
|
# ---- 比较运算符 ----
|
||||||
|
|
||||||
|
def __eq__(self, other) -> Q: # type: ignore[override]
|
||||||
if isinstance(other, FieldExpression):
|
if isinstance(other, FieldExpression):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
if other is None:
|
if other is None:
|
||||||
return Q(**{f'{self._django_name}__isnull': True})
|
return Q(**{f'{self._django_name}__isnull': True})
|
||||||
return Q(**{self._django_name: other})
|
return Q(**{self._django_name: other})
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other) -> Q: # type: ignore[override]
|
||||||
if isinstance(other, FieldExpression):
|
if isinstance(other, FieldExpression):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return ~Q(**{self._django_name: other})
|
return ~Q(**{self._django_name: other})
|
||||||
|
|
||||||
def __gt__(self, other):
|
def __gt__(self, other) -> Q:
|
||||||
return Q(**{f'{self._django_name}__gt': other})
|
return Q(**{f'{self._django_name}__gt': other})
|
||||||
|
|
||||||
def __ge__(self, other):
|
def __ge__(self, other) -> Q:
|
||||||
return Q(**{f'{self._django_name}__gte': other})
|
return Q(**{f'{self._django_name}__gte': other})
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other) -> Q:
|
||||||
return Q(**{f'{self._django_name}__lt': other})
|
return Q(**{f'{self._django_name}__lt': other})
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other) -> Q:
|
||||||
return Q(**{f'{self._django_name}__lte': other})
|
return Q(**{f'{self._django_name}__lte': other})
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __hash__(self) -> int:
|
||||||
return _JsonKeyExpression(self._django_name, key)
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.model_class.__name__, self.field_name))
|
return hash((self.model_class.__name__, self.field_name))
|
||||||
|
|
||||||
def __and__(self, other):
|
# ---- 逻辑运算符 ----
|
||||||
|
|
||||||
|
def __and__(self, other) -> Q:
|
||||||
if isinstance(other, Q):
|
if isinstance(other, Q):
|
||||||
return Q(self) & other
|
return Q(self) & other
|
||||||
|
if isinstance(other, FieldExpression):
|
||||||
|
return Q(self) & Q(other)
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __or__(self, other):
|
def __or__(self, other) -> Q:
|
||||||
if isinstance(other, Q):
|
if isinstance(other, Q):
|
||||||
return Q(self) | other
|
return Q(self) | other
|
||||||
|
if isinstance(other, FieldExpression):
|
||||||
|
return Q(self) | Q(other)
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __invert__(self):
|
def __invert__(self) -> _InvertedFieldExpression:
|
||||||
return _InvertedFieldExpression(self)
|
return _InvertedFieldExpression(self)
|
||||||
|
|
||||||
def is_(self, other):
|
# ---- 查询方法 ----
|
||||||
|
|
||||||
|
def is_(self, other) -> Q:
|
||||||
|
"""``field IS NULL`` 或 ``field IS value``。"""
|
||||||
if other is None:
|
if other is None:
|
||||||
return Q(**{f'{self._django_name}__isnull': True})
|
return Q(**{f'{self._django_name}__isnull': True})
|
||||||
return Q(**{self._django_name: other})
|
return Q(**{self._django_name: other})
|
||||||
|
|
||||||
def is_not(self, other):
|
def is_not(self, other) -> Q:
|
||||||
|
"""``field IS NOT NULL`` 或 ``field IS NOT value``。"""
|
||||||
if other is None:
|
if other is None:
|
||||||
return Q(**{f'{self._django_name}__isnull': False})
|
return Q(**{f'{self._django_name}__isnull': False})
|
||||||
return ~Q(**{self._django_name: other})
|
return ~Q(**{self._django_name: other})
|
||||||
|
|
||||||
def like(self, pattern):
|
def like(self, pattern: str) -> Q:
|
||||||
|
"""模糊匹配(不区分大小写),自动去除 ``%`` 通配符。"""
|
||||||
clean = pattern.strip('%')
|
clean = pattern.strip('%')
|
||||||
return Q(**{f'{self._django_name}__icontains': clean})
|
return Q(**{f'{self._django_name}__icontains': clean})
|
||||||
|
|
||||||
def contains(self, value):
|
def contains(self, value: str) -> Q:
|
||||||
|
"""包含子串(区分大小写)。"""
|
||||||
return Q(**{f'{self._django_name}__contains': value})
|
return Q(**{f'{self._django_name}__contains': value})
|
||||||
|
|
||||||
def ilike(self, pattern):
|
def ilike(self, pattern: str) -> Q:
|
||||||
|
"""like() 的别名。"""
|
||||||
return self.like(pattern)
|
return self.like(pattern)
|
||||||
|
|
||||||
def startswith(self, value):
|
def startswith(self, value: str) -> Q:
|
||||||
|
"""前缀匹配(区分大小写)。"""
|
||||||
return Q(**{f'{self._django_name}__startswith': value})
|
return Q(**{f'{self._django_name}__startswith': value})
|
||||||
|
|
||||||
def istartswith(self, value):
|
def istartswith(self, value: str) -> Q:
|
||||||
|
"""前缀匹配(不区分大小写)。"""
|
||||||
return Q(**{f'{self._django_name}__istartswith': value})
|
return Q(**{f'{self._django_name}__istartswith': value})
|
||||||
|
|
||||||
def endswith(self, value):
|
def endswith(self, value: str) -> Q:
|
||||||
|
"""后缀匹配(区分大小写)。"""
|
||||||
return Q(**{f'{self._django_name}__endswith': value})
|
return Q(**{f'{self._django_name}__endswith': value})
|
||||||
|
|
||||||
def iendswith(self, value):
|
def iendswith(self, value: str) -> Q:
|
||||||
|
"""后缀匹配(不区分大小写)。"""
|
||||||
return Q(**{f'{self._django_name}__iendswith': value})
|
return Q(**{f'{self._django_name}__iendswith': value})
|
||||||
|
|
||||||
def in_(self, values):
|
def in_(self, values) -> Q:
|
||||||
|
"""``field IN (values)``,支持列表和 FluentQuery 子查询。"""
|
||||||
if hasattr(values, '_qs'):
|
if hasattr(values, '_qs'):
|
||||||
return Q(**{f'{self._django_name}__in': values._qs.values(self._django_name)})
|
return Q(**{f'{self._django_name}__in': values._qs.values(self._django_name)})
|
||||||
return Q(**{f'{self._django_name}__in': values})
|
return Q(**{f'{self._django_name}__in': values})
|
||||||
|
|
||||||
def between(self, low, high):
|
def between(self, low, high) -> Q:
|
||||||
|
"""``field BETWEEN low AND high``。"""
|
||||||
return Q(**{f'{self._django_name}__gte': low}) & Q(**{f'{self._django_name}__lte': high})
|
return Q(**{f'{self._django_name}__gte': low}) & Q(**{f'{self._django_name}__lte': high})
|
||||||
|
|
||||||
def regex(self, pattern):
|
def regex(self, pattern: str) -> Q:
|
||||||
|
"""正则匹配(区分大小写)。"""
|
||||||
return Q(**{f'{self._django_name}__regex': pattern})
|
return Q(**{f'{self._django_name}__regex': pattern})
|
||||||
|
|
||||||
def iregex(self, pattern):
|
def iregex(self, pattern: str) -> Q:
|
||||||
|
"""正则匹配(不区分大小写)。"""
|
||||||
return Q(**{f'{self._django_name}__iregex': pattern})
|
return Q(**{f'{self._django_name}__iregex': pattern})
|
||||||
|
|
||||||
def desc(self):
|
# ---- 排序辅助 ----
|
||||||
|
|
||||||
|
def desc(self) -> str:
|
||||||
|
"""返回降序排序字符串 ``'-field_name'``。"""
|
||||||
return f'-{self._django_name}'
|
return f'-{self._django_name}'
|
||||||
|
|
||||||
def asc(self):
|
def asc(self) -> str:
|
||||||
|
"""返回升序排序字符串 ``'field_name'``。"""
|
||||||
return self._django_name
|
return self._django_name
|
||||||
|
|
||||||
def __repr__(self):
|
# ---- JSON 字段访问 ----
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> _JsonKeyExpression:
|
||||||
|
"""访问 JSON 字段内的键,如 ``Model.meta['key'] == value``。"""
|
||||||
|
return _JsonKeyExpression(self._django_name, key)
|
||||||
|
|
||||||
|
# ---- 显示 ----
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
return f'FieldExpr({self.model_class.__name__}.{self.field_name})'
|
return f'FieldExpr({self.model_class.__name__}.{self.field_name})'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self._django_name
|
return self._django_name
|
||||||
|
|
||||||
|
|
||||||
class _JsonKeyExpression:
|
class _JsonKeyExpression:
|
||||||
def __init__(self, field_name, key):
|
"""JSON 字段键表达式,由 ``Model.json_field['key']`` 产生。"""
|
||||||
|
|
||||||
|
def __init__(self, field_name: str, key: str) -> None:
|
||||||
self.field_name = field_name
|
self.field_name = field_name
|
||||||
self.key = key
|
self.key = key
|
||||||
|
|
||||||
def _lookup(self, suffix=''):
|
def _lookup(self, suffix: str = '') -> str:
|
||||||
if suffix:
|
if suffix:
|
||||||
return f'{self.field_name}__{self.key}__{suffix}'
|
return f'{self.field_name}__{self.key}__{suffix}'
|
||||||
return f'{self.field_name}__{self.key}'
|
return f'{self.field_name}__{self.key}'
|
||||||
|
|
||||||
def cast(self, type_hint):
|
def cast(self, type_hint) -> _CastJsonExpression:
|
||||||
|
"""将 JSON 值转换为指定类型后再比较。"""
|
||||||
return _CastJsonExpression(self.field_name, self.key, type_hint)
|
return _CastJsonExpression(self.field_name, self.key, type_hint)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other) -> Q: # type: ignore[override]
|
||||||
return Q(**{self._lookup(): other})
|
return Q(**{self._lookup(): other})
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other) -> Q: # type: ignore[override]
|
||||||
return ~Q(**{self._lookup(): other})
|
return ~Q(**{self._lookup(): other})
|
||||||
|
|
||||||
def __gt__(self, other):
|
def __gt__(self, other) -> Q:
|
||||||
return Q(**{self._lookup('gt'): other})
|
return Q(**{self._lookup('gt'): other})
|
||||||
|
|
||||||
def __ge__(self, other):
|
def __ge__(self, other) -> Q:
|
||||||
return Q(**{self._lookup('gte'): other})
|
return Q(**{self._lookup('gte'): other})
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other) -> Q:
|
||||||
return Q(**{self._lookup('lt'): other})
|
return Q(**{self._lookup('lt'): other})
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other) -> Q:
|
||||||
return Q(**{self._lookup('lte'): other})
|
return Q(**{self._lookup('lte'): other})
|
||||||
|
|
||||||
def between(self, low, high):
|
def between(self, low, high) -> Q:
|
||||||
return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high})
|
return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high})
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f'JsonKeyExpr({self.field_name}[{self.key!r}])'
|
return f'JsonKeyExpr({self.field_name}[{self.key!r}])'
|
||||||
|
|
||||||
|
|
||||||
class _CastJsonExpression:
|
class _CastJsonExpression:
|
||||||
def __init__(self, field_name, key, type_hint):
|
"""类型转换后的 JSON 键表达式。"""
|
||||||
|
|
||||||
|
def __init__(self, field_name: str, key: str, type_hint) -> None:
|
||||||
self.field_name = field_name
|
self.field_name = field_name
|
||||||
self.key = key
|
self.key = key
|
||||||
self.type_hint = type_hint
|
self.type_hint = type_hint
|
||||||
|
|
||||||
def _lookup(self, suffix=''):
|
def _lookup(self, suffix: str = '') -> str:
|
||||||
if suffix:
|
if suffix:
|
||||||
return f'{self.field_name}__{self.key}__{suffix}'
|
return f'{self.field_name}__{self.key}__{suffix}'
|
||||||
return f'{self.field_name}__{self.key}'
|
return f'{self.field_name}__{self.key}'
|
||||||
|
|
||||||
def between(self, low, high):
|
def __eq__(self, other) -> Q: # type: ignore[override]
|
||||||
return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high})
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return Q(**{self._lookup(): other})
|
return Q(**{self._lookup(): other})
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other) -> Q: # type: ignore[override]
|
||||||
return ~Q(**{self._lookup(): other})
|
return ~Q(**{self._lookup(): other})
|
||||||
|
|
||||||
def __gt__(self, other):
|
def __gt__(self, other) -> Q:
|
||||||
return Q(**{self._lookup('gt'): other})
|
return Q(**{self._lookup('gt'): other})
|
||||||
|
|
||||||
def __ge__(self, other):
|
def __ge__(self, other) -> Q:
|
||||||
return Q(**{self._lookup('gte'): other})
|
return Q(**{self._lookup('gte'): other})
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other) -> Q:
|
||||||
return Q(**{self._lookup('lt'): other})
|
return Q(**{self._lookup('lt'): other})
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other) -> Q:
|
||||||
return Q(**{self._lookup('lte'): other})
|
return Q(**{self._lookup('lte'): other})
|
||||||
|
|
||||||
def __repr__(self):
|
def between(self, low, high) -> Q:
|
||||||
|
return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high})
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
type_name = getattr(self.type_hint, '__name__', str(self.type_hint))
|
type_name = getattr(self.type_hint, '__name__', str(self.type_hint))
|
||||||
return f'CastJsonExpr({self.field_name}[{self.key!r}]->{type_name})'
|
return f'CastJsonExpr({self.field_name}[{self.key!r}]->{type_name})'
|
||||||
|
|
||||||
|
|
||||||
class ForeignKeyIdDescriptor:
|
# ---------------------------------------------------------------------------
|
||||||
"""FK 字段描述符:类级别访问返回 FieldExpression,实例级别访问/设置委托给原始 Django 字段。"""
|
# QModel 元类与基类
|
||||||
def __init__(self, fk_name):
|
# ---------------------------------------------------------------------------
|
||||||
self.fk_name = fk_name
|
|
||||||
self.id_attr = fk_name + '_id'
|
|
||||||
self._original_field = None # 延迟绑定原始 Django 字段
|
|
||||||
|
|
||||||
def _get_original_field(self, owner):
|
|
||||||
if self._original_field is None:
|
|
||||||
try:
|
|
||||||
self._original_field = owner._meta.get_field(self.fk_name)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return self._original_field
|
|
||||||
|
|
||||||
def __get__(self, obj, objtype=None):
|
|
||||||
if obj is None:
|
|
||||||
return FieldExpression(objtype, self.id_attr)
|
|
||||||
return getattr(obj, self.id_attr)
|
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
|
||||||
# 直接写入实例 __dict__,避免触发描述符递归
|
|
||||||
obj.__dict__[self.id_attr] = value
|
|
||||||
|
|
||||||
|
|
||||||
class FieldExpressionDescriptor:
|
|
||||||
"""普通字段描述符:类级别访问返回 FieldExpression,实例级别访问/设置委托给原始 Django 字段。"""
|
|
||||||
def __init__(self, field_name):
|
|
||||||
self.field_name = field_name
|
|
||||||
self._original_field = None # 延迟绑定原始 Django 字段
|
|
||||||
|
|
||||||
def _get_original_field(self, owner):
|
|
||||||
if self._original_field is None:
|
|
||||||
try:
|
|
||||||
self._original_field = owner._meta.get_field(self.field_name)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return self._original_field
|
|
||||||
|
|
||||||
def __get__(self, obj, objtype=None):
|
|
||||||
if obj is None:
|
|
||||||
return FieldExpression(objtype, self.field_name)
|
|
||||||
# 实例级别:从 __dict__ 获取,或让 Django 字段描述符处理
|
|
||||||
if self.field_name in obj.__dict__:
|
|
||||||
return obj.__dict__[self.field_name]
|
|
||||||
# 委托给原始 Django 字段
|
|
||||||
orig = self._get_original_field(objtype)
|
|
||||||
if orig is not None:
|
|
||||||
return orig.__get__(obj, objtype)
|
|
||||||
return getattr(obj, self.field_name)
|
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
|
||||||
# 直接写入实例 __dict__,避免触发描述符递归
|
|
||||||
obj.__dict__[self.field_name] = value
|
|
||||||
|
|
||||||
|
|
||||||
class QModelBase(models.base.ModelBase):
|
class QModelBase(models.base.ModelBase):
|
||||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
"""QModel 元类,负责:
|
||||||
# 不再强制设置 db_column —— Django 会自动处理
|
|
||||||
|
|
||||||
|
1. 根据 ``GVSDSDK_MANAGE_TABLES`` 设置动态控制 ``managed``
|
||||||
|
2. 包装字段描述符,使类级别访问返回 FieldExpression
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||||
# 动态设置 managed:子服务器可设 GVSDSDK_MANAGE_TABLES=True 让 gvsdsdk 管理表
|
# 动态设置 managed:子服务器可设 GVSDSDK_MANAGE_TABLES=True 让 gvsdsdk 管理表
|
||||||
meta = namespace.get('Meta')
|
meta = namespace.get('Meta')
|
||||||
if meta and getattr(meta, 'app_label', None) == 'gvsdsdk':
|
if meta and getattr(meta, 'app_label', None) == 'gvsdsdk':
|
||||||
@@ -378,12 +418,12 @@ def _wrap_field_descriptors(cls: Type) -> None:
|
|||||||
实例级别完全委托给 Django 原生描述符。
|
实例级别完全委托给 Django 原生描述符。
|
||||||
|
|
||||||
Django 的字段描述符(DeferredAttribute / ForwardManyToOneDescriptor):
|
Django 的字段描述符(DeferredAttribute / ForwardManyToOneDescriptor):
|
||||||
- __get__(None, cls) → 返回字段对象本身(类级别)
|
- ``__get__(None, cls)`` → 返回字段对象本身(类级别)
|
||||||
- __get__(instance, cls) → 返回字段值(实例级别)
|
- ``__get__(instance, cls)`` → 返回字段值(实例级别)
|
||||||
|
|
||||||
我们包装后:
|
我们包装后:
|
||||||
- __get__(None, cls) → 返回 FieldExpression(类级别,用于链式 OOP)
|
- ``__get__(None, cls)`` → 返回 FieldExpression(类级别,用于链式 OOP)
|
||||||
- __get__(instance, cls) → 委托给原生描述符(实例级别,正常行为)
|
- ``__get__(instance, cls)`` → 委托给原生描述符(实例级别,正常行为)
|
||||||
"""
|
"""
|
||||||
from django.db.models.query_utils import DeferredAttribute
|
from django.db.models.query_utils import DeferredAttribute
|
||||||
from django.db.models.fields.related_descriptors import (
|
from django.db.models.fields.related_descriptors import (
|
||||||
@@ -408,13 +448,18 @@ def _wrap_field_descriptors(cls: Type) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class _FieldExpressionWrapper:
|
class _FieldExpressionWrapper:
|
||||||
"""包装 Django 字段描述符,类级别返回 FieldExpression,实例级别委托给原生描述符。"""
|
"""包装 Django 字段描述符,类级别返回 FieldExpression,实例级别委托给原生描述符。
|
||||||
|
|
||||||
def __init__(self, original_descriptor, model_class, field_name):
|
这是 QModel 字段表达式机制的核心:当你在类上访问 ``Model.field_name`` 时,
|
||||||
|
返回 ``FieldExpression`` 对象,支持 ``Model.field == value`` 等运算符构建 Q 对象;
|
||||||
|
当你在实例上访问 ``obj.field_name`` 时,行为与原生 Django 完全一致。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, original_descriptor, model_class: Type, field_name: str) -> None:
|
||||||
self._original = original_descriptor
|
self._original = original_descriptor
|
||||||
self._model = model_class
|
self._model = model_class
|
||||||
self._field_name = field_name
|
self._field_name = field_name
|
||||||
self._has_set = hasattr(original_descriptor, '__set__')
|
self._has_set: bool = hasattr(original_descriptor, '__set__')
|
||||||
|
|
||||||
def __get__(self, obj, objtype=None):
|
def __get__(self, obj, objtype=None):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
@@ -423,7 +468,7 @@ class _FieldExpressionWrapper:
|
|||||||
# 实例级别访问:委托给原生 Django 描述符
|
# 实例级别访问:委托给原生 Django 描述符
|
||||||
return self._original.__get__(obj, objtype)
|
return self._original.__get__(obj, objtype)
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
def __set__(self, obj, value) -> None:
|
||||||
if self._has_set:
|
if self._has_set:
|
||||||
# FK/O2O 描述符有 __set__
|
# FK/O2O 描述符有 __set__
|
||||||
self._original.__set__(obj, value)
|
self._original.__set__(obj, value)
|
||||||
@@ -431,14 +476,19 @@ class _FieldExpressionWrapper:
|
|||||||
# 普通字段(DeferredAttribute)没有 __set__,直接写 __dict__
|
# 普通字段(DeferredAttribute)没有 __set__,直接写 __dict__
|
||||||
obj.__dict__[self._field_name] = value
|
obj.__dict__[self._field_name] = value
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'_FieldExpressionWrapper({self._model.__name__}.{self._field_name})'
|
||||||
|
|
||||||
|
|
||||||
class QModel(models.Model, metaclass=QModelBase):
|
class QModel(models.Model, metaclass=QModelBase):
|
||||||
"""QModel 兼容层基类,提供 SQLAlchemy 风格的链式 OOP 查询接口。
|
"""QModel 兼容层基类,提供 SQLAlchemy 风格的链式 OOP 查询接口。
|
||||||
|
|
||||||
所有继承 QModel 的模型自动获得:
|
所有继承 QModel 的模型自动获得:
|
||||||
|
|
||||||
- ``Model.query`` : 返回 FluentQuery,支持链式查询
|
- ``Model.query`` : 返回 FluentQuery,支持链式查询
|
||||||
- ``Model.field_name`` : 类级别访问返回 FieldExpression,支持表达式查询
|
- ``Model.field_name`` : 类级别访问返回 FieldExpression,支持表达式查询
|
||||||
- ``Model.field_name`` : 实例级别访问返回字段值(与普通 Django Model 一致)
|
- ``Model.field_name`` : 实例级别访问返回字段值(与普通 Django Model 一致)
|
||||||
|
- ``Model[condition]`` : 快捷 filter 语法
|
||||||
|
|
||||||
用法示例::
|
用法示例::
|
||||||
|
|
||||||
@@ -456,28 +506,30 @@ class QModel(models.Model, metaclass=QModelBase):
|
|||||||
|
|
||||||
# 分页
|
# 分页
|
||||||
Dingdan.query.paginate(page=1, per_page=20)
|
Dingdan.query.paginate(page=1, per_page=20)
|
||||||
|
|
||||||
|
# 快捷 filter
|
||||||
|
Dingdan[Dingdan.zhuangtai == 8].order_by('-create_time').to_list()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def query(cls) -> FluentQuery:
|
def query(cls) -> FluentQuery[Self]:
|
||||||
"""返回 FluentQuery 兼容层查询构建器,支持链式 OOP 查询。"""
|
"""返回 FluentQuery 兼容层查询构建器,支持链式 OOP 查询。"""
|
||||||
return FluentQuery(cls)
|
return FluentQuery(cls)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __class_getitem__(cls, item) -> FluentQuery:
|
def __class_getitem__(cls, item) -> FluentQuery[Self]:
|
||||||
"""支持 Model[condition] 语法,返回 filter 后的 FluentQuery。
|
"""支持 ``Model[condition]`` 语法,返回 filter 后的 FluentQuery。
|
||||||
|
|
||||||
用法::
|
用法::
|
||||||
|
|
||||||
Dingdan[Dingdan.zhuangtai == 8].order_by('-create_time').to_list()
|
Dingdan[Dingdan.zhuangtai == 8].order_by('-create_time').to_list()
|
||||||
|
Dingdan[{'zhuangtai': 8}].to_list()
|
||||||
"""
|
"""
|
||||||
if isinstance(item, Q):
|
if isinstance(item, Q):
|
||||||
return cls.query.filter(item)
|
return cls.query.filter(item)
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
return cls.query.filter(**item)
|
return cls.query.filter(**item)
|
||||||
return cls.query.filter(item)
|
return cls.query.filter(item)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class UserManager(BaseUserManager):
|
|||||||
class User(QModel):
|
class User(QModel):
|
||||||
"""用户表——系统核心身份模型(映射 SAAS 主库 User 表)"""
|
"""用户表——系统核心身份模型(映射 SAAS 主库 User 表)"""
|
||||||
|
|
||||||
UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID', default=uuid.uuid4)
|
UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID', default=_uuid.uuid4)
|
||||||
UserName = models.CharField(max_length=256, unique=True, db_column='UserName', verbose_name='用户名')
|
UserName = models.CharField(max_length=256, unique=True, db_column='UserName', verbose_name='用户名')
|
||||||
UserPassword = models.CharField(max_length=60, default='', db_column='UserPassword', verbose_name='密码')
|
UserPassword = models.CharField(max_length=60, default='', db_column='UserPassword', verbose_name='密码')
|
||||||
UserPositionStatus = models.SmallIntegerField(default=0, db_column='UserPositionStatus', verbose_name='职位状态')
|
UserPositionStatus = models.SmallIntegerField(default=0, db_column='UserPositionStatus', verbose_name='职位状态')
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from backend.models import (
|
from backend.models import (
|
||||||
DashouRiTongji,
|
PlayerDailyStats,
|
||||||
GuanshiRiTongji,
|
ManagerDailyStats,
|
||||||
ZuzhangRiTongji,
|
LeaderDailyStats,
|
||||||
ShangjiaRiTongji,
|
MerchantDailyStats,
|
||||||
)
|
)
|
||||||
from users.models import UserDashou, UserShangjia, UserBoss
|
from users.models import UserDashou, UserShangjia, UserBoss
|
||||||
from gvsdsdk.models import User
|
from gvsdsdk.models import User
|
||||||
@@ -29,28 +29,32 @@ SHENFEN_OPTIONS = frozenset({'dashou', 'guanshi', 'zuzhang', 'shangjia'})
|
|||||||
|
|
||||||
ROLE_CONFIG = {
|
ROLE_CONFIG = {
|
||||||
'dashou': {
|
'dashou': {
|
||||||
'model': DashouRiTongji,
|
'model': PlayerDailyStats,
|
||||||
'sort_field': 'chengjiao_zonge',
|
'id_field': 'PlayerID',
|
||||||
'fields': ('jiedan_zongliang', 'chengjiao_zongliang', 'jiedan_zonge', 'chengjiao_zonge'),
|
'sort_field': 'CompletedAmount',
|
||||||
'int_fields': frozenset({'jiedan_zongliang', 'chengjiao_zongliang'}),
|
'fields': ('AcceptedOrderTotal', 'CompletedOrderTotal', 'AcceptedAmount', 'CompletedAmount'),
|
||||||
|
'int_fields': frozenset({'AcceptedOrderTotal', 'CompletedOrderTotal'}),
|
||||||
},
|
},
|
||||||
'guanshi': {
|
'guanshi': {
|
||||||
'model': GuanshiRiTongji,
|
'model': ManagerDailyStats,
|
||||||
'sort_field': 'shouru_zonge',
|
'id_field': 'ManagerID',
|
||||||
'fields': ('yaoqing_dashou_shu', 'chongzhi_dashou_shu', 'shouru_zonge'),
|
'sort_field': 'TotalIncome',
|
||||||
'int_fields': frozenset({'yaoqing_dashou_shu', 'chongzhi_dashou_shu'}),
|
'fields': ('InvitedPlayerCount', 'RechargedPlayerCount', 'TotalIncome'),
|
||||||
|
'int_fields': frozenset({'InvitedPlayerCount', 'RechargedPlayerCount'}),
|
||||||
},
|
},
|
||||||
'zuzhang': {
|
'zuzhang': {
|
||||||
'model': ZuzhangRiTongji,
|
'model': LeaderDailyStats,
|
||||||
'sort_field': 'shouru_zonge',
|
'id_field': 'LeaderID',
|
||||||
'fields': ('yaoqing_guanshi_shu', 'fenyong_jine', 'shouru_zonge'),
|
'sort_field': 'TotalIncome',
|
||||||
'int_fields': frozenset({'yaoqing_guanshi_shu'}),
|
'fields': ('InvitedManagerCount', 'CommissionAmount', 'TotalIncome'),
|
||||||
|
'int_fields': frozenset({'InvitedManagerCount'}),
|
||||||
},
|
},
|
||||||
'shangjia': {
|
'shangjia': {
|
||||||
'model': ShangjiaRiTongji,
|
'model': MerchantDailyStats,
|
||||||
'sort_field': 'jiesuan_jine',
|
'id_field': 'MerchantID',
|
||||||
'fields': ('paifa_dingdan_shu', 'paifa_jine', 'jiesuan_dingdan_shu', 'jiesuan_jine'),
|
'sort_field': 'SettledAmount',
|
||||||
'int_fields': frozenset({'paifa_dingdan_shu', 'jiesuan_dingdan_shu'}),
|
'fields': ('AssignedOrderCount', 'AssignedAmount', 'SettledOrderCount', 'SettledAmount'),
|
||||||
|
'int_fields': frozenset({'AssignedOrderCount', 'SettledOrderCount'}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,10 +141,11 @@ class PhbHqsjView(APIView):
|
|||||||
nick_map, avatar_map = self._load_user_display(rows, shenfen)
|
nick_map, avatar_map = self._load_user_display(rows, shenfen)
|
||||||
cfg = ROLE_CONFIG[shenfen]
|
cfg = ROLE_CONFIG[shenfen]
|
||||||
int_fields = cfg['int_fields']
|
int_fields = cfg['int_fields']
|
||||||
|
id_field = cfg['id_field']
|
||||||
|
|
||||||
result_list = []
|
result_list = []
|
||||||
for idx, row in enumerate(rows):
|
for idx, row in enumerate(rows):
|
||||||
yonghuid = row['yonghuid']
|
yonghuid = row[id_field]
|
||||||
item = {
|
item = {
|
||||||
'mingci': idx + 1,
|
'mingci': idx + 1,
|
||||||
'yonghuid': yonghuid,
|
'yonghuid': yonghuid,
|
||||||
@@ -169,35 +174,37 @@ class PhbHqsjView(APIView):
|
|||||||
model = cfg['model']
|
model = cfg['model']
|
||||||
sort_field = cfg['sort_field']
|
sort_field = cfg['sort_field']
|
||||||
fields = cfg['fields']
|
fields = cfg['fields']
|
||||||
|
id_field = cfg['id_field']
|
||||||
annotate_kwargs = {f: Sum(f) for f in fields}
|
annotate_kwargs = {f: Sum(f) for f in fields}
|
||||||
|
|
||||||
if all_time:
|
if all_time:
|
||||||
rows = (
|
rows = (
|
||||||
model.objects
|
model.objects
|
||||||
.values('yonghuid')
|
.values(id_field)
|
||||||
.annotate(**annotate_kwargs)
|
.annotate(**annotate_kwargs)
|
||||||
.order_by(f'-{sort_field}')[:MAX_RANK]
|
.order_by(f'-{sort_field}')[:MAX_RANK]
|
||||||
)
|
)
|
||||||
return list(rows)
|
return list(rows)
|
||||||
|
|
||||||
if start_date == end_date:
|
if start_date == end_date:
|
||||||
qs = model.objects.filter(riqi=start_date).order_by(f'-{sort_field}')[:MAX_RANK]
|
qs = model.objects.filter(Date=start_date).order_by(f'-{sort_field}')[:MAX_RANK]
|
||||||
return [
|
return [
|
||||||
{'yonghuid': r.yonghuid, **{f: getattr(r, f) for f in fields}}
|
{id_field: getattr(r, id_field), **{f: getattr(r, f) for f in fields}}
|
||||||
for r in qs
|
for r in qs
|
||||||
]
|
]
|
||||||
|
|
||||||
rows = (
|
rows = (
|
||||||
model.objects
|
model.objects
|
||||||
.filter(riqi__gte=start_date, riqi__lte=end_date)
|
.filter(Date__gte=start_date, Date__lte=end_date)
|
||||||
.values('yonghuid')
|
.values(id_field)
|
||||||
.annotate(**annotate_kwargs)
|
.annotate(**annotate_kwargs)
|
||||||
.order_by(f'-{sort_field}')[:MAX_RANK]
|
.order_by(f'-{sort_field}')[:MAX_RANK]
|
||||||
)
|
)
|
||||||
return list(rows)
|
return list(rows)
|
||||||
|
|
||||||
def _load_user_display(self, rows, shenfen):
|
def _load_user_display(self, rows, shenfen):
|
||||||
yonghuids = [r['yonghuid'] for r in rows if r.get('yonghuid')]
|
id_field = ROLE_CONFIG[shenfen]['id_field']
|
||||||
|
yonghuids = [r[id_field] for r in rows if r.get(id_field)]
|
||||||
if not yonghuids:
|
if not yonghuids:
|
||||||
return {}, {}
|
return {}, {}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from config.models import Szjilu, TixianQuotaDefault
|
|||||||
from utils.fadan_utils import check_fadan_qiangdan_eligible
|
from utils.fadan_utils import check_fadan_qiangdan_eligible
|
||||||
|
|
||||||
from products.models import Huiyuangoumai
|
from products.models import Huiyuangoumai
|
||||||
from backend.models import TixianRiTongji
|
from backend.models import WithdrawalDailyStats
|
||||||
|
|
||||||
from utils.wechat_v3 import build_authorization
|
from utils.wechat_v3 import build_authorization
|
||||||
|
|
||||||
@@ -445,12 +445,12 @@ def _get_active_auto_record(shenhe_danhao):
|
|||||||
|
|
||||||
|
|
||||||
def _get_platform_daily_total(leixing, today=None):
|
def _get_platform_daily_total(leixing, today=None):
|
||||||
"""平台今日已提现总额:只读 houtai.TixianRiTongji(到账金额口径)"""
|
"""平台今日已提现总额:只读 houtai.WithdrawalDailyStats(到账金额口径)"""
|
||||||
if today is None:
|
if today is None:
|
||||||
today = date.today()
|
today = date.today()
|
||||||
try:
|
try:
|
||||||
return TixianRiTongji.query.get(riqi=today, leixing=leixing).total_amount
|
return WithdrawalDailyStats.query.get(Date=today, WithdrawalType=leixing).total_amount
|
||||||
except TixianRiTongji.DoesNotExist:
|
except WithdrawalDailyStats.DoesNotExist:
|
||||||
return decimal.Decimal('0.00')
|
return decimal.Decimal('0.00')
|
||||||
|
|
||||||
|
|
||||||
@@ -576,7 +576,7 @@ def check_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe_d
|
|||||||
def reserve_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe_danhao=''):
|
def reserve_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe_danhao=''):
|
||||||
"""
|
"""
|
||||||
收款前原子预占每日限额(必须在 transaction.atomic 内调用)。
|
收款前原子预占每日限额(必须在 transaction.atomic 内调用)。
|
||||||
锁定 TixianRiTongji + 用户行,校验通过后立即写入统计,防止高并发超额。
|
锁定 WithdrawalDailyStats + 用户行,校验通过后立即写入统计,防止高并发超额。
|
||||||
本审核单已有进行中/成功打款记录则视为已预占,不重复加计。
|
本审核单已有进行中/成功打款记录则视为已预占,不重复加计。
|
||||||
返回 (True, '', '') 或 (False, msg, 'personal'|'platform')
|
返回 (True, '', '') 或 (False, msg, 'personal'|'platform')
|
||||||
"""
|
"""
|
||||||
@@ -617,9 +617,9 @@ def reserve_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe
|
|||||||
'personal',
|
'personal',
|
||||||
)
|
)
|
||||||
|
|
||||||
platform_stat, _ = TixianRiTongji.objects.select_for_update().get_or_create(
|
platform_stat, _ = WithdrawalDailyStats.objects.select_for_update().get_or_create(
|
||||||
riqi=today,
|
Date=today,
|
||||||
leixing=leixing,
|
WithdrawalType=leixing,
|
||||||
defaults={'total_amount': decimal.Decimal('0.00')},
|
defaults={'total_amount': decimal.Decimal('0.00')},
|
||||||
)
|
)
|
||||||
platform_limit = _get_platform_daily_limit(leixing)
|
platform_limit = _get_platform_daily_limit(leixing)
|
||||||
@@ -678,15 +678,15 @@ def release_collect_quota_reservation(user_main, leixing, jine, shijidaozhang):
|
|||||||
zuzhang.save(update_fields=['jinri_tixian'])
|
zuzhang.save(update_fields=['jinri_tixian'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stat = TixianRiTongji.objects.select_for_update().get(
|
stat = WithdrawalDailyStats.objects.select_for_update().get(
|
||||||
riqi=today, leixing=leixing,
|
Date=today, WithdrawalType=leixing,
|
||||||
)
|
)
|
||||||
stat.total_amount = max(
|
stat.total_amount = max(
|
||||||
stat.total_amount - shijidaozhang,
|
stat.total_amount - shijidaozhang,
|
||||||
decimal.Decimal('0.00'),
|
decimal.Decimal('0.00'),
|
||||||
)
|
)
|
||||||
stat.save(update_fields=['total_amount'])
|
stat.save(update_fields=['total_amount'])
|
||||||
except TixianRiTongji.DoesNotExist:
|
except WithdrawalDailyStats.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -741,7 +741,7 @@ def _quota_already_reserved_for_record(auto_record):
|
|||||||
def apply_transfer_success_quota(auto_record):
|
def apply_transfer_success_quota(auto_record):
|
||||||
"""
|
"""
|
||||||
打款成功时累加每日限额统计(仅自动审核流程 shenhe_danhao 非空)。
|
打款成功时累加每日限额统计(仅自动审核流程 shenhe_danhao 非空)。
|
||||||
- houtai.TixianRiTongji.total_amount += 实际到账
|
- houtai.WithdrawalDailyStats.total_amount += 实际到账
|
||||||
- 用户扩展表个人今日已提 += 申请金额(打手/管事/组长)
|
- 用户扩展表个人今日已提 += 申请金额(打手/管事/组长)
|
||||||
须在 transaction.atomic 内、且打款记录 zhuangtai 由 0→1 时调用一次。
|
须在 transaction.atomic 内、且打款记录 zhuangtai 由 0→1 时调用一次。
|
||||||
"""
|
"""
|
||||||
@@ -758,9 +758,9 @@ def apply_transfer_success_quota(auto_record):
|
|||||||
logger.error('打款成功累加限额失败:用户不存在 %s', auto_record.yonghuid)
|
logger.error('打款成功累加限额失败:用户不存在 %s', auto_record.yonghuid)
|
||||||
return
|
return
|
||||||
|
|
||||||
platform_stat, _ = TixianRiTongji.objects.select_for_update().get_or_create(
|
platform_stat, _ = WithdrawalDailyStats.objects.select_for_update().get_or_create(
|
||||||
riqi=today,
|
Date=today,
|
||||||
leixing=leixing,
|
WithdrawalType=leixing,
|
||||||
defaults={'total_amount': decimal.Decimal('0.00')},
|
defaults={'total_amount': decimal.Decimal('0.00')},
|
||||||
)
|
)
|
||||||
platform_stat.total_amount += shijidaozhang
|
platform_stat.total_amount += shijidaozhang
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ from products.models import (
|
|||||||
ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu
|
ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu
|
||||||
)
|
)
|
||||||
from config.models import Qunpeizhi, Szjilu
|
from config.models import Qunpeizhi, Szjilu
|
||||||
from backend.models import ShangjiaRiTongji
|
from backend.models import MerchantDailyStats
|
||||||
from rank.models import (
|
from rank.models import (
|
||||||
KaohePayTemp, Chenghao, ShenheJilu,
|
KaohePayTemp, Chenghao, ShenheJilu,
|
||||||
KaoheCishuFeiyong, YonghuChenghao
|
KaoheCishuFeiyong, YonghuChenghao
|
||||||
@@ -1088,13 +1088,13 @@ class ShangJiaXinXiView(APIView):
|
|||||||
})
|
})
|
||||||
response_data['chenghao_list'] = chenghao_list
|
response_data['chenghao_list'] = chenghao_list
|
||||||
|
|
||||||
# ------------------- 3. 从 ShangjiaRiTongji 获取今日统计 -------------------
|
# ------------------- 3. 从 MerchantDailyStats 获取今日统计 -------------------
|
||||||
try:
|
try:
|
||||||
today_stat = ShangjiaRiTongji.query.get(yonghuid=yonghuid, riqi=today)
|
today_stat = MerchantDailyStats.query.get(MerchantID=yonghuid, Date=today)
|
||||||
jinri_paifa_dingdan = today_stat.paifa_dingdan_shu
|
jinri_paifa_dingdan = today_stat.AssignedOrderCount
|
||||||
jinri_paifa_jine = float(today_stat.paifa_jine)
|
jinri_paifa_jine = float(today_stat.AssignedAmount)
|
||||||
jinri_tuikuan_dingdan = today_stat.tuikuan_dingdan_shu
|
jinri_tuikuan_dingdan = today_stat.RefundOrderCount
|
||||||
except ShangjiaRiTongji.DoesNotExist:
|
except MerchantDailyStats.DoesNotExist:
|
||||||
jinri_paifa_dingdan = 0
|
jinri_paifa_dingdan = 0
|
||||||
jinri_paifa_jine = 0.00
|
jinri_paifa_jine = 0.00
|
||||||
jinri_tuikuan_dingdan = 0
|
jinri_tuikuan_dingdan = 0
|
||||||
@@ -1103,14 +1103,14 @@ class ShangJiaXinXiView(APIView):
|
|||||||
response_data['jinrituikuan'] = jinri_tuikuan_dingdan # 今日退款数(新增)
|
response_data['jinrituikuan'] = jinri_tuikuan_dingdan # 今日退款数(新增)
|
||||||
response_data['riliushui'] = jinri_paifa_jine # 今日流水(派发总金额)
|
response_data['riliushui'] = jinri_paifa_jine # 今日流水(派发总金额)
|
||||||
|
|
||||||
# ------------------- 4. 从 ShangjiaRiTongji 获取本月统计 -------------------
|
# ------------------- 4. 从 MerchantDailyStats 获取本月统计 -------------------
|
||||||
month_stats = ShangjiaRiTongji.query.filter(
|
month_stats = MerchantDailyStats.query.filter(
|
||||||
yonghuid=yonghuid,
|
MerchantID=yonghuid,
|
||||||
riqi__gte=this_month_start,
|
Date__gte=this_month_start,
|
||||||
riqi__lte=today
|
Date__lte=today
|
||||||
).aggregate(
|
).aggregate(
|
||||||
total_paifa_jine=Sum('paifa_jine'),
|
total_paifa_jine=Sum('AssignedAmount'),
|
||||||
total_paifa_dingdan=Sum('paifa_dingdan_shu')
|
total_paifa_dingdan=Sum('AssignedOrderCount')
|
||||||
)
|
)
|
||||||
yue_paifa_jine = float(month_stats.get('total_paifa_jine') or 0.00)
|
yue_paifa_jine = float(month_stats.get('total_paifa_jine') or 0.00)
|
||||||
# yue_paifa_dingdan = month_stats.get('total_paifa_dingdan') or 0 # 如果前端不需要可不加
|
# yue_paifa_dingdan = month_stats.get('total_paifa_dingdan') or 0 # 如果前端不需要可不加
|
||||||
|
|||||||
Reference in New Issue
Block a user