Files
pyGoEdge-UserPanel/admin_panel/forms.py
2025-11-18 03:36:49 +08:00

218 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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__)