from django import forms from django.contrib.auth.models import User from .models import PostgresCluster, ClusterUser, ClusterDatabase class PostgresClusterForm(forms.ModelForm): """Form for creating and editing Postgres clusters.""" class Meta: model = PostgresCluster fields = [ 'name', 'description', 'cluster_type', 'deployment_type', 'postgres_version', 'port', 'data_directory', 'cpu_limit', 'memory_limit', 'storage_size', 'host', 'external_port', 'admin_user', 'admin_password', 'extensions', 'libraries', 'tls_enabled', 'tls_cert_path', 'tls_key_path', 'tls_ca_path', 'pgpool_enabled', 'pgpool_instances' ] widgets = { 'name': forms.TextInput(attrs={'class': 'form-control'}), 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'cluster_type': forms.Select(attrs={'class': 'form-select'}), 'deployment_type': forms.Select(attrs={'class': 'form-select'}), 'postgres_version': forms.Select(attrs={'class': 'form-select'}, choices=[ ('13', 'PostgreSQL 13'), ('14', 'PostgreSQL 14'), ('15', 'PostgreSQL 15'), ('16', 'PostgreSQL 16'), ('17', 'PostgreSQL 17'), ]), 'port': forms.NumberInput(attrs={'class': 'form-control'}), 'data_directory': forms.TextInput(attrs={'class': 'form-control'}), 'cpu_limit': forms.TextInput(attrs={'class': 'form-control'}), 'memory_limit': forms.TextInput(attrs={'class': 'form-control'}), 'storage_size': forms.TextInput(attrs={'class': 'form-control'}), 'host': forms.TextInput(attrs={'class': 'form-control'}), 'external_port': forms.NumberInput(attrs={'class': 'form-control'}), 'admin_user': forms.TextInput(attrs={'class': 'form-control'}), 'admin_password': forms.PasswordInput(attrs={'class': 'form-control'}), 'tls_cert_path': forms.TextInput(attrs={'class': 'form-control'}), 'tls_key_path': forms.TextInput(attrs={'class': 'form-control'}), 'tls_ca_path': forms.TextInput(attrs={'class': 'form-control'}), 'pgpool_instances': forms.NumberInput(attrs={'class': 'form-control'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Set default values for extensions and libraries if not self.instance.pk: self.fields['extensions'].initial = [ 'pg_stat_statements', 'pg_auth_mon', ] self.fields['libraries'].initial = [ 'bg_mon', 'pg_stat_statements', 'pgextwlist', 'pg_auth_mon', ] def clean_name(self): name = self.cleaned_data['name'] if not name.replace('-', '').replace('_', '').isalnum(): raise forms.ValidationError( 'Cluster name can only contain letters, numbers, hyphens, and underscores.' ) return name def clean_admin_password(self): password = self.cleaned_data['admin_password'] if len(password) < 8: raise forms.ValidationError( 'Admin password must be at least 8 characters long.' ) return password class ClusterUserForm(forms.ModelForm): """Form for creating and editing cluster users.""" confirm_password = forms.CharField( widget=forms.PasswordInput(attrs={'class': 'form-control'}), help_text='Confirm the password' ) class Meta: model = ClusterUser fields = [ 'username', 'password', 'is_superuser', 'can_create_db', 'can_login', 'permissions' ] widgets = { 'username': forms.TextInput(attrs={'class': 'form-control'}), 'password': forms.PasswordInput(attrs={'class': 'form-control'}), 'is_superuser': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'can_create_db': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'can_login': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'permissions': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } def clean_username(self): username = self.cleaned_data['username'] if not username.replace('_', '').isalnum(): raise forms.ValidationError( 'Username can only contain letters, numbers, and underscores.' ) return username def clean(self): cleaned_data = super().clean() password = cleaned_data.get('password') confirm_password = cleaned_data.get('confirm_password') if password and confirm_password and password != confirm_password: raise forms.ValidationError('Passwords do not match.') return cleaned_data class ClusterDatabaseForm(forms.ModelForm): """Form for creating and editing cluster databases.""" class Meta: model = ClusterDatabase fields = ['name', 'owner', 'encoding', 'collation', 'ctype'] widgets = { 'name': forms.TextInput(attrs={'class': 'form-control'}), 'owner': forms.Select(attrs={'class': 'form-select'}), 'encoding': forms.Select(attrs={'class': 'form-select'}, choices=[ ('UTF8', 'UTF8'), ('LATIN1', 'LATIN1'), ('SQL_ASCII', 'SQL_ASCII'), ]), 'collation': forms.TextInput(attrs={'class': 'form-control'}), 'ctype': forms.TextInput(attrs={'class': 'form-control'}), } def __init__(self, *args, **kwargs): cluster = kwargs.pop('cluster', None) super().__init__(*args, **kwargs) if cluster: self.fields['owner'].queryset = cluster.users.all() def clean_name(self): name = self.cleaned_data['name'] if not name.replace('_', '').isalnum(): raise forms.ValidationError( 'Database name can only contain letters, numbers, and underscores.' ) return name