diff --git a/orchestra/contrib/accounts/admin.py b/orchestra/contrib/accounts/admin.py index b2d6442a..0c1e81c0 100644 --- a/orchestra/contrib/accounts/admin.py +++ b/orchestra/contrib/accounts/admin.py @@ -26,12 +26,13 @@ from .actions import (list_contacts, service_report, delete_related_services, di enable_selected) from .forms import AccountCreationForm from .models import Account +from .filters import HasTipeServerFilter class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin): list_display = ('username', 'full_name', 'type', 'is_active') list_filter = ( - 'type', 'is_active', + 'type', 'is_active', HasTipeServerFilter ) add_fieldsets = ( (_("User"), { diff --git a/orchestra/contrib/accounts/filters.py b/orchestra/contrib/accounts/filters.py index ff4be0ae..bbffd8c2 100644 --- a/orchestra/contrib/accounts/filters.py +++ b/orchestra/contrib/accounts/filters.py @@ -1,7 +1,9 @@ from django.contrib.admin import SimpleListFilter from django.db.models import Q from django.utils.translation import gettext_lazy as _ - +from orchestra.contrib.orchestration.models import Server +from orchestra.contrib.websites.models import Website +from orchestra.settings import WEB_SERVERS class IsActiveListFilter(SimpleListFilter): title = _("is active") @@ -25,3 +27,16 @@ class IsActiveListFilter(SimpleListFilter): elif self.value() == 'object': return queryset.filter(is_active=False) return queryset + +class HasTipeServerFilter(SimpleListFilter): + title = _("has type server") + parameter_name = 'has_servers' + + def lookups(self, request, model_admin): + return [ (x.id, x.name) for x in Server.objects.filter(name__in=WEB_SERVERS) ] + + def queryset(self, request, queryset): + if self.value() is not None: + serverWebsites = Website.objects.filter(target_server=self.value()) + return queryset.filter(id__in=[ x.account.id for x in serverWebsites ] ) + return queryset \ No newline at end of file diff --git a/orchestra/contrib/lists/admin.py b/orchestra/contrib/lists/admin.py index d356d726..fc6255f9 100644 --- a/orchestra/contrib/lists/admin.py +++ b/orchestra/contrib/lists/admin.py @@ -16,7 +16,7 @@ from .filters import HasCustomAddressListFilter from .models import List -class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin): +class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): list_display = ( 'name', 'address_name', 'address_domain_link', 'account_link', 'display_active' ) @@ -31,7 +31,7 @@ class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModel }), (_("Admin"), { 'classes': ('wide',), - 'fields': ('admin_email', 'password1', 'password2'), + 'fields': ('admin_email',), }), ) fieldsets = ( @@ -45,35 +45,15 @@ class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModel ) % settings.LISTS_DEFAULT_DOMAIN, 'fields': (('address_name', 'address_domain'),) }), - (_("Admin"), { - 'classes': ('wide',), - 'fields': ('password',), - }), ) search_fields = ('name', 'address_name', 'address_domain__name', 'account__username') list_filter = (IsActiveListFilter, HasCustomAddressListFilter) readonly_fields = ('account_link',) change_readonly_fields = ('name',) - form = NonStoredUserChangeForm - add_form = UserCreationForm list_select_related = ('account', 'address_domain',) filter_by_account_fields = ['address_domain'] actions = (disable, enable, list_accounts) address_domain_link = admin_link('address_domain', order='address_domain__name') - - def get_urls(self): - useradmin = UserAdmin(List, self.admin_site) - return [ - url(r'^(\d+)/password/$', - self.admin_site.admin_view(useradmin.user_change_password)) - ] + super(ListAdmin, self).get_urls() - - def save_model(self, request, obj, form, change): - """ set password """ - if not change: - obj.set_password(form.cleaned_data["password1"]) - super(ListAdmin, self).save_model(request, obj, form, change) - admin.site.register(List, ListAdmin) diff --git a/orchestra/contrib/lists/backends.py b/orchestra/contrib/lists/backends.py index b6d4dc97..1c31e826 100644 --- a/orchestra/contrib/lists/backends.py +++ b/orchestra/contrib/lists/backends.py @@ -182,7 +182,6 @@ class MailmanController(MailmanVirtualDomainController): context.update({ 'banner': self.get_banner(mail_list), 'name': mail_list.name, - 'password': mail_list.password, 'domain': mail_list.address_domain or settings.LISTS_DEFAULT_DOMAIN, 'address_name': mail_list.get_address_name(), 'address_domain': mail_list.address_domain, diff --git a/orchestra/contrib/lists/migrations/0001_initial.py b/orchestra/contrib/lists/migrations/0001_initial.py new file mode 100644 index 00000000..064843fc --- /dev/null +++ b/orchestra/contrib/lists/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.28 on 2023-09-01 14:59 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import orchestra.core.validators + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('domains', '__first__'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='List', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Default list address <name>@grups.pangea.org', max_length=64, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name')), + ('address_name', models.CharField(blank=True, max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='address name')), + ('admin_email', models.EmailField(help_text='Administration email address', max_length=254, verbose_name='admin email')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lists', to=settings.AUTH_USER_MODEL, verbose_name='Account')), + ('address_domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='domains.Domain', verbose_name='address domain')), + ], + options={ + 'unique_together': {('address_name', 'address_domain')}, + }, + ), + ] diff --git a/orchestra/contrib/lists/migrations/__init__.py b/orchestra/contrib/lists/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/lists/models.py b/orchestra/contrib/lists/models.py index 8ac33723..82ab91f7 100644 --- a/orchestra/contrib/lists/models.py +++ b/orchestra/contrib/lists/models.py @@ -8,17 +8,6 @@ from orchestra.core.validators import validate_name from . import settings -class ListQuerySet(models.QuerySet): - def create(self, **kwargs): - """ Sets password if provided, all within a single DB operation """ - password = kwargs.pop('password') - instance = self.model(**kwargs) - if password: - instance.set_password(password) - instance.save() - return instance - - # TODO address and domain, perhaps allow only domain? class List(models.Model): name = models.CharField(_("name"), max_length=64, unique=True, validators=[validate_name], @@ -35,9 +24,6 @@ class List(models.Model): is_active = models.BooleanField(_("active"), default=True, help_text=_("Designates whether this account should be treated as active. " "Unselect this instead of deleting accounts.")) - password = None - - objects = ListQuerySet.as_manager() class Meta: unique_together = ('address_name', 'address_domain') @@ -75,9 +61,6 @@ class List(models.Model): def get_username(self): return self.name - def set_password(self, password): - self.password = password - def get_absolute_url(self): context = { 'name': self.name diff --git a/orchestra/contrib/lists/settings.py b/orchestra/contrib/lists/settings.py index 9d5e25ae..8df45b41 100644 --- a/orchestra/contrib/lists/settings.py +++ b/orchestra/contrib/lists/settings.py @@ -9,13 +9,14 @@ LISTS_DOMAIN_MODEL = Setting('LISTS_DOMAIN_MODEL', LISTS_DEFAULT_DOMAIN = Setting('LISTS_DEFAULT_DOMAIN', - 'lists.{}'.format(ORCHESTRA_BASE_DOMAIN), + 'grups.{}'.format(ORCHESTRA_BASE_DOMAIN), help_text="Uses ORCHESTRA_BASE_DOMAIN by default." ) LISTS_LIST_URL = Setting('LISTS_LIST_URL', - 'https://lists.{}/mailman/listinfo/%(name)s'.format(ORCHESTRA_BASE_DOMAIN), + # 'https://lists.{}/mailman/listinfo/%(name)s'.format(ORCHESTRA_BASE_DOMAIN), + 'https://www.{0}/mailman3/lists/%(name)s.{0}'.format(LISTS_DEFAULT_DOMAIN), help_text="Uses ORCHESTRA_BASE_DOMAIN by default." ) diff --git a/orchestra/contrib/mailboxes/admin.py b/orchestra/contrib/mailboxes/admin.py index d1094d3f..09924dd6 100644 --- a/orchestra/contrib/mailboxes/admin.py +++ b/orchestra/contrib/mailboxes/admin.py @@ -20,7 +20,7 @@ from orchestra.core import caches from . import settings from .actions import SendMailboxEmail, SendAddressEmail -from .filters import HasMailboxListFilter, HasForwardListFilter, HasAddressListFilter +from .filters import HasMailboxListFilter, HasForwardListFilter, HasAddressListFilter, HasTipeServerFilter from .forms import MailboxCreationForm, MailboxChangeForm, AddressForm from .models import Mailbox, Address, Autoresponse from .widgets import OpenCustomFilteringOnSelect @@ -40,7 +40,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo list_display = ( 'name', 'account_link', 'display_filtering', 'display_addresses', 'display_active', ) - list_filter = (IsActiveListFilter, HasAddressListFilter, 'filtering') + list_filter = (IsActiveListFilter, HasAddressListFilter, 'filtering', HasTipeServerFilter) search_fields = ( 'account__username', 'account__short_name', 'account__full_name', 'name', 'addresses__name', 'addresses__domain__name', diff --git a/orchestra/contrib/mailboxes/filters.py b/orchestra/contrib/mailboxes/filters.py index 2c1dd603..0ef6978b 100644 --- a/orchestra/contrib/mailboxes/filters.py +++ b/orchestra/contrib/mailboxes/filters.py @@ -1,6 +1,8 @@ from django.contrib.admin import SimpleListFilter from django.utils.translation import gettext_lazy as _ - +from orchestra.contrib.orchestration.models import Server +from orchestra.contrib.websites.models import Website +from orchestra.settings import WEB_SERVERS class HasMailboxListFilter(SimpleListFilter): """ Filter addresses whether they have any mailbox or not """ @@ -45,3 +47,17 @@ class HasAddressListFilter(HasMailboxListFilter): elif self.value() == 'False': return queryset.filter(addresses__isnull=True) return queryset + + +class HasTipeServerFilter(SimpleListFilter): + title = _("has type server") + parameter_name = 'has_servers' + + def lookups(self, request, model_admin): + return [ (x.id, x.name) for x in Server.objects.filter(name__in=WEB_SERVERS) ] + + def queryset(self, request, queryset): + if self.value() is not None: + serverWebsites = Website.objects.filter(target_server=self.value()) + return queryset.filter(account__in=[ x.account.id for x in serverWebsites ] ) + return queryset \ No newline at end of file diff --git a/orchestra/contrib/webapps/admin.py b/orchestra/contrib/webapps/admin.py index 24bd4948..afa9c8ab 100644 --- a/orchestra/contrib/webapps/admin.py +++ b/orchestra/contrib/webapps/admin.py @@ -15,6 +15,7 @@ from orchestra.contrib.systemusers.models import WebappUsers from orchestra.forms.widgets import DynamicHelpTextSelect from orchestra.plugins.admin import SelectPluginAdminMixin, display_plugin_field from orchestra.utils.html import get_on_site_link +from orchestra.settings import NEW_SERVERS from .filters import HasWebsiteListFilter, DetailListFilter from .models import WebApp, WebAppOption @@ -57,7 +58,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin) list_display = ( 'name', 'display_type', 'display_detail', 'display_websites', 'account_link', 'target_server', ) - list_filter = ('type', HasWebsiteListFilter, DetailListFilter) + list_filter = ('type', HasWebsiteListFilter, DetailListFilter, 'target_server') inlines = [WebAppOptionInline] readonly_fields = ('account_link',) change_readonly_fields = ('name', 'type', 'display_websites', 'display_sftpuser', 'target_server',) @@ -110,7 +111,8 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin) def save_model(self, request, obj, form, change): if not change: user = form.cleaned_data.get('username') - if user: + server = form.cleaned_data.get('target_server') + if user and server.name in NEW_SERVERS: user = WebappUsers( username=form.cleaned_data['username'], account_id=obj.account.pk, diff --git a/orchestra/contrib/webapps/types/misc.py b/orchestra/contrib/webapps/types/misc.py index 92fd1fa3..d80a66b8 100644 --- a/orchestra/contrib/webapps/types/misc.py +++ b/orchestra/contrib/webapps/types/misc.py @@ -3,7 +3,7 @@ import os from django import forms from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from orchestra.plugins.forms import ExtendedPluginDataForm +from orchestra.plugins.forms import ExtendedPluginDataForm, PluginDataForm from ..options import AppOption from . import AppType @@ -32,6 +32,7 @@ class WebalizerApp(AppType): "Statistics will be collected once this app is mounted into one or more Websites.") icon = 'orchestra/icons/apps/Stats.png' option_groups = () + form = PluginDataForm def get_directive(self): webalizer_path = os.path.join(self.instance.get_path(), '%(site_name)s') diff --git a/orchestra/contrib/websites/admin.py b/orchestra/contrib/websites/admin.py index 6682c4c7..f47f28b9 100644 --- a/orchestra/contrib/websites/admin.py +++ b/orchestra/contrib/websites/admin.py @@ -63,7 +63,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): 'name', 'display_domains', 'display_webapps', 'account_link', 'target_server', 'display_active' ) list_filter = ( - 'protocol', IsActiveListFilter, HasWebAppsListFilter, HasDomainsFilter + 'protocol', IsActiveListFilter, HasWebAppsListFilter, HasDomainsFilter, 'target_server' ) change_readonly_fields = ('name',) inlines = (ContentInline, WebsiteDirectiveInline) diff --git a/orchestra/plugins/forms.py b/orchestra/plugins/forms.py index 16885756..cbd71489 100644 --- a/orchestra/plugins/forms.py +++ b/orchestra/plugins/forms.py @@ -7,9 +7,11 @@ from orchestra.forms.widgets import SpanWidget from django.core.exceptions import ValidationError from orchestra.core import validators from orchestra.utils.python import random_ascii -from orchestra.settings import NEW_SERVERS +from orchestra.settings import NEW_SERVERS, WEB_SERVERS from django.utils.translation import gettext_lazy as _ +import textwrap +from orchestra.contrib.orchestration.models import Server class PluginForm(forms.ModelForm): def __init__(self, *args, **kwargs): @@ -21,12 +23,13 @@ class PluginForm(forms.ModelForm): help_text = self.fields[self.plugin_field].help_text self.fields[self.plugin_field].help_text = getattr(self.plugin, 'help_text', help_text) - class PluginDataForm(PluginForm): data = forms.CharField(widget=forms.HiddenInput, required=False) + target_server = forms.ModelChoiceField(queryset=Server.objects.filter(name__in=WEB_SERVERS),) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.fields['sftpuser'].widget = forms.HiddenInput() if self.instance: for field in self.declared_fields: initial = self.fields[field].initial @@ -98,15 +101,25 @@ class ExtendedPluginDataForm(PluginDataForm): widget=forms.PasswordInput, help_text=_("Enter the same password as above, for verification.")) - def __init__(self, *args, **kwargs): super(ExtendedPluginDataForm, self).__init__(*args, **kwargs) - self.fields['sftpuser'].widget = forms.HiddenInput() if self.instance.id is not None: self.fields['username'].widget = forms.HiddenInput() self.fields['password1'].widget = forms.HiddenInput() self.fields['password2'].widget = forms.HiddenInput() + if not self.instance.pk: + self.fields['target_server'].widget.attrs['onChange'] = textwrap.dedent("""\ + field = $(".field-username, .field-password1, .field-password2"); + input = $("#id_username, #id_password1, #id_password2"); + if (%s.includes(this.options[this.selectedIndex].text)) { + field.removeClass("hidden"); + } else { + field.addClass("hidden"); + input.val(""); + };""" % list(NEW_SERVERS) + ) + def clean_username(self): if not self.instance.id: webapp_server = self.cleaned_data.get("target_server") diff --git a/orchestra/settings.py b/orchestra/settings.py index ff311382..3e43573e 100644 --- a/orchestra/settings.py +++ b/orchestra/settings.py @@ -97,3 +97,12 @@ NEW_SERVERS = Setting('NEW_SERVERS', 'web-12.pangea.lan', ) ) + +WEB_SERVERS = Setting('WEBAPPS_SERVERS', ( + 'web.pangea.lan', + 'web-ng', + 'web-11.pangea.lan', + 'web-12.pangea.lan', + 'bookworm', + ) +) \ No newline at end of file