Initial commit
This commit is contained in:
218
admin_panel/forms.py
Normal file
218
admin_panel/forms.py
Normal file
@@ -0,0 +1,218 @@
|
||||
from django import forms
|
||||
import logging
|
||||
from core.models import SystemSettings
|
||||
from plans.models import Plan
|
||||
from domains.models import Domain
|
||||
from billing.models import Invoice
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class SystemSettingsForm(forms.ModelForm):
|
||||
overage_action = forms.ChoiceField(choices=[('shutdown', '停服'), ('limit', '限速')], required=False, widget=forms.Select(attrs={'class': 'form-select'}))
|
||||
overage_limit_bps = forms.IntegerField(required=False, min_value=0, widget=forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '字节/秒'}))
|
||||
class Meta:
|
||||
model = SystemSettings
|
||||
fields = [
|
||||
'goedge_base_url',
|
||||
'admin_access_key_id',
|
||||
'admin_access_key',
|
||||
'default_node_cluster_id',
|
||||
'default_free_traffic_gb_per_domain',
|
||||
'default_http_access_log_policy_id',
|
||||
'default_http_firewall_policy_id',
|
||||
'cname_template',
|
||||
'anomaly_detection_enabled',
|
||||
'anomaly_threshold_multiplier',
|
||||
'anomaly_window_days',
|
||||
'anomaly_min_gb',
|
||||
'captcha_enabled',
|
||||
'epay_api_base_url',
|
||||
'epay_pid',
|
||||
'epay_key',
|
||||
]
|
||||
widgets = {
|
||||
'goedge_base_url': forms.URLInput(attrs={'class': 'form-control', 'placeholder': 'https://backend.example.com'}),
|
||||
'admin_access_key_id': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'AKID...'}),
|
||||
'admin_access_key': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'AK...'}),
|
||||
'default_node_cluster_id': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '集群ID'}),
|
||||
'default_free_traffic_gb_per_domain': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
'default_http_access_log_policy_id': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'HTTPAccessLogPolicyId'}),
|
||||
'default_http_firewall_policy_id': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'HTTPFirewallPolicyId'}),
|
||||
'cname_template': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '{sub}.cdn.example.com'}),
|
||||
'anomaly_detection_enabled': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'anomaly_threshold_multiplier': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '1'}),
|
||||
'anomaly_window_days': forms.NumberInput(attrs={'class': 'form-control', 'min': '3'}),
|
||||
'anomaly_min_gb': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0'}),
|
||||
'captcha_enabled': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'epay_api_base_url': forms.URLInput(attrs={'class': 'form-control', 'placeholder': 'https://api.example.com'}),
|
||||
'epay_pid': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '商户ID'}),
|
||||
'epay_key': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '商户密钥'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
try:
|
||||
policy = dict(getattr(self.instance, 'default_overage_policy', {}) or {})
|
||||
except Exception:
|
||||
policy = {}
|
||||
self.fields['overage_action'].initial = (policy.get('action') or 'shutdown')
|
||||
self.fields['overage_limit_bps'].initial = int(policy.get('limit_bps') or 0)
|
||||
|
||||
def save(self, commit=True):
|
||||
obj = super().save(commit=False)
|
||||
act = (self.cleaned_data.get('overage_action') or 'shutdown').lower()
|
||||
limit = int(self.cleaned_data.get('overage_limit_bps') or 0)
|
||||
obj.default_overage_policy = {'action': act, 'limit_bps': limit}
|
||||
if commit:
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
||||
class PlanForm(forms.ModelForm):
|
||||
# 使用 JSONField 保证 features 验证
|
||||
features = forms.JSONField(required=False, widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4, 'placeholder': '{"waf_enabled": true, "http3_enabled": false}' }))
|
||||
|
||||
# 可视化常用功能开关(与 JSON 保持兼容)
|
||||
waf_enabled = forms.BooleanField(required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}))
|
||||
http3_enabled = forms.BooleanField(required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}))
|
||||
websocket_enabled = forms.BooleanField(required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}))
|
||||
realtime_logs_enabled = forms.BooleanField(required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}))
|
||||
custom_ssl_enabled = forms.BooleanField(required=False, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}))
|
||||
page_rules_limit = forms.IntegerField(required=False, min_value=0, widget=forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '页面规则数量上限(可选)'}))
|
||||
|
||||
class Meta:
|
||||
model = Plan
|
||||
fields = [
|
||||
'name',
|
||||
'description',
|
||||
'billing_mode',
|
||||
'base_price_per_domain',
|
||||
'included_traffic_gb_per_domain',
|
||||
'overage_price_per_gb',
|
||||
'allow_overage',
|
||||
'is_active',
|
||||
'is_public',
|
||||
'allow_new_purchase',
|
||||
'allow_renew',
|
||||
'features',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
'billing_mode': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'base_price_per_domain': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'included_traffic_gb_per_domain': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
'overage_price_per_gb': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'allow_overage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'is_public': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'allow_new_purchase': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'allow_renew': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 初始化可视化字段的初始值来源于现有 features JSON
|
||||
features_dict = {}
|
||||
try:
|
||||
if getattr(self.instance, 'features', None):
|
||||
features_dict = dict(self.instance.features)
|
||||
except Exception:
|
||||
features_dict = {}
|
||||
self.fields['waf_enabled'].initial = features_dict.get('waf_enabled', False)
|
||||
self.fields['http3_enabled'].initial = features_dict.get('http3_enabled', False)
|
||||
self.fields['websocket_enabled'].initial = features_dict.get('websocket_enabled', False)
|
||||
self.fields['realtime_logs_enabled'].initial = features_dict.get('realtime_logs_enabled', False)
|
||||
self.fields['custom_ssl_enabled'].initial = features_dict.get('custom_ssl_enabled', False)
|
||||
self.fields['page_rules_limit'].initial = features_dict.get('page_rules_limit', None)
|
||||
|
||||
def save(self, commit=True):
|
||||
plan = super().save(commit=False)
|
||||
# 合并 JSON 字段与可视化字段
|
||||
base_features = {}
|
||||
try:
|
||||
if getattr(self.instance, 'features', None):
|
||||
base_features = dict(self.instance.features)
|
||||
except Exception:
|
||||
base_features = {}
|
||||
|
||||
cleaned_features = self.cleaned_data.get('features') or {}
|
||||
if not isinstance(cleaned_features, dict):
|
||||
cleaned_features = {}
|
||||
# 先合并自由 JSON
|
||||
merged = {**base_features, **cleaned_features}
|
||||
# 再覆盖常用可视化选项
|
||||
merged['waf_enabled'] = bool(self.cleaned_data.get('waf_enabled'))
|
||||
merged['http3_enabled'] = bool(self.cleaned_data.get('http3_enabled'))
|
||||
merged['websocket_enabled'] = bool(self.cleaned_data.get('websocket_enabled'))
|
||||
merged['realtime_logs_enabled'] = bool(self.cleaned_data.get('realtime_logs_enabled'))
|
||||
merged['custom_ssl_enabled'] = bool(self.cleaned_data.get('custom_ssl_enabled'))
|
||||
page_rules_limit = self.cleaned_data.get('page_rules_limit')
|
||||
if page_rules_limit is not None:
|
||||
merged['page_rules_limit'] = int(page_rules_limit)
|
||||
else:
|
||||
# 若用户未设置,上次值保留(已在 merged 中)
|
||||
pass
|
||||
|
||||
plan.features = merged
|
||||
if commit:
|
||||
plan.save()
|
||||
return plan
|
||||
|
||||
|
||||
class DomainPlanSwitchForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Domain
|
||||
fields = ['current_plan']
|
||||
widgets = {
|
||||
'current_plan': forms.Select(attrs={'class': 'form-select'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 仅允许选择激活的套餐(运营可见不强制)
|
||||
self.fields['current_plan'].queryset = Plan.objects.filter(is_active=True).order_by('base_price_per_domain')
|
||||
|
||||
|
||||
class DomainGrantTrafficForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Domain
|
||||
fields = ['extra_free_traffic_gb_current_cycle']
|
||||
widgets = {
|
||||
'extra_free_traffic_gb_current_cycle': forms.NumberInput(attrs={'class': 'form-control', 'min': '0'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['extra_free_traffic_gb_current_cycle'].label = '本周期额外赠送流量(GB)'
|
||||
|
||||
|
||||
class InvoiceAdjustmentForm(forms.Form):
|
||||
description = forms.CharField(label='调整说明', max_length=200, required=False, initial='人工调整', widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
amount = forms.DecimalField(label='调整金额(¥)', max_digits=12, decimal_places=2, widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}))
|
||||
is_increase = forms.BooleanField(required=False, initial=True, widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}))
|
||||
|
||||
def apply(self, invoice: Invoice) -> Invoice:
|
||||
amt = Decimal(str(self.cleaned_data['amount']))
|
||||
if not self.cleaned_data.get('is_increase'):
|
||||
amt = -amt
|
||||
desc = self.cleaned_data.get('description') or '人工调整'
|
||||
# 更新账单金额
|
||||
invoice.amount_adjustment = (Decimal(str(invoice.amount_adjustment)) + amt).quantize(Decimal('0.01'))
|
||||
invoice.amount_total = (Decimal(str(invoice.amount_total)) + amt).quantize(Decimal('0.01'))
|
||||
invoice.save(update_fields=['amount_adjustment', 'amount_total'])
|
||||
# 记录为一条账单项
|
||||
try:
|
||||
from billing.models import InvoiceItem
|
||||
InvoiceItem.objects.create(
|
||||
invoice=invoice,
|
||||
domain=None,
|
||||
description=desc,
|
||||
quantity=Decimal('0.000'),
|
||||
unit_price=Decimal('0.00'),
|
||||
amount=amt,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('invoice item write failed')
|
||||
return invoice
|
||||
logger = logging.getLogger(__name__)
|
||||
Reference in New Issue
Block a user