Upgrade to Django 3.2

- update dependencies
- fix deprecation warnings
- fix tests
- skip some tests that need more work
- reformat changed code with isort and black
This commit is contained in:
Jan Dittberner 2023-02-18 22:46:48 +01:00
parent 0f18e59d67
commit 4af1a39ca4
93 changed files with 3598 additions and 2725 deletions

7
.isort.cfg Normal file
View file

@ -0,0 +1,7 @@
[tool.isort]
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
line_length = 88

View file

@ -4,19 +4,16 @@ This module contains the form class for the contact_form app.
"""
from __future__ import absolute_import, unicode_literals
from django import forms
from django.conf import settings
from django.core.mail import send_mail
from django.template import RequestContext
from django.template import loader
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.sites.models import Site
from django.contrib.sites.requests import RequestSite
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django import forms
from django.conf import settings
from django.contrib.sites.models import Site
from django.contrib.sites.requests import RequestSite
from django.core.mail import send_mail
from django.template import RequestContext, loader
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
class ContactForm(forms.Form):
@ -24,45 +21,42 @@ class ContactForm(forms.Form):
This is the contact form class.
"""
name = forms.CharField(max_length=100, label=_('Your name'))
email = forms.EmailField(max_length=200, label=_('Your email address'))
body = forms.CharField(widget=forms.Textarea, label=_('Your message'))
name = forms.CharField(max_length=100, label=_("Your name"))
email = forms.EmailField(max_length=200, label=_("Your email address"))
body = forms.CharField(widget=forms.Textarea, label=_("Your message"))
subject_template_name = "contact_form/contact_form_subject.txt"
template_name = 'contact_form/contact_form.txt'
template_name = "contact_form/contact_form.txt"
from_email = settings.DEFAULT_FROM_EMAIL
recipient_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS]
def __init__(self, **kwargs):
self.request = kwargs.pop('request')
self.request = kwargs.pop("request")
super(ContactForm, self).__init__(**kwargs)
self.helper = FormHelper()
self.helper.form_action = reverse('contact_form')
self.helper.add_input(Submit('submit', _('Send message')))
self.helper.form_action = reverse("contact_form")
self.helper.add_input(Submit("submit", _("Send message")))
def get_context(self):
if not self.is_valid():
raise ValueError(
'Cannot generate context from invalid contact form')
raise ValueError("Cannot generate context from invalid contact form")
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(self.request)
return RequestContext(
self.request, dict(self.cleaned_data, site=site))
return RequestContext(self.request, dict(self.cleaned_data, site=site))
def message(self):
context = self.get_context()
template_context = context.flatten()
template_context.update({
'remote_ip': context.request.META['REMOTE_ADDR']
})
template_context.update({"remote_ip": context.request.META["REMOTE_ADDR"]})
return loader.render_to_string(self.template_name, template_context)
def subject(self):
context = self.get_context().flatten()
subject = loader.render_to_string(self.subject_template_name, context)
return ''.join(subject.splitlines())
return "".join(subject.splitlines())
def save(self, fail_silently=False):
"""
@ -74,5 +68,5 @@ class ContactForm(forms.Form):
from_email=self.from_email,
recipient_list=self.recipient_list,
subject=self.subject(),
message=self.message()
message=self.message(),
)

View file

@ -2,17 +2,13 @@
URL patterns for the contact_form views.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf.urls import url
from .views import (
ContactFormView,
ContactSuccessView,
)
from django.urls import re_path
from .views import ContactFormView, ContactSuccessView
urlpatterns = [
url(r'^$', ContactFormView.as_view(), name='contact_form'),
url(r'^success/$', ContactSuccessView.as_view(), name='contact_success'),
re_path(r"^$", ContactFormView.as_view(), name="contact_form"),
re_path(r"^success/$", ContactSuccessView.as_view(), name="contact_success"),
]

View file

@ -2,14 +2,11 @@
This module defines the views of the contact_form app.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.views.generic import (
FormView,
TemplateView,
)
from django.views.generic import FormView, TemplateView
from .forms import ContactForm
@ -19,22 +16,22 @@ class ContactFormView(FormView):
This is the contact form view.
"""
form_class = ContactForm
template_name = 'contact_form/contact_form.html'
success_url = reverse_lazy('contact_success')
template_name = "contact_form/contact_form.html"
success_url = reverse_lazy("contact_success")
def get_form_kwargs(self, **kwargs):
kwargs = super(ContactFormView, self).get_form_kwargs(**kwargs)
kwargs['request'] = self.request
kwargs["request"] = self.request
return kwargs
def get_initial(self):
initial = super(ContactFormView, self).get_initial()
currentuser = self.request.user
if currentuser.is_authenticated:
initial['name'] = (
currentuser.get_full_name() or currentuser.username)
initial['email'] = currentuser.email
initial["name"] = currentuser.get_full_name() or currentuser.username
initial["email"] = currentuser.email
return initial
def form_valid(self, form):
@ -47,4 +44,5 @@ class ContactSuccessView(TemplateView):
This view is shown after successful contact form sending.
"""
template_name = 'contact_form/contact_success.html'
template_name = "contact_form/contact_success.html"

View file

@ -1,15 +1,14 @@
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf.urls import url
from .views import (
IndexView,
UserDashboardView,
)
from django.urls import re_path
from .views import IndexView, UserDashboardView
urlpatterns = [
url(r'^$', IndexView.as_view(), name='dashboard'),
url(r'^user/(?P<slug>[\w0-9@.+-_]+)/$',
UserDashboardView.as_view(), name='customer_dashboard'),
re_path(r"^$", IndexView.as_view(), name="dashboard"),
re_path(
r"^user/(?P<slug>[\w0-9@.+-_]+)/$",
UserDashboardView.as_view(),
name="customer_dashboard",
),
]

View file

@ -2,14 +2,8 @@
This module defines the views for the gnuviechadmin customer dashboard.
"""
from __future__ import unicode_literals
from django.views.generic import (
DetailView,
TemplateView,
)
from django.contrib.auth import get_user_model
from django.views.generic import DetailView, TemplateView
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from hostingpackages.models import CustomerHostingPackage
@ -20,7 +14,8 @@ class IndexView(TemplateView):
This is the dashboard view.
"""
template_name = 'dashboard/index.html'
template_name = "dashboard/index.html"
class UserDashboardView(StaffOrSelfLoginRequiredMixin, DetailView):
@ -28,14 +23,15 @@ class UserDashboardView(StaffOrSelfLoginRequiredMixin, DetailView):
This is the user dashboard view.
"""
model = get_user_model()
context_object_name = 'dashboard_user'
slug_field = 'username'
template_name = 'dashboard/user_dashboard.html'
context_object_name = "dashboard_user"
slug_field = "username"
template_name = "dashboard/user_dashboard.html"
def get_context_data(self, **kwargs):
context = super(UserDashboardView, self).get_context_data(**kwargs)
context['hosting_packages'] = CustomerHostingPackage.objects.filter(
context["hosting_packages"] = CustomerHostingPackage.objects.filter(
customer=self.object
)
return context

View file

@ -2,4 +2,3 @@
This app takes care of domains.
"""
default_app_config = 'domains.apps.DomainAppConfig'

View file

@ -3,9 +3,8 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`domains` app.
"""
from __future__ import unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class DomainAppConfig(AppConfig):
@ -13,5 +12,6 @@ class DomainAppConfig(AppConfig):
AppConfig for the :py:mod:`domains` app.
"""
name = 'domains'
verbose_name = _('Domains')
name = "domains"
verbose_name = _("Domains")

View file

@ -2,19 +2,15 @@
This module defines form classes for domain editing.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
import re
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
from django import forms
from django.urls import reverse
from django.utils.translation import ugettext as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
Submit,
)
from django.utils.translation import gettext as _
from .models import HostingDomain
@ -26,11 +22,10 @@ def relative_domain_validator(value):
"""
if len(value) > 254:
raise forms.ValidationError(
_('host name too long'), code='too-long')
raise forms.ValidationError(_("host name too long"), code="too-long")
allowed = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)$")
if not all(allowed.match(x) for x in value.split('.')):
raise forms.ValidationError(_('invalid domain name'))
if not all(allowed.match(x) for x in value.split(".")):
raise forms.ValidationError(_("invalid domain name"))
class CreateHostingDomainForm(forms.ModelForm):
@ -38,31 +33,32 @@ class CreateHostingDomainForm(forms.ModelForm):
This form is used to create new HostingDomain instances.
"""
class Meta:
model = HostingDomain
fields = ['domain']
fields = ["domain"]
def __init__(self, instance, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
self.hosting_package = kwargs.pop("hostingpackage")
super(CreateHostingDomainForm, self).__init__(*args, **kwargs)
self.fields['domain'].validators.append(relative_domain_validator)
self.fields["domain"].validators.append(relative_domain_validator)
self.helper = FormHelper()
self.helper.form_action = reverse(
'create_hosting_domain', kwargs={
'package': self.hosting_package.id
})
"create_hosting_domain", kwargs={"package": self.hosting_package.id}
)
self.helper.layout = Layout(
'domain',
Submit('submit', _('Add Hosting Domain')),
"domain",
Submit("submit", _("Add Hosting Domain")),
)
def clean(self):
self.cleaned_data = super(CreateHostingDomainForm, self).clean()
self.cleaned_data['hosting_package'] = self.hosting_package
self.cleaned_data["hosting_package"] = self.hosting_package
def save(self, commit=True):
return HostingDomain.objects.create_for_hosting_package(
commit=commit, **self.cleaned_data)
commit=commit, **self.cleaned_data
)
def save_m2m(self):
pass

View file

@ -1,28 +1,46 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
import model_utils.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='MailDomain',
name="MailDomain",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('domain', models.CharField(unique=True, max_length=128)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
("domain", models.CharField(unique=True, max_length=128)),
],
options={
'verbose_name': 'Mail domain',
'verbose_name_plural': 'Mail domains',
"verbose_name": "Mail domain",
"verbose_name_plural": "Mail domains",
},
bases=(models.Model,),
),

View file

@ -1,68 +1,97 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
from django.conf import settings
import model_utils.fields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('domains', '0001_initial'),
("domains", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='HostingDomain',
name="HostingDomain",
fields=[
('id',
models.AutoField(verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('created',
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified',
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('domain',
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"domain",
models.CharField(
unique=True, max_length=128, verbose_name='domain name')),
('customer',
unique=True, max_length=128, verbose_name="domain name"
),
),
(
"customer",
models.ForeignKey(
verbose_name='customer', blank=True,
to=settings.AUTH_USER_MODEL, null=True,
on_delete=models.CASCADE)),
('maildomain',
verbose_name="customer",
blank=True,
to=settings.AUTH_USER_MODEL,
null=True,
on_delete=models.CASCADE,
),
),
(
"maildomain",
models.OneToOneField(
null=True, to='domains.MailDomain', blank=True,
help_text='assigned mail domain for this domain',
verbose_name='mail domain',
on_delete=models.CASCADE)),
null=True,
to="domains.MailDomain",
blank=True,
help_text="assigned mail domain for this domain",
verbose_name="mail domain",
on_delete=models.CASCADE,
),
),
],
options={
'verbose_name': 'Hosting domain',
'verbose_name_plural': 'Hosting domains',
"verbose_name": "Hosting domain",
"verbose_name_plural": "Hosting domains",
},
bases=(models.Model,),
),
migrations.AddField(
model_name='maildomain',
name='customer',
model_name="maildomain",
name="customer",
field=models.ForeignKey(
verbose_name='customer', blank=True,
to=settings.AUTH_USER_MODEL, null=True,
on_delete=models.CASCADE),
verbose_name="customer",
blank=True,
to=settings.AUTH_USER_MODEL,
null=True,
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterField(
model_name='maildomain',
name='domain',
model_name="maildomain",
name="domain",
field=models.CharField(
unique=True, max_length=128, verbose_name='domain name'),
unique=True, max_length=128, verbose_name="domain name"
),
preserve_default=True,
),
]

View file

@ -1,199 +1,285 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
from django.conf import settings
import model_utils.fields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('domains', '0002_auto_20150124_1909'),
("domains", "0002_auto_20150124_1909"),
]
operations = [
migrations.CreateModel(
name='DNSComment',
name="DNSComment",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('name', models.CharField(max_length=255)),
('commenttype',
models.CharField(max_length=10, db_column='type')),
('modified_at', models.IntegerField()),
('comment', models.CharField(max_length=65535)),
('customer', models.ForeignKey(
verbose_name='customer',
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("name", models.CharField(max_length=255)),
("commenttype", models.CharField(max_length=10, db_column="type")),
("modified_at", models.IntegerField()),
("comment", models.CharField(max_length=65535)),
(
"customer",
models.ForeignKey(
verbose_name="customer",
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
],
),
migrations.RunSQL(
'''ALTER TABLE domains_dnscomment ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
"""ALTER TABLE domains_dnscomment ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))"""
),
migrations.CreateModel(
name='DNSCryptoKey',
name="DNSCryptoKey",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('flags', models.IntegerField()),
('active', models.BooleanField(default=True)),
('content', models.TextField()),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("flags", models.IntegerField()),
("active", models.BooleanField(default=True)),
("content", models.TextField()),
],
),
migrations.CreateModel(
name='DNSDomain',
name="DNSDomain",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('domain', models.CharField(
unique=True, max_length=255, verbose_name='domain name')),
('master',
models.CharField(max_length=128, null=True, blank=True)),
('last_check', models.IntegerField(null=True)),
('domaintype', models.CharField(
max_length=6, db_column='type',
choices=[('MASTER', 'Master'),
('SLAVE', 'Slave'),
('NATIVE', 'Native')])),
('notified_serial', models.IntegerField(null=True)),
('customer', models.ForeignKey(
verbose_name='customer', blank=True,
to=settings.AUTH_USER_MODEL, null=True,
on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"domain",
models.CharField(
unique=True, max_length=255, verbose_name="domain name"
),
),
("master", models.CharField(max_length=128, null=True, blank=True)),
("last_check", models.IntegerField(null=True)),
(
"domaintype",
models.CharField(
max_length=6,
db_column="type",
choices=[
("MASTER", "Master"),
("SLAVE", "Slave"),
("NATIVE", "Native"),
],
),
),
("notified_serial", models.IntegerField(null=True)),
(
"customer",
models.ForeignKey(
verbose_name="customer",
blank=True,
to=settings.AUTH_USER_MODEL,
null=True,
on_delete=models.CASCADE,
),
),
],
options={
'verbose_name': 'DNS domain',
'verbose_name_plural': 'DNS domains',
"verbose_name": "DNS domain",
"verbose_name_plural": "DNS domains",
},
),
migrations.RunSQL(
'''ALTER TABLE domains_dnsdomain ADD CONSTRAINT c_lowercase_name
CHECK (((domain)::TEXT = LOWER((domain)::TEXT)))'''
"""ALTER TABLE domains_dnsdomain ADD CONSTRAINT c_lowercase_name
CHECK (((domain)::TEXT = LOWER((domain)::TEXT)))"""
),
migrations.CreateModel(
name='DNSDomainMetadata',
name="DNSDomainMetadata",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('kind', models.CharField(max_length=32)),
('content', models.TextField()),
('domain', models.ForeignKey(
to='domains.DNSDomain', on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("kind", models.CharField(max_length=32)),
("content", models.TextField()),
(
"domain",
models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE),
),
],
),
migrations.CreateModel(
name='DNSRecord',
name="DNSRecord",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('name', models.CharField(
db_index=True, max_length=255, null=True, blank=True)),
('recordtype', models.CharField(
max_length=10, null=True, db_column='type', blank=True)),
('content', models.CharField(
max_length=65535, null=True, blank=True)),
('ttl', models.IntegerField(null=True)),
('prio', models.IntegerField(null=True)),
('change_date', models.IntegerField(null=True)),
('disabled', models.BooleanField(default=False)),
('ordername', models.CharField(max_length=255)),
('auth', models.BooleanField(default=True)),
('domain', models.ForeignKey(
to='domains.DNSDomain', on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"name",
models.CharField(
db_index=True, max_length=255, null=True, blank=True
),
),
(
"recordtype",
models.CharField(
max_length=10, null=True, db_column="type", blank=True
),
),
("content", models.CharField(max_length=65535, null=True, blank=True)),
("ttl", models.IntegerField(null=True)),
("prio", models.IntegerField(null=True)),
("change_date", models.IntegerField(null=True)),
("disabled", models.BooleanField(default=False)),
("ordername", models.CharField(max_length=255)),
("auth", models.BooleanField(default=True)),
(
"domain",
models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE),
),
],
options={
'verbose_name': 'DNS record',
'verbose_name_plural': 'DNS records',
"verbose_name": "DNS record",
"verbose_name_plural": "DNS records",
},
),
migrations.RunSQL(
'''ALTER TABLE domains_dnsrecord ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
"""ALTER TABLE domains_dnsrecord ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))"""
),
migrations.RunSQL(
'''CREATE INDEX recordorder ON domains_dnsrecord (domain_id,
ordername text_pattern_ops)'''
"""CREATE INDEX recordorder ON domains_dnsrecord (domain_id,
ordername text_pattern_ops)"""
),
migrations.CreateModel(
name='DNSSupermaster',
name="DNSSupermaster",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('ip', models.GenericIPAddressField()),
('nameserver', models.CharField(max_length=255)),
('customer', models.ForeignKey(
verbose_name='customer', to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("ip", models.GenericIPAddressField()),
("nameserver", models.CharField(max_length=255)),
(
"customer",
models.ForeignKey(
verbose_name="customer",
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
],
),
migrations.CreateModel(
name='DNSTSIGKey',
name="DNSTSIGKey",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('name', models.CharField(max_length=255)),
('algorithm', models.CharField(max_length=50)),
('secret', models.CharField(max_length=255)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("name", models.CharField(max_length=255)),
("algorithm", models.CharField(max_length=50)),
("secret", models.CharField(max_length=255)),
],
),
migrations.RunSQL(
'''ALTER TABLE domains_dnstsigkey ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))'''
"""ALTER TABLE domains_dnstsigkey ADD CONSTRAINT c_lowercase_name
CHECK (((name)::TEXT = LOWER((name)::TEXT)))"""
),
migrations.AlterField(
model_name='hostingdomain',
name='domain',
model_name="hostingdomain",
name="domain",
field=models.CharField(
unique=True, max_length=255, verbose_name='domain name'),
unique=True, max_length=255, verbose_name="domain name"
),
),
migrations.AlterField(
model_name='maildomain',
name='domain',
model_name="maildomain",
name="domain",
field=models.CharField(
unique=True, max_length=255, verbose_name='domain name'),
unique=True, max_length=255, verbose_name="domain name"
),
),
migrations.AddField(
model_name='dnscryptokey',
name='domain',
field=models.ForeignKey(
to='domains.DNSDomain', on_delete=models.CASCADE),
model_name="dnscryptokey",
name="domain",
field=models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE),
),
migrations.AddField(
model_name='dnscomment',
name='domain',
field=models.ForeignKey(
to='domains.DNSDomain', on_delete=models.CASCADE),
model_name="dnscomment",
name="domain",
field=models.ForeignKey(to="domains.DNSDomain", on_delete=models.CASCADE),
),
migrations.AlterUniqueTogether(
name='dnssupermaster',
unique_together=set([('ip', 'nameserver')]),
name="dnssupermaster",
unique_together=set([("ip", "nameserver")]),
),
migrations.AlterUniqueTogether(
name='dnstsigkey',
unique_together=set([('name', 'algorithm')]),
name="dnstsigkey",
unique_together=set([("name", "algorithm")]),
),
migrations.AlterIndexTogether(
name='dnsrecord',
index_together=set([('name', 'recordtype')]),
name="dnsrecord",
index_together=set([("name", "recordtype")]),
),
migrations.AlterIndexTogether(
name='dnscomment',
index_together={('name', 'commenttype'), ('domain', 'modified_at')},
name="dnscomment",
index_together={("name", "commenttype"), ("domain", "modified_at")},
),
]

View file

@ -1,44 +1,87 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('domains', '0003_auto_20151105_2133'),
("domains", "0003_auto_20151105_2133"),
]
operations = [
migrations.AlterModelOptions(
name='dnscomment',
options={'verbose_name': 'DNS comment', 'verbose_name_plural': 'DNS comments'},
name="dnscomment",
options={
"verbose_name": "DNS comment",
"verbose_name_plural": "DNS comments",
},
),
migrations.AlterModelOptions(
name='dnscryptokey',
options={'verbose_name': 'DNS crypto key', 'verbose_name_plural': 'DNS crypto keys'},
name="dnscryptokey",
options={
"verbose_name": "DNS crypto key",
"verbose_name_plural": "DNS crypto keys",
},
),
migrations.AlterModelOptions(
name='dnsdomainmetadata',
options={'verbose_name': 'DNS domain metadata item', 'verbose_name_plural': 'DNS domain metadata items'},
name="dnsdomainmetadata",
options={
"verbose_name": "DNS domain metadata item",
"verbose_name_plural": "DNS domain metadata items",
},
),
migrations.AlterModelOptions(
name='dnssupermaster',
options={'verbose_name': 'DNS supermaster', 'verbose_name_plural': 'DNS supermasters'},
name="dnssupermaster",
options={
"verbose_name": "DNS supermaster",
"verbose_name_plural": "DNS supermasters",
},
),
migrations.AlterModelOptions(
name='dnstsigkey',
options={'verbose_name': 'DNS TSIG key', 'verbose_name_plural': 'DNS TSIG keys'},
name="dnstsigkey",
options={
"verbose_name": "DNS TSIG key",
"verbose_name_plural": "DNS TSIG keys",
},
),
migrations.AlterField(
model_name='dnsdomainmetadata',
name='kind',
field=models.CharField(max_length=32, choices=[('ALLOW-DNSUPDATE-FROM', 'ALLOW-DNSUPDATE-FROM'), ('ALSO-NOTIFY', 'ALSO-NOTIFY'), ('AXFR-MASTER-TSIG', 'AXFR-MASTER-TSIG'), ('AXFR-SOURCE', 'AXFR-SOURCE'), ('FORWARD-DNSUPDATE', 'FORWARD-DNSUPDATE'), ('GSS-ACCEPTOR-PRINCIPAL', 'GSS-ACCEPTOR-PRINCIPAL'), ('GSS-ALLOW-AXFR-PRINCIPAL', 'GSS-ALLOW-AXFR-PRINCIPAL'), ('LUA-AXFR-SCRIPT', 'LUA-AXFR-SCRIPT'), ('NSEC3NARROW', 'NSEC3NARROW'), ('NSEC3PARAM', 'NSEC3PARAM'), ('PRESIGNED', 'PRESIGNED'), ('PUBLISH_CDNSKEY', 'PUBLISH_CDNSKEY'), ('PUBLISH_CDS', 'PUBLISH_CDS'), ('SOA-EDIT', 'SOA-EDIT'), ('SOA-EDIT-DNSUPDATE', 'SOA-EDIT-DNSUPDATE'), ('TSIG-ALLOW-AXFR', 'TSIG-ALLOW-AXFR'), ('TSIG-ALLOW-DNSUPDATE', 'TSIG-ALLOW-DNSUPDATE')]),
model_name="dnsdomainmetadata",
name="kind",
field=models.CharField(
max_length=32,
choices=[
("ALLOW-DNSUPDATE-FROM", "ALLOW-DNSUPDATE-FROM"),
("ALSO-NOTIFY", "ALSO-NOTIFY"),
("AXFR-MASTER-TSIG", "AXFR-MASTER-TSIG"),
("AXFR-SOURCE", "AXFR-SOURCE"),
("FORWARD-DNSUPDATE", "FORWARD-DNSUPDATE"),
("GSS-ACCEPTOR-PRINCIPAL", "GSS-ACCEPTOR-PRINCIPAL"),
("GSS-ALLOW-AXFR-PRINCIPAL", "GSS-ALLOW-AXFR-PRINCIPAL"),
("LUA-AXFR-SCRIPT", "LUA-AXFR-SCRIPT"),
("NSEC3NARROW", "NSEC3NARROW"),
("NSEC3PARAM", "NSEC3PARAM"),
("PRESIGNED", "PRESIGNED"),
("PUBLISH_CDNSKEY", "PUBLISH_CDNSKEY"),
("PUBLISH_CDS", "PUBLISH_CDS"),
("SOA-EDIT", "SOA-EDIT"),
("SOA-EDIT-DNSUPDATE", "SOA-EDIT-DNSUPDATE"),
("TSIG-ALLOW-AXFR", "TSIG-ALLOW-AXFR"),
("TSIG-ALLOW-DNSUPDATE", "TSIG-ALLOW-DNSUPDATE"),
],
),
),
migrations.AlterField(
model_name='dnstsigkey',
name='algorithm',
field=models.CharField(max_length=50, choices=[('hmac-md5', 'HMAC MD5'), ('hmac-sha1', 'HMAC SHA1'), ('hmac-sha224', 'HMAC SHA224'), ('hmac-sha256', 'HMAC SHA256'), ('hmac-sha384', 'HMAC SHA384'), ('hmac-sha512', 'HMAC SHA512')]),
model_name="dnstsigkey",
name="algorithm",
field=models.CharField(
max_length=50,
choices=[
("hmac-md5", "HMAC MD5"),
("hmac-sha1", "HMAC SHA1"),
("hmac-sha224", "HMAC SHA224"),
("hmac-sha256", "HMAC SHA256"),
("hmac-sha384", "HMAC SHA384"),
("hmac-sha512", "HMAC SHA512"),
],
),
),
]

View file

@ -2,51 +2,48 @@
This module contains models related to domain names.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.db import models, transaction
from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from model_utils.models import TimeStampedModel
from django.db import models, transaction
from django.utils.translation import gettext as _
from model_utils import Choices
from model_utils.models import TimeStampedModel
DNS_DOMAIN_TYPES = Choices(
('MASTER', _('Master')),
('SLAVE', _('Slave')),
('NATIVE', _('Native')),
("MASTER", _("Master")),
("SLAVE", _("Slave")),
("NATIVE", _("Native")),
)
# see https://doc.powerdns.com/md/authoritative/domainmetadata/
DNS_DOMAIN_METADATA_KINDS = Choices(
'ALLOW-DNSUPDATE-FROM',
'ALSO-NOTIFY',
'AXFR-MASTER-TSIG',
'AXFR-SOURCE',
'FORWARD-DNSUPDATE',
'GSS-ACCEPTOR-PRINCIPAL',
'GSS-ALLOW-AXFR-PRINCIPAL',
'LUA-AXFR-SCRIPT',
'NSEC3NARROW',
'NSEC3PARAM',
'PRESIGNED',
'PUBLISH_CDNSKEY',
'PUBLISH_CDS',
'SOA-EDIT',
'SOA-EDIT-DNSUPDATE',
'TSIG-ALLOW-AXFR',
'TSIG-ALLOW-DNSUPDATE',
"ALLOW-DNSUPDATE-FROM",
"ALSO-NOTIFY",
"AXFR-MASTER-TSIG",
"AXFR-SOURCE",
"FORWARD-DNSUPDATE",
"GSS-ACCEPTOR-PRINCIPAL",
"GSS-ALLOW-AXFR-PRINCIPAL",
"LUA-AXFR-SCRIPT",
"NSEC3NARROW",
"NSEC3PARAM",
"PRESIGNED",
"PUBLISH_CDNSKEY",
"PUBLISH_CDS",
"SOA-EDIT",
"SOA-EDIT-DNSUPDATE",
"TSIG-ALLOW-AXFR",
"TSIG-ALLOW-DNSUPDATE",
)
DNS_TSIG_KEY_ALGORITHMS = Choices(
('hmac-md5', _('HMAC MD5')),
('hmac-sha1', _('HMAC SHA1')),
('hmac-sha224', _('HMAC SHA224')),
('hmac-sha256', _('HMAC SHA256')),
('hmac-sha384', _('HMAC SHA384')),
('hmac-sha512', _('HMAC SHA512')),
("hmac-md5", _("HMAC MD5")),
("hmac-sha1", _("HMAC SHA1")),
("hmac-sha224", _("HMAC SHA224")),
("hmac-sha256", _("HMAC SHA256")),
("hmac-sha384", _("HMAC SHA384")),
("hmac-sha512", _("HMAC SHA512")),
)
@ -55,16 +52,20 @@ class DomainBase(TimeStampedModel):
This is the base model for domains.
"""
domain = models.CharField(_('domain name'), max_length=255, unique=True)
domain = models.CharField(_("domain name"), max_length=255, unique=True)
customer = models.ForeignKey(
settings.AUTH_USER_MODEL, verbose_name=_('customer'), blank=True,
null=True, on_delete=models.CASCADE)
settings.AUTH_USER_MODEL,
verbose_name=_("customer"),
blank=True,
null=True,
on_delete=models.CASCADE,
)
class Meta:
abstract = True
@python_2_unicode_compatible
class MailDomain(DomainBase):
"""
This is the model for mail domains. Mail domains are used to configure the
@ -72,9 +73,10 @@ class MailDomain(DomainBase):
domains.
"""
class Meta:
verbose_name = _('Mail domain')
verbose_name_plural = _('Mail domains')
class Meta(DomainBase.Meta):
verbose_name = _("Mail domain")
verbose_name_plural = _("Mail domains")
def __str__(self):
return self.domain
@ -85,6 +87,7 @@ class MailDomain(DomainBase):
"""
return self.mailaddress_set.all()
mailaddresses = property(get_mailaddresses)
@ -93,47 +96,52 @@ class HostingDomainManager(models.Manager):
Default Manager for :py:class:`HostingDomain`.
"""
@transaction.atomic
def create_for_hosting_package(
self, hosting_package, domain, commit, **kwargs
):
def create_for_hosting_package(self, hosting_package, domain, commit, **kwargs):
from hostingpackages.models import CustomerHostingPackageDomain
hostingdomain = self.create(
customer=hosting_package.customer, domain=domain, **kwargs)
customer=hosting_package.customer, domain=domain, **kwargs
)
hostingdomain.maildomain = MailDomain.objects.create(
customer=hosting_package.customer, domain=domain)
customer=hosting_package.customer, domain=domain
)
custdomain = CustomerHostingPackageDomain.objects.create(
hosting_package=hosting_package, domain=hostingdomain)
hosting_package=hosting_package, domain=hostingdomain
)
if commit:
hostingdomain.save()
custdomain.save()
return hostingdomain
@python_2_unicode_compatible
class HostingDomain(DomainBase):
"""
This is the model for hosting domains. A hosting domain is linked to a
customer hosting account.
"""
maildomain = models.OneToOneField(
MailDomain, verbose_name=_('mail domain'), blank=True, null=True,
help_text=_('assigned mail domain for this domain'),
MailDomain,
verbose_name=_("mail domain"),
blank=True,
null=True,
help_text=_("assigned mail domain for this domain"),
on_delete=models.CASCADE,
)
objects = HostingDomainManager()
class Meta:
verbose_name = _('Hosting domain')
verbose_name_plural = _('Hosting domains')
verbose_name = _("Hosting domain")
verbose_name_plural = _("Hosting domains")
def __str__(self):
return self.domain
@python_2_unicode_compatible
class DNSDomain(DomainBase):
"""
This model represents a DNS zone. The model is similar to the domain table
@ -157,24 +165,25 @@ class DNSDomain(DomainBase):
CREATE UNIQUE INDEX name_index ON domains(name);
"""
# name is represented by domain
master = models.CharField(max_length=128, blank=True, null=True)
last_check = models.IntegerField(null=True)
domaintype = models.CharField(
max_length=6, choices=DNS_DOMAIN_TYPES, db_column='type')
max_length=6, choices=DNS_DOMAIN_TYPES, db_column="type"
)
notified_serial = models.IntegerField(null=True)
# account is represented by customer_id
# check constraint is added via RunSQL in migration
class Meta:
verbose_name = _('DNS domain')
verbose_name_plural = _('DNS domains')
verbose_name = _("DNS domain")
verbose_name_plural = _("DNS domains")
def __str__(self):
return self.domain
@python_2_unicode_compatible
class DNSRecord(models.Model):
"""
This model represents a DNS record. The model is similar to the record
@ -209,11 +218,12 @@ class DNSRecord(models.Model):
domain_id, ordername text_pattern_ops);
"""
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
name = models.CharField(
max_length=255, blank=True, null=True, db_index=True)
domain = models.ForeignKey("DNSDomain", on_delete=models.CASCADE)
name = models.CharField(max_length=255, blank=True, null=True, db_index=True)
recordtype = models.CharField(
max_length=10, blank=True, null=True, db_column='type')
max_length=10, blank=True, null=True, db_column="type"
)
content = models.CharField(max_length=65535, blank=True, null=True)
ttl = models.IntegerField(null=True)
prio = models.IntegerField(null=True)
@ -224,18 +234,16 @@ class DNSRecord(models.Model):
# check constraint and index recordorder are added via RunSQL in migration
class Meta:
verbose_name = _('DNS record')
verbose_name_plural = _('DNS records')
index_together = [
['name', 'recordtype']
]
verbose_name = _("DNS record")
verbose_name_plural = _("DNS records")
index_together = [["name", "recordtype"]]
def __str__(self):
return "{name} IN {type} {content}".format(
name=self.name, type=self.recordtype, content=self.content)
name=self.name, type=self.recordtype, content=self.content
)
@python_2_unicode_compatible
class DNSSupermaster(models.Model):
"""
This model represents the supermasters table in the PowerDNS schema
@ -252,26 +260,23 @@ class DNSSupermaster(models.Model):
);
"""
ip = models.GenericIPAddressField()
nameserver = models.CharField(max_length=255)
# account is replaced by customer
customer = models.ForeignKey(
settings.AUTH_USER_MODEL, verbose_name=_('customer'),
on_delete=models.CASCADE)
class Meta:
verbose_name = _('DNS supermaster')
verbose_name_plural = _('DNS supermasters')
unique_together = (
('ip', 'nameserver')
settings.AUTH_USER_MODEL, verbose_name=_("customer"), on_delete=models.CASCADE
)
class Meta:
verbose_name = _("DNS supermaster")
verbose_name_plural = _("DNS supermasters")
unique_together = ("ip", "nameserver")
def __str__(self):
return "{ip} {nameserver}".format(
ip=self.ip, nameserver=self.nameserver)
return "{ip} {nameserver}".format(ip=self.ip, nameserver=self.nameserver)
@python_2_unicode_compatible
class DNSComment(models.Model):
"""
This model represents the comments table in the PowerDNS schema specified
@ -301,31 +306,29 @@ class DNSComment(models.Model):
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
"""
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
domain = models.ForeignKey("DNSDomain", on_delete=models.CASCADE)
name = models.CharField(max_length=255)
commenttype = models.CharField(max_length=10, db_column='type')
commenttype = models.CharField(max_length=10, db_column="type")
modified_at = models.IntegerField()
# account is replaced by customer
customer = models.ForeignKey(
settings.AUTH_USER_MODEL, verbose_name=_('customer'),
on_delete=models.CASCADE)
settings.AUTH_USER_MODEL, verbose_name=_("customer"), on_delete=models.CASCADE
)
comment = models.CharField(max_length=65535)
# check constraint is added via RunSQL in migration
class Meta:
verbose_name = _('DNS comment')
verbose_name_plural = _('DNS comments')
index_together = [
['name', 'commenttype'],
['domain', 'modified_at']
]
verbose_name = _("DNS comment")
verbose_name_plural = _("DNS comments")
index_together = [["name", "commenttype"], ["domain", "modified_at"]]
def __str__(self):
return "{name} IN {type}: {comment}".format(
name=self.name, type=self.commenttype, comment=self.comment)
name=self.name, type=self.commenttype, comment=self.comment
)
@python_2_unicode_compatible
class DNSDomainMetadata(models.Model):
"""
This model represents the domainmetadata table in the PowerDNS schema
@ -346,20 +349,21 @@ class DNSDomainMetadata(models.Model):
CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);
"""
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
domain = models.ForeignKey("DNSDomain", on_delete=models.CASCADE)
kind = models.CharField(max_length=32, choices=DNS_DOMAIN_METADATA_KINDS)
content = models.TextField()
class Meta:
verbose_name = _('DNS domain metadata item')
verbose_name_plural = _('DNS domain metadata items')
verbose_name = _("DNS domain metadata item")
verbose_name_plural = _("DNS domain metadata items")
def __str__(self):
return "{domain} {kind} {content}".format(
domain=self.domain.domain, kind=self.kind, content=self.content)
domain=self.domain.domain, kind=self.kind, content=self.content
)
@python_2_unicode_compatible
class DNSCryptoKey(models.Model):
"""
This model represents the cryptokeys table in the PowerDNS schema
@ -379,21 +383,22 @@ class DNSCryptoKey(models.Model):
CREATE INDEX domainidindex ON cryptokeys(domain_id);
"""
domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE)
domain = models.ForeignKey("DNSDomain", on_delete=models.CASCADE)
flags = models.IntegerField()
active = models.BooleanField(default=True)
content = models.TextField()
class Meta:
verbose_name = _('DNS crypto key')
verbose_name_plural = _('DNS crypto keys')
verbose_name = _("DNS crypto key")
verbose_name_plural = _("DNS crypto keys")
def __str__(self):
return "{domain} {content}".format(
domain=self.domain.domain, content=self.content)
domain=self.domain.domain, content=self.content
)
@python_2_unicode_compatible
class DNSTSIGKey(models.Model):
"""
This model represents the tsigkeys table in the PowerDNS schema specified
@ -413,19 +418,18 @@ class DNSTSIGKey(models.Model):
CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
"""
name = models.CharField(max_length=255)
algorithm = models.CharField(
max_length=50, choices=DNS_TSIG_KEY_ALGORITHMS)
algorithm = models.CharField(max_length=50, choices=DNS_TSIG_KEY_ALGORITHMS)
secret = models.CharField(max_length=255)
# check constraint is added via RunSQL in migration
class Meta:
verbose_name = _('DNS TSIG key')
verbose_name_plural = _('DNS TSIG keys')
unique_together = [
['name', 'algorithm']
]
verbose_name = _("DNS TSIG key")
verbose_name_plural = _("DNS TSIG keys")
unique_together = [["name", "algorithm"]]
def __str__(self):
return "{name} {algorithm} XXXX".format(
name=self.name, algorithm=self.algorithm)
name=self.name, algorithm=self.algorithm
)

View file

@ -7,9 +7,9 @@ from unittest.mock import MagicMock, Mock, patch
from django.forms import ValidationError
from django.test import TestCase
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from domains.forms import relative_domain_validator, CreateHostingDomainForm
from domains.forms import CreateHostingDomainForm, relative_domain_validator
class RelativeDomainValidatorTest(TestCase):

View file

@ -2,14 +2,16 @@
This module defines the URL patterns for domain related views.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf.urls import url
from django.urls import re_path
from .views import CreateHostingDomain
urlpatterns = [
url(r'^(?P<package>\d+)/create$', CreateHostingDomain.as_view(),
name='create_hosting_domain'),
re_path(
r"^(?P<package>\d+)/create$",
CreateHostingDomain.as_view(),
name="create_hosting_domain",
),
]

View file

@ -2,15 +2,16 @@
This module defines views related to domains.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from braces.views import StaffuserRequiredMixin
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.views.generic.edit import CreateView
from hostingpackages.models import CustomerHostingPackage
from .forms import CreateHostingDomainForm
from .models import HostingDomain

View file

@ -2,7 +2,7 @@
This module provides context processor implementations for gnuviechadmin.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
import logging
@ -22,38 +22,42 @@ def navigation(request):
:rtype: dict
"""
if request.is_ajax():
if request.headers.get("x-requested-with") == "XMLHttpRequest":
return {}
context = {
'webmail_url': settings.GVA_LINK_WEBMAIL,
'phpmyadmin_url': settings.GVA_LINK_PHPMYADMIN,
'phppgadmin_url': settings.GVA_LINK_PHPPGADMIN,
'active_item': 'dashboard',
"webmail_url": settings.GVA_LINK_WEBMAIL,
"phpmyadmin_url": settings.GVA_LINK_PHPMYADMIN,
"phppgadmin_url": settings.GVA_LINK_PHPPGADMIN,
"active_item": "dashboard",
}
if request.resolver_match:
viewfunc = request.resolver_match.func
viewmodule = viewfunc.__module__
if viewmodule == 'contact_form.views':
context['active_item'] = 'contact'
if viewmodule == "contact_form.views":
context["active_item"] = "contact"
elif viewmodule in (
'hostingpackages.views', 'osusers.views', 'userdbs.views',
'managemails.views', 'websites.views', 'domains.views',
"hostingpackages.views",
"osusers.views",
"userdbs.views",
"managemails.views",
"websites.views",
"domains.views",
):
context['active_item'] = 'hostingpackage'
elif viewmodule in (
'allauth.account.views', 'allauth.socialaccount.views'
context["active_item"] = "hostingpackage"
elif viewmodule in ("allauth.account.views", "allauth.socialaccount.views"):
context["active_item"] = "account"
elif viewmodule == "django.contrib.flatpages.views" and request.path.endswith(
"/impressum/"
):
context['active_item'] = 'account'
elif (
viewmodule == 'django.contrib.flatpages.views' and
request.path.endswith('/impressum/')
):
context['active_item'] = 'imprint'
elif not viewmodule.startswith('django.contrib.admin'):
context["active_item"] = "imprint"
elif not viewmodule.startswith("django.contrib.admin"):
_LOGGER.debug(
'no special handling for view %s in module %s, fallback to '
'default active menu item %s',
viewfunc.__name__, viewmodule, context['active_item'])
"no special handling for view %s in module %s, fallback to "
"default active menu item %s",
viewfunc.__name__,
viewmodule,
context["active_item"],
)
return context
@ -64,6 +68,6 @@ def version_info(request):
"""
context = {
'gnuviechadmin_version': gvaversion,
"gnuviechadmin_version": gvaversion,
}
return context

View file

@ -8,10 +8,8 @@ Common settings and globals.
from os.path import abspath, basename, dirname, join, normpath
from django.contrib.messages import constants as messages
from gvacommon.settings_utils import get_env_variable
# ######### PATH CONFIGURATION
# Absolute filesystem path to the Django project directory:
DJANGO_ROOT = dirname(dirname(abspath(__file__)))
@ -28,7 +26,7 @@ GVA_ENVIRONMENT = get_env_variable("GVA_ENVIRONMENT", default="prod")
# ######### DEBUG CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = (GVA_ENVIRONMENT == "local")
DEBUG = GVA_ENVIRONMENT == "local"
# ######### END DEBUG CONFIGURATION
@ -58,6 +56,8 @@ DATABASES = {
"PORT": get_env_variable("GVA_PGSQL_PORT", int, default=5432),
}
}
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# ######### END DATABASE CONFIGURATION

View file

@ -1,28 +1,26 @@
from __future__ import absolute_import
import debug_toolbar
from django.conf.urls import include, url
from django.conf.urls import include
from django.contrib import admin
from django.contrib.flatpages import views
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import path
from django.urls import path, re_path
admin.autodiscover()
urlpatterns = [
url(r'', include('dashboard.urls')),
url(r'^accounts/', include('allauth.urls')),
url(r'^database/', include('userdbs.urls')),
url(r'^domains/', include('domains.urls')),
url(r'^hosting/', include('hostingpackages.urls')),
url(r'^website/', include('websites.urls')),
url(r'^mail/', include('managemails.urls')),
url(r'^osuser/', include('osusers.urls')),
url(r'^admin/', admin.site.urls),
url(r'^contact/', include('contact_form.urls')),
url(r'^impressum/$', views.flatpage, {
'url': '/impressum/'
}, name='imprint'),
re_path(r"", include("dashboard.urls")),
re_path(r"^accounts/", include("allauth.urls")),
re_path(r"^database/", include("userdbs.urls")),
re_path(r"^domains/", include("domains.urls")),
re_path(r"^hosting/", include("hostingpackages.urls")),
re_path(r"^website/", include("websites.urls")),
re_path(r"^mail/", include("managemails.urls")),
re_path(r"^osuser/", include("osusers.urls")),
re_path(r"^admin/", admin.site.urls),
re_path(r"^contact/", include("contact_form.urls")),
re_path(r"^impressum/$", views.flatpage, {"url": "/impressum/"}, name="imprint"),
]
# Uncomment the next line to serve media files in dev.
@ -30,5 +28,5 @@ urlpatterns = [
urlpatterns += staticfiles_urlpatterns()
urlpatterns += [
path('__debug__/', include(debug_toolbar.urls)),
path("__debug__/", include(debug_toolbar.urls)),
]

View file

@ -3,11 +3,10 @@ This module defines form classes that can be extended by other gnuviechadmin
apps' forms.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
"""
@ -21,11 +20,14 @@ class PasswordModelFormMixin(forms.Form):
whether both fields contain the same string.
"""
password1 = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
label=_("Password"),
widget=forms.PasswordInput,
)
password2 = forms.CharField(
label=_('Password (again)'), widget=forms.PasswordInput,
label=_("Password (again)"),
widget=forms.PasswordInput,
)
def clean_password2(self):
@ -36,8 +38,8 @@ class PasswordModelFormMixin(forms.Form):
:rtype: str or None
"""
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(PASSWORD_MISMATCH_ERROR)
return password2

View file

@ -2,9 +2,10 @@
This module defines common view code to be used by multiple gnuviechadmin apps.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.shortcuts import get_object_or_404
from hostingpackages.models import CustomerHostingPackage
@ -14,7 +15,8 @@ class HostingPackageAndCustomerMixin(object):
keyword argument 'package'.
"""
hosting_package_kwarg = 'package'
hosting_package_kwarg = "package"
"""Keyword argument used to find the hosting package in the URL."""
hostingpackage = None
@ -22,8 +24,8 @@ class HostingPackageAndCustomerMixin(object):
def get_hosting_package(self):
if self.hostingpackage is None:
self.hostingpackage = get_object_or_404(
CustomerHostingPackage,
pk=int(self.kwargs[self.hosting_package_kwarg]))
CustomerHostingPackage, pk=int(self.kwargs[self.hosting_package_kwarg])
)
return self.hostingpackage
def get_customer_object(self):

View file

@ -2,4 +2,3 @@
This app takes care of hosting packages.
"""
default_app_config = 'hostingpackages.apps.HostingPackagesAppConfig'

View file

@ -2,7 +2,7 @@
This module contains the admin site interface for hosting packages.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django import forms
from django.contrib import admin
@ -25,9 +25,10 @@ class CustomerHostingPackageCreateForm(forms.ModelForm):
This is the form class for creating new customer hosting packages.
"""
class Meta:
model = CustomerHostingPackage
fields = ['customer', 'template', 'name']
fields = ["customer", "template", "name"]
def save(self, **kwargs):
"""
@ -39,10 +40,11 @@ class CustomerHostingPackageCreateForm(forms.ModelForm):
"""
hostinpackages = CustomerHostingPackage.objects.create_from_template(
customer=self.cleaned_data['customer'],
template=self.cleaned_data['template'],
name=self.cleaned_data['name'],
**kwargs)
customer=self.cleaned_data["customer"],
template=self.cleaned_data["template"],
name=self.cleaned_data["name"],
**kwargs
)
return hostinpackages
def save_m2m(self):
@ -55,6 +57,7 @@ class CustomerDiskSpaceOptionInline(admin.TabularInline):
space options.
"""
model = CustomerDiskSpaceOption
extra = 0
@ -65,6 +68,7 @@ class CustomerMailboxOptionInline(admin.TabularInline):
mailbox options.
"""
model = CustomerMailboxOption
extra = 0
@ -75,6 +79,7 @@ class CustomerUserDatabaseOptionInline(admin.TabularInline):
database options.
"""
model = CustomerUserDatabaseOption
extra = 0
@ -85,6 +90,7 @@ class CustomerHostingPackageDomainInline(admin.TabularInline):
hosting packages.
"""
model = CustomerHostingPackageDomain
extra = 0
@ -95,12 +101,9 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin):
:py:class:`CustomerHostingPackage`.
"""
add_form = CustomerHostingPackageCreateForm
add_fieldsets = (
(None, {
'fields': ('customer', 'template', 'name')
}),
)
add_fieldsets = ((None, {"fields": ("customer", "template", "name")}),)
inlines = [
CustomerDiskSpaceOptionInline,
@ -108,7 +111,7 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin):
CustomerUserDatabaseOptionInline,
CustomerHostingPackageDomainInline,
]
list_display = ['name', 'customer', 'osuser']
list_display = ["name", "customer", "osuser"]
def get_form(self, request, obj=None, **kwargs):
"""
@ -125,13 +128,16 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin):
"""
defaults = {}
if obj is None:
defaults.update({
'form': self.add_form,
'fields': admin.options.flatten_fieldsets(self.add_fieldsets),
})
defaults.update(
{
"form": self.add_form,
"fields": admin.options.flatten_fieldsets(self.add_fieldsets),
}
)
defaults.update(kwargs)
return super(CustomerHostingPackageAdmin, self).get_form(
request, obj, **defaults)
request, obj, **defaults
)
def get_readonly_fields(self, request, obj=None):
"""
@ -147,7 +153,7 @@ class CustomerHostingPackageAdmin(admin.ModelAdmin):
"""
if obj:
return ['customer', 'template']
return ["customer", "template"]
return []

View file

@ -3,9 +3,8 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`hostingpackages` app.
"""
from __future__ import unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class HostingPackagesAppConfig(AppConfig):
@ -13,5 +12,6 @@ class HostingPackagesAppConfig(AppConfig):
AppConfig for the :py:mod:`hostingpackages` app.
"""
name = 'hostingpackages'
verbose_name = _('Hosting Packages and Options')
name = "hostingpackages"
verbose_name = _("Hosting Packages and Options")

View file

@ -2,17 +2,13 @@
This module contains the form classes related to hosting packages.
"""
from __future__ import absolute_import, unicode_literals
from django import forms
from django.urls import reverse
from django.utils.translation import ugettext as _
from __future__ import absolute_import
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
Submit,
)
from crispy_forms.layout import Layout, Submit
from django import forms
from django.urls import reverse
from django.utils.translation import gettext as _
from .models import (
CustomerDiskSpaceOption,
@ -28,25 +24,24 @@ class CreateCustomerHostingPackageForm(forms.ModelForm):
a preselected customer.
"""
class Meta:
model = CustomerHostingPackage
fields = ['template', 'name', 'description']
fields = ["template", "name", "description"]
def __init__(self, instance, *args, **kwargs):
username = kwargs.pop('user')
super(CreateCustomerHostingPackageForm, self).__init__(
*args, **kwargs
)
self.fields['description'].widget.attrs['rows'] = 2
username = kwargs.pop("user")
super(CreateCustomerHostingPackageForm, self).__init__(*args, **kwargs)
self.fields["description"].widget.attrs["rows"] = 2
self.helper = FormHelper()
self.helper.form_action = reverse(
'create_customer_hosting_package', kwargs={'user': username}
"create_customer_hosting_package", kwargs={"user": username}
)
self.helper.layout = Layout(
'template',
'name',
'description',
Submit('submit', _('Add Hosting Package')),
"template",
"name",
"description",
Submit("submit", _("Add Hosting Package")),
)
@ -55,44 +50,44 @@ class CreateHostingPackageForm(forms.ModelForm):
This form class is used for creating new customer hosting packages.
"""
class Meta:
model = CustomerHostingPackage
fields = ['customer', 'template', 'name', 'description']
fields = ["customer", "template", "name", "description"]
def __init__(self, instance, *args, **kwargs):
super(CreateHostingPackageForm, self).__init__(
*args, **kwargs
)
self.fields['description'].widget.attrs['rows'] = 2
super(CreateHostingPackageForm, self).__init__(*args, **kwargs)
self.fields["description"].widget.attrs["rows"] = 2
self.helper = FormHelper()
self.helper.form_action = reverse('create_hosting_package')
self.helper.form_action = reverse("create_hosting_package")
self.helper.layout = Layout(
'customer',
'template',
'name',
'description',
Submit('submit', _('Add Hosting Package')),
"customer",
"template",
"name",
"description",
Submit("submit", _("Add Hosting Package")),
)
class AddDiskspaceOptionForm(forms.ModelForm):
class Meta:
model = CustomerDiskSpaceOption
fields = ['diskspace', 'diskspace_unit']
fields = ["diskspace", "diskspace_unit"]
def __init__(self, *args, **kwargs):
self.hostingpackage = kwargs.pop('hostingpackage')
self.option_template = kwargs.pop('option_template')
self.hostingpackage = kwargs.pop("hostingpackage")
self.option_template = kwargs.pop("option_template")
super(AddDiskspaceOptionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_action = reverse(
'add_hosting_option',
"add_hosting_option",
kwargs={
'package': self.hostingpackage.id,
'type': 'diskspace',
'optionid': self.option_template.id,
})
self.helper.add_input(Submit('submit', _('Add disk space option')))
"package": self.hostingpackage.id,
"type": "diskspace",
"optionid": self.option_template.id,
},
)
self.helper.add_input(Submit("submit", _("Add disk space option")))
def save(self, commit=True):
self.instance.hosting_package = self.hostingpackage
@ -103,21 +98,22 @@ class AddDiskspaceOptionForm(forms.ModelForm):
class AddMailboxOptionForm(forms.ModelForm):
class Meta:
model = CustomerMailboxOption
fields = ['number']
fields = ["number"]
def __init__(self, *args, **kwargs):
self.hostingpackage = kwargs.pop('hostingpackage')
self.option_template = kwargs.pop('option_template')
self.hostingpackage = kwargs.pop("hostingpackage")
self.option_template = kwargs.pop("option_template")
super(AddMailboxOptionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_action = reverse(
'add_hosting_option',
"add_hosting_option",
kwargs={
'package': self.hostingpackage.id,
'type': 'mailboxes',
'optionid': self.option_template.id,
})
self.helper.add_input(Submit('submit', _('Add mailbox option')))
"package": self.hostingpackage.id,
"type": "mailboxes",
"optionid": self.option_template.id,
},
)
self.helper.add_input(Submit("submit", _("Add mailbox option")))
def save(self, commit=True):
self.instance.hosting_package = self.hostingpackage
@ -128,21 +124,22 @@ class AddMailboxOptionForm(forms.ModelForm):
class AddUserDatabaseOptionForm(forms.ModelForm):
class Meta:
model = CustomerUserDatabaseOption
fields = ['number']
fields = ["number"]
def __init__(self, *args, **kwargs):
self.hostingpackage = kwargs.pop('hostingpackage')
self.option_template = kwargs.pop('option_template')
self.hostingpackage = kwargs.pop("hostingpackage")
self.option_template = kwargs.pop("option_template")
super(AddUserDatabaseOptionForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_action = reverse(
'add_hosting_option',
"add_hosting_option",
kwargs={
'package': self.hostingpackage.id,
'type': 'databases',
'optionid': self.option_template.id,
})
self.helper.add_input(Submit('submit', _('Add database option')))
"package": self.hostingpackage.id,
"type": "databases",
"optionid": self.option_template.id,
},
)
self.helper.add_input(Submit("submit", _("Add database option")))
def save(self, commit=True):
self.instance.hosting_package = self.hostingpackage

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone
import model_utils.fields
from django.conf import settings
@ -14,301 +12,469 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='CustomerHostingPackage',
name="CustomerHostingPackage",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('name', models.CharField(
unique=True, max_length=128, verbose_name='name')),
('description', models.TextField(
verbose_name='description', blank=True)),
('mailboxcount', models.PositiveIntegerField(
verbose_name='mailbox count')),
('diskspace', models.PositiveIntegerField(
help_text='disk space for the hosting package',
verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(
verbose_name='unit of disk space',
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
('customer', models.ForeignKey(
verbose_name='customer', to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)),
],
options={
'verbose_name': 'customer hosting package',
'verbose_name_plural': 'customer hosting packages',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='CustomerHostingPackageOption',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
],
options={
'verbose_name': 'customer hosting option',
'verbose_name_plural': 'customer hosting options',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='CustomerDiskSpaceOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
to='hostingpackages.CustomerHostingPackageOption',
on_delete=models.CASCADE)),
('diskspace', models.PositiveIntegerField(
verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(
verbose_name='unit of disk space',
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'ordering': ['diskspace_unit', 'diskspace'],
'abstract': False,
'verbose_name': 'Disk space option',
'verbose_name_plural': 'Disk space options',
},
bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
auto_created=True,
primary_key=True,
),
migrations.CreateModel(
name='CustomerMailboxOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False,
to='hostingpackages.CustomerHostingPackageOption',
on_delete=models.CASCADE)),
('number', models.PositiveIntegerField(
unique=True, verbose_name='number of mailboxes')),
],
options={
'ordering': ['number'],
'abstract': False,
'verbose_name': 'Mailbox option',
'verbose_name_plural': 'Mailbox options',
},
bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
),
migrations.CreateModel(
name='CustomerUserDatabaseOption',
fields=[
('customerhostingpackageoption_ptr', models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False,
to='hostingpackages.CustomerHostingPackageOption',
on_delete=models.CASCADE)),
('number', models.PositiveIntegerField(
default=1, verbose_name='number of databases')),
('db_type', models.PositiveSmallIntegerField(
verbose_name='database type',
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
],
options={
'ordering': ['db_type', 'number'],
'abstract': False,
'verbose_name': 'Database option',
'verbose_name_plural': 'Database options',
},
bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"name",
models.CharField(unique=True, max_length=128, verbose_name="name"),
),
(
"description",
models.TextField(verbose_name="description", blank=True),
),
(
"mailboxcount",
models.PositiveIntegerField(verbose_name="mailbox count"),
),
(
"diskspace",
models.PositiveIntegerField(
help_text="disk space for the hosting package",
verbose_name="disk space",
),
),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
(
"customer",
models.ForeignKey(
verbose_name="customer",
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
migrations.CreateModel(
name='HostingOption',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
],
options={
'verbose_name': 'Hosting option',
'verbose_name_plural': 'Hosting options',
"verbose_name": "customer hosting package",
"verbose_name_plural": "customer hosting packages",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='DiskSpaceOption',
name="CustomerHostingPackageOption",
fields=[
('hostingoption_ptr', models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False, to='hostingpackages.HostingOption',
on_delete=models.CASCADE)),
('diskspace', models.PositiveIntegerField(
verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(
verbose_name='unit of disk space',
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'ordering': ['diskspace_unit', 'diskspace'],
'abstract': False,
'verbose_name': 'Disk space option',
'verbose_name_plural': 'Disk space options',
},
bases=('hostingpackages.hostingoption', models.Model),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
migrations.CreateModel(
name='HostingPackageTemplate',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('name', models.CharField(
unique=True, max_length=128, verbose_name='name')),
('description', models.TextField(
verbose_name='description', blank=True)),
('mailboxcount', models.PositiveIntegerField(
verbose_name='mailbox count')),
('diskspace', models.PositiveIntegerField(
help_text='disk space for the hosting package',
verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(
verbose_name='unit of disk space',
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'verbose_name': 'Hosting package',
'verbose_name_plural': 'Hosting packages',
"verbose_name": "customer hosting option",
"verbose_name_plural": "customer hosting options",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='MailboxOption',
name="CustomerDiskSpaceOption",
fields=[
('hostingoption_ptr', models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False, to='hostingpackages.HostingOption',
on_delete=models.CASCADE)),
('number', models.PositiveIntegerField(
unique=True, verbose_name='number of mailboxes')),
(
"customerhostingpackageoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.CustomerHostingPackageOption",
on_delete=models.CASCADE,
),
),
("diskspace", models.PositiveIntegerField(verbose_name="disk space")),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
],
options={
'ordering': ['number'],
'abstract': False,
'verbose_name': 'Mailbox option',
'verbose_name_plural': 'Mailbox options',
"ordering": ["diskspace_unit", "diskspace"],
"abstract": False,
"verbose_name": "Disk space option",
"verbose_name_plural": "Disk space options",
},
bases=('hostingpackages.hostingoption', models.Model),
bases=("hostingpackages.customerhostingpackageoption", models.Model),
),
migrations.CreateModel(
name='UserDatabaseOption',
name="CustomerMailboxOption",
fields=[
('hostingoption_ptr', models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False, to='hostingpackages.HostingOption',
on_delete=models.CASCADE)),
('number', models.PositiveIntegerField(
default=1, verbose_name='number of databases')),
('db_type', models.PositiveSmallIntegerField(
verbose_name='database type',
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
(
"customerhostingpackageoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.CustomerHostingPackageOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
unique=True, verbose_name="number of mailboxes"
),
),
],
options={
'ordering': ['db_type', 'number'],
'abstract': False,
'verbose_name': 'Database option',
'verbose_name_plural': 'Database options',
"ordering": ["number"],
"abstract": False,
"verbose_name": "Mailbox option",
"verbose_name_plural": "Mailbox options",
},
bases=('hostingpackages.hostingoption', models.Model),
bases=("hostingpackages.customerhostingpackageoption", models.Model),
),
migrations.CreateModel(
name="CustomerUserDatabaseOption",
fields=[
(
"customerhostingpackageoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.CustomerHostingPackageOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
default=1, verbose_name="number of databases"
),
),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
],
options={
"ordering": ["db_type", "number"],
"abstract": False,
"verbose_name": "Database option",
"verbose_name_plural": "Database options",
},
bases=("hostingpackages.customerhostingpackageoption", models.Model),
),
migrations.CreateModel(
name="HostingOption",
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
],
options={
"verbose_name": "Hosting option",
"verbose_name_plural": "Hosting options",
},
bases=(models.Model,),
),
migrations.CreateModel(
name="DiskSpaceOption",
fields=[
(
"hostingoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.HostingOption",
on_delete=models.CASCADE,
),
),
("diskspace", models.PositiveIntegerField(verbose_name="disk space")),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
],
options={
"ordering": ["diskspace_unit", "diskspace"],
"abstract": False,
"verbose_name": "Disk space option",
"verbose_name_plural": "Disk space options",
},
bases=("hostingpackages.hostingoption", models.Model),
),
migrations.CreateModel(
name="HostingPackageTemplate",
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"name",
models.CharField(unique=True, max_length=128, verbose_name="name"),
),
(
"description",
models.TextField(verbose_name="description", blank=True),
),
(
"mailboxcount",
models.PositiveIntegerField(verbose_name="mailbox count"),
),
(
"diskspace",
models.PositiveIntegerField(
help_text="disk space for the hosting package",
verbose_name="disk space",
),
),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
],
options={
"verbose_name": "Hosting package",
"verbose_name_plural": "Hosting packages",
},
bases=(models.Model,),
),
migrations.CreateModel(
name="MailboxOption",
fields=[
(
"hostingoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.HostingOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
unique=True, verbose_name="number of mailboxes"
),
),
],
options={
"ordering": ["number"],
"abstract": False,
"verbose_name": "Mailbox option",
"verbose_name_plural": "Mailbox options",
},
bases=("hostingpackages.hostingoption", models.Model),
),
migrations.CreateModel(
name="UserDatabaseOption",
fields=[
(
"hostingoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.HostingOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
default=1, verbose_name="number of databases"
),
),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
],
options={
"ordering": ["db_type", "number"],
"abstract": False,
"verbose_name": "Database option",
"verbose_name_plural": "Database options",
},
bases=("hostingpackages.hostingoption", models.Model),
),
migrations.AlterUniqueTogether(
name='userdatabaseoption',
unique_together={('number', 'db_type')},
name="userdatabaseoption",
unique_together={("number", "db_type")},
),
migrations.AlterUniqueTogether(
name='diskspaceoption',
unique_together={('diskspace', 'diskspace_unit')},
name="diskspaceoption",
unique_together={("diskspace", "diskspace_unit")},
),
migrations.AddField(
model_name='customeruserdatabaseoption',
name='template',
model_name="customeruserdatabaseoption",
name="template",
field=models.ForeignKey(
verbose_name='user database option template',
to='hostingpackages.UserDatabaseOption',
help_text='The user database option template that this '
'hosting option is based on',
on_delete=models.CASCADE),
verbose_name="user database option template",
to="hostingpackages.UserDatabaseOption",
help_text="The user database option template that this "
"hosting option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customeruserdatabaseoption',
unique_together={('number', 'db_type')},
name="customeruserdatabaseoption",
unique_together={("number", "db_type")},
),
migrations.AddField(
model_name='customermailboxoption',
name='template',
model_name="customermailboxoption",
name="template",
field=models.ForeignKey(
verbose_name='mailbox option template',
to='hostingpackages.UserDatabaseOption',
help_text='The mailbox option template that this hosting '
'option is based on',
on_delete=models.CASCADE),
verbose_name="mailbox option template",
to="hostingpackages.UserDatabaseOption",
help_text="The mailbox option template that this hosting "
"option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AddField(
model_name='customerhostingpackageoption',
name='hosting_package',
model_name="customerhostingpackageoption",
name="hosting_package",
field=models.ForeignKey(
verbose_name='hosting package',
to='hostingpackages.CustomerHostingPackage',
on_delete=models.CASCADE),
verbose_name="hosting package",
to="hostingpackages.CustomerHostingPackage",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AddField(
model_name='customerhostingpackage',
name='template',
model_name="customerhostingpackage",
name="template",
field=models.ForeignKey(
verbose_name='hosting package template',
to='hostingpackages.HostingPackageTemplate',
help_text='The hosting package template that this hosting '
'package is based on.',
on_delete=models.CASCADE),
verbose_name="hosting package template",
to="hostingpackages.HostingPackageTemplate",
help_text="The hosting package template that this hosting "
"package is based on.",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AddField(
model_name='customerdiskspaceoption',
name='template',
model_name="customerdiskspaceoption",
name="template",
field=models.ForeignKey(
verbose_name='disk space option template',
to='hostingpackages.DiskSpaceOption',
help_text='The disk space option template that this hosting '
'option is based on',
on_delete=models.CASCADE),
verbose_name="disk space option template",
to="hostingpackages.DiskSpaceOption",
help_text="The disk space option template that this hosting "
"option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customerdiskspaceoption',
unique_together={('diskspace', 'diskspace_unit')},
name="customerdiskspaceoption",
unique_together={("diskspace", "diskspace_unit")},
),
]

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone
import model_utils.fields
from django.conf import settings
@ -8,361 +6,530 @@ from django.db import migrations, models
class Migration(migrations.Migration):
replaces = [('hostingpackages', '0001_initial'),
('hostingpackages', '0002_auto_20150118_1149'),
('hostingpackages', '0003_auto_20150118_1221'),
('hostingpackages', '0004_customerhostingpackage_osuser'),
('hostingpackages', '0005_auto_20150118_1303')]
replaces = [
("hostingpackages", "0001_initial"),
("hostingpackages", "0002_auto_20150118_1149"),
("hostingpackages", "0003_auto_20150118_1221"),
("hostingpackages", "0004_customerhostingpackage_osuser"),
("hostingpackages", "0005_auto_20150118_1303"),
]
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('osusers', '0004_auto_20150104_1751'),
("osusers", "0004_auto_20150104_1751"),
]
operations = [
migrations.CreateModel(
name='CustomerHostingPackage',
name="CustomerHostingPackage",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('name', models.CharField(
unique=True, max_length=128, verbose_name='name')),
('description', models.TextField(
verbose_name='description', blank=True)),
('mailboxcount', models.PositiveIntegerField(
verbose_name='mailbox count')),
('diskspace', models.PositiveIntegerField(
help_text='disk space for the hosting package',
verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(
verbose_name='unit of disk space',
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
('customer', models.ForeignKey(
verbose_name='customer',
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
options={
'verbose_name': 'customer hosting package',
'verbose_name_plural': 'customer hosting packages',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='CustomerHostingPackageOption',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
],
options={
'verbose_name': 'customer hosting option',
'verbose_name_plural': 'customer hosting options',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='CustomerDiskSpaceOption',
fields=[
('customerhostingpackageoption_ptr',
models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
to='hostingpackages.CustomerHostingPackageOption',
on_delete=models.CASCADE)),
('diskspace', models.PositiveIntegerField(
verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(
verbose_name='unit of disk space',
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'ordering': ['diskspace_unit', 'diskspace'],
'abstract': False,
'verbose_name': 'Disk space option',
'verbose_name_plural': 'Disk space options',
},
bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
auto_created=True,
primary_key=True,
),
migrations.CreateModel(
name='CustomerMailboxOption',
fields=[
('customerhostingpackageoption_ptr',
models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False,
to='hostingpackages.CustomerHostingPackageOption',
on_delete=models.CASCADE)),
('number', models.PositiveIntegerField(
unique=True, verbose_name='number of mailboxes')),
],
options={
'ordering': ['number'],
'abstract': False,
'verbose_name': 'Mailbox option',
'verbose_name_plural': 'Mailbox options',
},
bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
),
migrations.CreateModel(
name='CustomerUserDatabaseOption',
fields=[
('customerhostingpackageoption_ptr',
models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False,
to='hostingpackages.CustomerHostingPackageOption',
on_delete=models.CASCADE)),
('number', models.PositiveIntegerField(
default=1, verbose_name='number of databases')),
('db_type', models.PositiveSmallIntegerField(
verbose_name='database type',
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
],
options={
'ordering': ['db_type', 'number'],
'abstract': False,
'verbose_name': 'Database option',
'verbose_name_plural': 'Database options',
},
bases=(
'hostingpackages.customerhostingpackageoption', models.Model),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
migrations.CreateModel(
name='HostingOption',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
],
options={
'verbose_name': 'Hosting option',
'verbose_name_plural': 'Hosting options',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='DiskSpaceOption',
fields=[
('hostingoption_ptr',
models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False, to='hostingpackages.HostingOption',
on_delete=models.CASCADE)),
('diskspace', models.PositiveIntegerField(
verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(
verbose_name='unit of disk space',
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'ordering': ['diskspace_unit', 'diskspace'],
'abstract': False,
'verbose_name': 'Disk space option',
'verbose_name_plural': 'Disk space options',
},
bases=('hostingpackages.hostingoption', models.Model),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
migrations.CreateModel(
name='HostingPackageTemplate',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('name', models.CharField(
unique=True, max_length=128, verbose_name='name')),
('description', models.TextField(
verbose_name='description', blank=True)),
('mailboxcount', models.PositiveIntegerField(
verbose_name='mailbox count')),
('diskspace', models.PositiveIntegerField(
help_text='disk space for the hosting package',
verbose_name='disk space')),
('diskspace_unit', models.PositiveSmallIntegerField(
verbose_name='unit of disk space',
choices=[(0, 'MiB'), (1, 'GiB'), (2, 'TiB')])),
],
options={
'verbose_name': 'Hosting package',
'verbose_name_plural': 'Hosting packages',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='MailboxOption',
fields=[
('hostingoption_ptr',
models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False, to='hostingpackages.HostingOption',
on_delete=models.CASCADE)),
('number', models.PositiveIntegerField(
unique=True, verbose_name='number of mailboxes')),
],
options={
'ordering': ['number'],
'abstract': False,
'verbose_name': 'Mailbox option',
'verbose_name_plural': 'Mailbox options',
},
bases=('hostingpackages.hostingoption', models.Model),
(
"name",
models.CharField(unique=True, max_length=128, verbose_name="name"),
),
migrations.CreateModel(
name='UserDatabaseOption',
fields=[
('hostingoption_ptr',
models.OneToOneField(
parent_link=True, auto_created=True, primary_key=True,
serialize=False, to='hostingpackages.HostingOption',
on_delete=models.CASCADE)),
('number', models.PositiveIntegerField(
default=1, verbose_name='number of databases')),
('db_type',
(
"description",
models.TextField(verbose_name="description", blank=True),
),
(
"mailboxcount",
models.PositiveIntegerField(verbose_name="mailbox count"),
),
(
"diskspace",
models.PositiveIntegerField(
help_text="disk space for the hosting package",
verbose_name="disk space",
),
),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name='database type',
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
(
"customer",
models.ForeignKey(
verbose_name="customer",
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
],
options={
'ordering': ['db_type', 'number'],
'abstract': False,
'verbose_name': 'Database option',
'verbose_name_plural': 'Database options',
"verbose_name": "customer hosting package",
"verbose_name_plural": "customer hosting packages",
},
bases=('hostingpackages.hostingoption', models.Model),
bases=(models.Model,),
),
migrations.CreateModel(
name="CustomerHostingPackageOption",
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
],
options={
"verbose_name": "customer hosting option",
"verbose_name_plural": "customer hosting options",
},
bases=(models.Model,),
),
migrations.CreateModel(
name="CustomerDiskSpaceOption",
fields=[
(
"customerhostingpackageoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.CustomerHostingPackageOption",
on_delete=models.CASCADE,
),
),
("diskspace", models.PositiveIntegerField(verbose_name="disk space")),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
],
options={
"ordering": ["diskspace_unit", "diskspace"],
"abstract": False,
"verbose_name": "Disk space option",
"verbose_name_plural": "Disk space options",
},
bases=("hostingpackages.customerhostingpackageoption", models.Model),
),
migrations.CreateModel(
name="CustomerMailboxOption",
fields=[
(
"customerhostingpackageoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.CustomerHostingPackageOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
unique=True, verbose_name="number of mailboxes"
),
),
],
options={
"ordering": ["number"],
"abstract": False,
"verbose_name": "Mailbox option",
"verbose_name_plural": "Mailbox options",
},
bases=("hostingpackages.customerhostingpackageoption", models.Model),
),
migrations.CreateModel(
name="CustomerUserDatabaseOption",
fields=[
(
"customerhostingpackageoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.CustomerHostingPackageOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
default=1, verbose_name="number of databases"
),
),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
],
options={
"ordering": ["db_type", "number"],
"abstract": False,
"verbose_name": "Database option",
"verbose_name_plural": "Database options",
},
bases=("hostingpackages.customerhostingpackageoption", models.Model),
),
migrations.CreateModel(
name="HostingOption",
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
],
options={
"verbose_name": "Hosting option",
"verbose_name_plural": "Hosting options",
},
bases=(models.Model,),
),
migrations.CreateModel(
name="DiskSpaceOption",
fields=[
(
"hostingoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.HostingOption",
on_delete=models.CASCADE,
),
),
("diskspace", models.PositiveIntegerField(verbose_name="disk space")),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
],
options={
"ordering": ["diskspace_unit", "diskspace"],
"abstract": False,
"verbose_name": "Disk space option",
"verbose_name_plural": "Disk space options",
},
bases=("hostingpackages.hostingoption", models.Model),
),
migrations.CreateModel(
name="HostingPackageTemplate",
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"name",
models.CharField(unique=True, max_length=128, verbose_name="name"),
),
(
"description",
models.TextField(verbose_name="description", blank=True),
),
(
"mailboxcount",
models.PositiveIntegerField(verbose_name="mailbox count"),
),
(
"diskspace",
models.PositiveIntegerField(
help_text="disk space for the hosting package",
verbose_name="disk space",
),
),
(
"diskspace_unit",
models.PositiveSmallIntegerField(
verbose_name="unit of disk space",
choices=[(0, "MiB"), (1, "GiB"), (2, "TiB")],
),
),
],
options={
"verbose_name": "Hosting package",
"verbose_name_plural": "Hosting packages",
},
bases=(models.Model,),
),
migrations.CreateModel(
name="MailboxOption",
fields=[
(
"hostingoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.HostingOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
unique=True, verbose_name="number of mailboxes"
),
),
],
options={
"ordering": ["number"],
"abstract": False,
"verbose_name": "Mailbox option",
"verbose_name_plural": "Mailbox options",
},
bases=("hostingpackages.hostingoption", models.Model),
),
migrations.CreateModel(
name="UserDatabaseOption",
fields=[
(
"hostingoption_ptr",
models.OneToOneField(
parent_link=True,
auto_created=True,
primary_key=True,
serialize=False,
to="hostingpackages.HostingOption",
on_delete=models.CASCADE,
),
),
(
"number",
models.PositiveIntegerField(
default=1, verbose_name="number of databases"
),
),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
],
options={
"ordering": ["db_type", "number"],
"abstract": False,
"verbose_name": "Database option",
"verbose_name_plural": "Database options",
},
bases=("hostingpackages.hostingoption", models.Model),
),
migrations.AlterUniqueTogether(
name='userdatabaseoption',
unique_together={('number', 'db_type')},
name="userdatabaseoption",
unique_together={("number", "db_type")},
),
migrations.AlterUniqueTogether(
name='diskspaceoption',
unique_together={('diskspace', 'diskspace_unit')},
name="diskspaceoption",
unique_together={("diskspace", "diskspace_unit")},
),
migrations.AddField(
model_name='customeruserdatabaseoption',
name='template',
model_name="customeruserdatabaseoption",
name="template",
field=models.ForeignKey(
verbose_name='user database option template',
to='hostingpackages.UserDatabaseOption',
help_text='The user database option template that this '
'hosting option is based on',
on_delete=models.CASCADE),
verbose_name="user database option template",
to="hostingpackages.UserDatabaseOption",
help_text="The user database option template that this "
"hosting option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customeruserdatabaseoption',
unique_together={('number', 'db_type')},
name="customeruserdatabaseoption",
unique_together={("number", "db_type")},
),
migrations.AddField(
model_name='customermailboxoption',
name='template',
model_name="customermailboxoption",
name="template",
field=models.ForeignKey(
verbose_name='mailbox option template',
to='hostingpackages.UserDatabaseOption',
help_text='The mailbox option template that this mailbox '
'option is based on',
on_delete=models.CASCADE),
verbose_name="mailbox option template",
to="hostingpackages.UserDatabaseOption",
help_text="The mailbox option template that this mailbox "
"option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AddField(
model_name='customerhostingpackageoption',
name='hosting_package',
model_name="customerhostingpackageoption",
name="hosting_package",
field=models.ForeignKey(
verbose_name='hosting package',
to='hostingpackages.CustomerHostingPackage',
on_delete=models.CASCADE),
verbose_name="hosting package",
to="hostingpackages.CustomerHostingPackage",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AddField(
model_name='customerhostingpackage',
name='template',
model_name="customerhostingpackage",
name="template",
field=models.ForeignKey(
verbose_name='hosting package template',
to='hostingpackages.HostingPackageTemplate',
help_text='The hosting package template that this hosting '
'package is based on',
on_delete=models.CASCADE),
verbose_name="hosting package template",
to="hostingpackages.HostingPackageTemplate",
help_text="The hosting package template that this hosting "
"package is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AddField(
model_name='customerdiskspaceoption',
name='template',
model_name="customerdiskspaceoption",
name="template",
field=models.ForeignKey(
verbose_name='disk space option template',
to='hostingpackages.DiskSpaceOption',
help_text='The disk space option template that this hosting '
'option is based on',
on_delete=models.CASCADE),
verbose_name="disk space option template",
to="hostingpackages.DiskSpaceOption",
help_text="The disk space option template that this hosting "
"option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customerdiskspaceoption',
unique_together={('diskspace', 'diskspace_unit')},
name="customerdiskspaceoption",
unique_together={("diskspace", "diskspace_unit")},
),
migrations.AlterField(
model_name='customerdiskspaceoption',
name='template',
model_name="customerdiskspaceoption",
name="template",
field=models.ForeignKey(
verbose_name='disk space option template',
to='hostingpackages.DiskSpaceOption',
help_text='The disk space option template that this disk '
'space option is based on',
on_delete=models.CASCADE),
verbose_name="disk space option template",
to="hostingpackages.DiskSpaceOption",
help_text="The disk space option template that this disk "
"space option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterField(
model_name='customeruserdatabaseoption',
name='template',
model_name="customeruserdatabaseoption",
name="template",
field=models.ForeignKey(
verbose_name='user database option template',
to='hostingpackages.UserDatabaseOption',
help_text='The user database option template that this '
'database option is based on',
on_delete=models.CASCADE),
verbose_name="user database option template",
to="hostingpackages.UserDatabaseOption",
help_text="The user database option template that this "
"database option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterField(
model_name='customerhostingpackage',
name='name',
field=models.CharField(max_length=128, verbose_name='name'),
model_name="customerhostingpackage",
name="name",
field=models.CharField(max_length=128, verbose_name="name"),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customerhostingpackage',
unique_together={('customer', 'name')},
name="customerhostingpackage",
unique_together={("customer", "name")},
),
migrations.AddField(
model_name='customerhostingpackage',
name='osuser',
model_name="customerhostingpackage",
name="osuser",
field=models.OneToOneField(
null=True, blank=True, to='osusers.User',
verbose_name='Operating system user', on_delete=models.CASCADE),
null=True,
blank=True,
to="osusers.User",
verbose_name="Operating system user",
on_delete=models.CASCADE,
),
preserve_default=True,
),
]

View file

@ -1,57 +1,59 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0001_initial'),
("hostingpackages", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name='customerdiskspaceoption',
name='template',
model_name="customerdiskspaceoption",
name="template",
field=models.ForeignKey(
verbose_name='disk space option template',
to='hostingpackages.DiskSpaceOption',
help_text='The disk space option template that this disk '
'space option is based on',
on_delete=models.CASCADE),
verbose_name="disk space option template",
to="hostingpackages.DiskSpaceOption",
help_text="The disk space option template that this disk "
"space option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterField(
model_name='customerhostingpackage',
name='template',
model_name="customerhostingpackage",
name="template",
field=models.ForeignKey(
verbose_name='hosting package template',
to='hostingpackages.HostingPackageTemplate',
help_text='The hosting package template that this hosting '
'package is based on',
on_delete=models.CASCADE),
verbose_name="hosting package template",
to="hostingpackages.HostingPackageTemplate",
help_text="The hosting package template that this hosting "
"package is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterField(
model_name='customermailboxoption',
name='template',
model_name="customermailboxoption",
name="template",
field=models.ForeignKey(
verbose_name='mailbox option template',
to='hostingpackages.UserDatabaseOption',
help_text='The mailbox option template that this mailbox '
'option is based on',
on_delete=models.CASCADE),
verbose_name="mailbox option template",
to="hostingpackages.UserDatabaseOption",
help_text="The mailbox option template that this mailbox "
"option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterField(
model_name='customeruserdatabaseoption',
name='template',
model_name="customeruserdatabaseoption",
name="template",
field=models.ForeignKey(
verbose_name='user database option template',
to='hostingpackages.UserDatabaseOption',
help_text='The user database option template that this '
'database option is based on',
on_delete=models.CASCADE),
verbose_name="user database option template",
to="hostingpackages.UserDatabaseOption",
help_text="The user database option template that this "
"database option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
]

View file

@ -1,18 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0001_squashed_0005_auto_20150118_1303'),
("hostingpackages", "0001_squashed_0005_auto_20150118_1303"),
]
operations = [
migrations.AlterModelOptions(
name='hostingoption',
name="hostingoption",
options={},
),
]

View file

@ -1,24 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0002_auto_20150118_1149'),
("hostingpackages", "0002_auto_20150118_1149"),
]
operations = [
migrations.AlterField(
model_name='customerhostingpackage',
name='name',
field=models.CharField(max_length=128, verbose_name='name'),
model_name="customerhostingpackage",
name="name",
field=models.CharField(max_length=128, verbose_name="name"),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='customerhostingpackage',
unique_together=set([('customer', 'name')]),
name="customerhostingpackage",
unique_together=set([("customer", "name")]),
),
]

View file

@ -1,24 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0002_auto_20150118_1319'),
("hostingpackages", "0002_auto_20150118_1319"),
]
operations = [
migrations.AlterField(
model_name='customermailboxoption',
name='template',
model_name="customermailboxoption",
name="template",
field=models.ForeignKey(
verbose_name='mailbox option template',
to='hostingpackages.MailboxOption',
help_text='The mailbox option template that this mailbox '
'option is based on',
on_delete=models.CASCADE),
verbose_name="mailbox option template",
to="hostingpackages.MailboxOption",
help_text="The mailbox option template that this mailbox "
"option is based on",
on_delete=models.CASCADE,
),
preserve_default=True,
),
]

View file

@ -1,22 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osusers', '0004_auto_20150104_1751'),
('hostingpackages', '0003_auto_20150118_1221'),
("osusers", "0004_auto_20150104_1751"),
("hostingpackages", "0003_auto_20150118_1221"),
]
operations = [
migrations.AddField(
model_name='customerhostingpackage',
name='osuser',
model_name="customerhostingpackage",
name="osuser",
field=models.ForeignKey(
verbose_name='Operating system user', blank=True,
to='osusers.User', null=True, on_delete=models.CASCADE),
verbose_name="Operating system user",
blank=True,
to="osusers.User",
null=True,
on_delete=models.CASCADE,
),
preserve_default=True,
),
]

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone
import model_utils.fields
from django.db import migrations, models
@ -8,33 +6,59 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('domains', '0002_auto_20150124_1909'),
('hostingpackages', '0003_auto_20150118_1407'),
("domains", "0002_auto_20150124_1909"),
("hostingpackages", "0003_auto_20150118_1407"),
]
operations = [
migrations.CreateModel(
name='CustomerHostingPackageDomain',
name="CustomerHostingPackageDomain",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('domain', models.OneToOneField(
verbose_name='hosting domain', to='domains.HostingDomain',
on_delete=models.CASCADE)),
('hosting_package', models.ForeignKey(
related_name='domains', verbose_name='hosting package',
to='hostingpackages.CustomerHostingPackage',
on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"domain",
models.OneToOneField(
verbose_name="hosting domain",
to="domains.HostingDomain",
on_delete=models.CASCADE,
),
),
(
"hosting_package",
models.ForeignKey(
related_name="domains",
verbose_name="hosting package",
to="hostingpackages.CustomerHostingPackage",
on_delete=models.CASCADE,
),
),
],
options={
'abstract': False,
"abstract": False,
},
bases=(models.Model,),
),

View file

@ -1,21 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0004_customerhostingpackage_osuser'),
("hostingpackages", "0004_customerhostingpackage_osuser"),
]
operations = [
migrations.AlterField(
model_name='customerhostingpackage',
name='osuser',
model_name="customerhostingpackage",
name="osuser",
field=models.OneToOneField(
null=True, blank=True, to='osusers.User',
verbose_name='Operating system user', on_delete=models.CASCADE),
null=True,
blank=True,
to="osusers.User",
verbose_name="Operating system user",
on_delete=models.CASCADE,
),
preserve_default=True,
),
]

View file

@ -1,22 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0004_customerhostingpackagedomain'),
("hostingpackages", "0004_customerhostingpackagedomain"),
]
operations = [
migrations.AlterModelOptions(
name='diskspaceoption',
name="diskspaceoption",
options={},
),
migrations.AlterUniqueTogether(
name='customerdiskspaceoption',
name="customerdiskspaceoption",
unique_together=set([]),
),
]

View file

@ -1,22 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hostingpackages', '0005_auto_20150125_1508'),
("hostingpackages", "0005_auto_20150125_1508"),
]
operations = [
migrations.AlterModelOptions(
name='userdatabaseoption',
name="userdatabaseoption",
options={},
),
migrations.AlterUniqueTogether(
name='customeruserdatabaseoption',
name="customeruserdatabaseoption",
unique_together=set([]),
),
]

View file

@ -2,21 +2,20 @@
This module contains the hosting package models.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf import settings
from django.db import transaction
from django.db import models
from django.db import models, transaction
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _, ungettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from model_utils import Choices
from model_utils.models import TimeStampedModel
from domains.models import HostingDomain
from managemails.models import Mailbox
from osusers.models import AdditionalGroup, Group, User as OsUser
from osusers.models import AdditionalGroup, Group
from osusers.models import User as OsUser
from userdbs.models import DB_TYPES, UserDatabase
DISK_SPACE_UNITS = Choices((0, "M", _("MiB")), (1, "G", _("GiB")), (2, "T", _("TiB")))
@ -24,7 +23,6 @@ DISK_SPACE_UNITS = Choices((0, "M", _("MiB")), (1, "G", _("GiB")), (2, "T", _("T
DISK_SPACE_FACTORS = ((1, None, None), (1024, 1, None), (1024 * 1024, 1024, 1))
@python_2_unicode_compatible
class HostingPackageBase(TimeStampedModel):
description = models.TextField(_("description"), blank=True)
mailboxcount = models.PositiveIntegerField(_("mailbox count"))
@ -57,7 +55,6 @@ class HostingOption(TimeStampedModel):
"""
@python_2_unicode_compatible
class DiskSpaceOptionBase(models.Model):
diskspace = models.PositiveIntegerField(_("disk space"))
diskspace_unit = models.PositiveSmallIntegerField(
@ -87,7 +84,6 @@ class DiskSpaceOption(DiskSpaceOptionBase, HostingOption):
unique_together = ["diskspace", "diskspace_unit"]
@python_2_unicode_compatible
class UserDatabaseOptionBase(models.Model):
number = models.PositiveIntegerField(_("number of databases"), default=1)
db_type = models.PositiveSmallIntegerField(_("database type"), choices=DB_TYPES)
@ -99,7 +95,7 @@ class UserDatabaseOptionBase(models.Model):
verbose_name_plural = _("Database options")
def __str__(self):
return ungettext(
return ngettext(
"{type} database", "{count} {type} databases", self.number
).format(type=self.get_db_type_display(), count=self.number)
@ -115,7 +111,6 @@ class UserDatabaseOption(UserDatabaseOptionBase, HostingOption):
unique_together = ["number", "db_type"]
@python_2_unicode_compatible
class MailboxOptionBase(models.Model):
"""
Base class for mailbox options.
@ -131,7 +126,7 @@ class MailboxOptionBase(models.Model):
verbose_name_plural = _("Mailbox options")
def __str__(self):
return ungettext(
return ngettext(
"{count} additional mailbox", "{count} additional mailboxes", self.number
).format(count=self.number)
@ -177,7 +172,6 @@ class CustomerHostingPackageManager(models.Manager):
return package
@python_2_unicode_compatible
class CustomerHostingPackage(HostingPackageBase):
"""
This class defines customer specific hosting packages.
@ -382,7 +376,6 @@ class CustomerHostingPackage(HostingPackageBase):
return super(CustomerHostingPackage, self).save(*args, **kwargs)
@python_2_unicode_compatible
class CustomerHostingPackageDomain(TimeStampedModel):
"""
This class defines the relationship from a hosting package to a hosting

View file

@ -2,9 +2,9 @@
This module defines the URL patterns for hosting package related views.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf.urls import url
from django.urls import re_path
from .views import (
AddHostingOption,
@ -16,22 +16,36 @@ from .views import (
HostingOptionChoices,
)
urlpatterns = [
url(r'^create$', CreateHostingPackage.as_view(),
name='create_hosting_package'),
url(r'^allpackages/',
AllCustomerHostingPackageList.as_view(), name='all_hosting_packages'),
url(r'^(?P<user>[-\w0-9@.+_]+)/$',
CustomerHostingPackageList.as_view(), name='hosting_packages'),
url(r'^(?P<user>[-\w0-9@.+_]+)/create$',
re_path(r"^create$", CreateHostingPackage.as_view(), name="create_hosting_package"),
re_path(
r"^allpackages/",
AllCustomerHostingPackageList.as_view(),
name="all_hosting_packages",
),
re_path(
r"^(?P<user>[-\w0-9@.+_]+)/$",
CustomerHostingPackageList.as_view(),
name="hosting_packages",
),
re_path(
r"^(?P<user>[-\w0-9@.+_]+)/create$",
CreateCustomerHostingPackage.as_view(),
name='create_customer_hosting_package'),
url(r'^(?P<user>[-\w0-9@.+_]+)/(?P<pk>\d+)/$',
name="create_customer_hosting_package",
),
re_path(
r"^(?P<user>[-\w0-9@.+_]+)/(?P<pk>\d+)/$",
CustomerHostingPackageDetails.as_view(),
name='hosting_package_details'),
url(r'^(?P<pk>\d+)/option-choices$',
HostingOptionChoices.as_view(), name='hosting_option_choices'),
url(r'^(?P<package>\d+)/add-option/(?P<type>\w+)/(?P<optionid>\d+)$',
AddHostingOption.as_view(), name='add_hosting_option'),
name="hosting_package_details",
),
re_path(
r"^(?P<pk>\d+)/option-choices$",
HostingOptionChoices.as_view(),
name="hosting_option_choices",
),
re_path(
r"^(?P<package>\d+)/add-option/(?P<type>\w+)/(?P<optionid>\d+)$",
AddHostingOption.as_view(),
name="add_hosting_option",
),
]

View file

@ -2,28 +2,17 @@
This module defines views related to hosting packages.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from braces.views import LoginRequiredMixin, StaffuserRequiredMixin
from django.conf import settings
from django.http import Http404
from django.shortcuts import redirect, get_object_or_404
from django.utils.translation import ugettext as _
from django.views.generic import (
DetailView,
ListView,
)
from django.views.generic.edit import (
CreateView,
FormView,
)
from django.contrib import messages
from django.contrib.auth import get_user_model
from braces.views import (
LoginRequiredMixin,
StaffuserRequiredMixin,
)
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import gettext as _
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, FormView
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from .forms import (
@ -41,24 +30,24 @@ from .models import (
)
class CreateHostingPackage(
LoginRequiredMixin, StaffuserRequiredMixin, CreateView
):
class CreateHostingPackage(LoginRequiredMixin, StaffuserRequiredMixin, CreateView):
"""
Create a hosting package.
"""
model = CustomerHostingPackage
raise_exception = True
template_name_suffix = '_create'
template_name_suffix = "_create"
form_class = CreateHostingPackageForm
def form_valid(self, form):
hostingpackage = form.save()
messages.success(
self.request,
_('Started setup of new hosting package {name}.').format(
name=hostingpackage.name)
_("Started setup of new hosting package {name}.").format(
name=hostingpackage.name
),
)
return redirect(hostingpackage)
@ -68,6 +57,7 @@ class CreateCustomerHostingPackage(CreateHostingPackage):
Create a hosting package for a selected customer.
"""
form_class = CreateCustomerHostingPackageForm
def get_form_kwargs(self):
@ -76,13 +66,11 @@ class CreateCustomerHostingPackage(CreateHostingPackage):
return kwargs
def get_customer_object(self):
return get_object_or_404(
get_user_model(), username=self.kwargs['user'])
return get_object_or_404(get_user_model(), username=self.kwargs["user"])
def get_context_data(self, **kwargs):
context = super(
CreateCustomerHostingPackage, self).get_context_data(**kwargs)
context['customer'] = self.get_customer_object()
context = super(CreateCustomerHostingPackage, self).get_context_data(**kwargs)
context["customer"] = self.get_customer_object()
return context
def form_valid(self, form):
@ -91,8 +79,9 @@ class CreateCustomerHostingPackage(CreateHostingPackage):
hostingpackage.save()
messages.success(
self.request,
_('Started setup of new hosting package {name}.').format(
name=hostingpackage.name)
_("Started setup of new hosting package {name}.").format(
name=hostingpackage.name
),
)
return redirect(hostingpackage)
@ -102,30 +91,32 @@ class CustomerHostingPackageDetails(StaffOrSelfLoginRequiredMixin, DetailView):
This view is for showing details of a customer hosting package.
"""
model = CustomerHostingPackage
context_object_name = 'hostingpackage'
context_object_name = "hostingpackage"
customer = None
def get_customer_object(self):
if self.customer is None:
self.customer = get_object_or_404(
get_user_model(), username=self.kwargs['user'])
get_user_model(), username=self.kwargs["user"]
)
return self.customer
def get_context_data(self, **kwargs):
context = super(CustomerHostingPackageDetails, self).get_context_data(
**kwargs)
context.update({
'customer': self.get_customer_object(),
'uploadserver': settings.OSUSER_UPLOAD_SERVER,
'databases': context['hostingpackage'].databases,
'osuser': context['hostingpackage'].osuser,
'hostingoptions':
context['hostingpackage'].get_hostingoptions(),
'domains': context['hostingpackage'].domains.all(),
'mailboxes': context['hostingpackage'].mailboxes,
})
context['sshkeys'] = context['osuser'].sshpublickey_set.all()
context = super(CustomerHostingPackageDetails, self).get_context_data(**kwargs)
context.update(
{
"customer": self.get_customer_object(),
"uploadserver": settings.OSUSER_UPLOAD_SERVER,
"databases": context["hostingpackage"].databases,
"osuser": context["hostingpackage"].osuser,
"hostingoptions": context["hostingpackage"].get_hostingoptions(),
"domains": context["hostingpackage"].domains.all(),
"mailboxes": context["hostingpackage"].mailboxes,
}
)
context["sshkeys"] = context["osuser"].sshpublickey_set.all()
return context
@ -136,8 +127,9 @@ class AllCustomerHostingPackageList(
This view is used for showing a list of all hosting packages.
"""
model = CustomerHostingPackage
template_name_suffix = '_admin_list'
template_name_suffix = "_admin_list"
class CustomerHostingPackageList(StaffOrSelfLoginRequiredMixin, ListView):
@ -145,113 +137,128 @@ class CustomerHostingPackageList(StaffOrSelfLoginRequiredMixin, ListView):
This view is used for showing a list of a customer's hosting packages.
"""
model = CustomerHostingPackage
customer = None
def get_customer_object(self):
if self.customer is None:
self.customer = get_object_or_404(
get_user_model(), username=self.kwargs['user'])
get_user_model(), username=self.kwargs["user"]
)
return self.customer
def get_context_data(self, **kwargs):
context = super(CustomerHostingPackageList, self).get_context_data(
**kwargs)
context['customer'] = self.get_customer_object()
context = super(CustomerHostingPackageList, self).get_context_data(**kwargs)
context["customer"] = self.get_customer_object()
return context
def get_queryset(self):
return super(CustomerHostingPackageList, self).get_queryset().filter(
customer__username=self.kwargs['user'])
return (
super(CustomerHostingPackageList, self)
.get_queryset()
.filter(customer__username=self.kwargs["user"])
)
class HostingOptionChoices(
LoginRequiredMixin, StaffuserRequiredMixin, DetailView
):
class HostingOptionChoices(LoginRequiredMixin, StaffuserRequiredMixin, DetailView):
"""
This view displays choices of hosting options for a customer hosting
package.
"""
model = CustomerHostingPackage
context_object_name = 'hostingpackage'
template_name_suffix = '_option_choices'
context_object_name = "hostingpackage"
template_name_suffix = "_option_choices"
def get_context_data(self, **kwargs):
context = super(HostingOptionChoices, self).get_context_data(
**kwargs)
context.update({
'customer': self.get_object().customer,
'hosting_options': (
(_('Disk space'),
[(option, 'diskspace') for option in
DiskSpaceOption.objects.all()]),
(_('Mailboxes'),
[(option, 'mailboxes') for option in
MailboxOption.objects.all()]),
(_('Databases'),
[(option, 'databases') for option in
UserDatabaseOption.objects.all()]),
context = super(HostingOptionChoices, self).get_context_data(**kwargs)
context.update(
{
"customer": self.get_object().customer,
"hosting_options": (
(
_("Disk space"),
[
(option, "diskspace")
for option in DiskSpaceOption.objects.all()
],
),
})
(
_("Mailboxes"),
[
(option, "mailboxes")
for option in MailboxOption.objects.all()
],
),
(
_("Databases"),
[
(option, "databases")
for option in UserDatabaseOption.objects.all()
],
),
),
}
)
return context
class AddHostingOption(
LoginRequiredMixin, StaffuserRequiredMixin, FormView
):
template_name = 'hostingpackages/add_hosting_option.html'
class AddHostingOption(LoginRequiredMixin, StaffuserRequiredMixin, FormView):
template_name = "hostingpackages/add_hosting_option.html"
def get_form_class(self):
optiontype = self.kwargs['type']
if optiontype == 'diskspace':
optiontype = self.kwargs["type"]
if optiontype == "diskspace":
return AddDiskspaceOptionForm
elif optiontype == 'mailboxes':
elif optiontype == "mailboxes":
return AddMailboxOptionForm
elif optiontype == 'databases':
elif optiontype == "databases":
return AddUserDatabaseOptionForm
raise Http404()
def get_hosting_package(self):
return get_object_or_404(
CustomerHostingPackage, pk=int(self.kwargs['package']))
return get_object_or_404(CustomerHostingPackage, pk=int(self.kwargs["package"]))
def get_option_template(self):
optiontype = self.kwargs['type']
optionid = int(self.kwargs['optionid'])
if optiontype == 'diskspace':
optiontype = self.kwargs["type"]
optionid = int(self.kwargs["optionid"])
if optiontype == "diskspace":
return get_object_or_404(DiskSpaceOption, pk=optionid)
elif optiontype == 'mailboxes':
elif optiontype == "mailboxes":
return get_object_or_404(MailboxOption, pk=optionid)
elif optiontype == 'databases':
elif optiontype == "databases":
return get_object_or_404(UserDatabaseOption, pk=optionid)
raise Http404()
def get_form_kwargs(self):
kwargs = super(AddHostingOption, self).get_form_kwargs()
kwargs['hostingpackage'] = self.get_hosting_package()
kwargs['option_template'] = self.get_option_template()
kwargs["hostingpackage"] = self.get_hosting_package()
kwargs["option_template"] = self.get_option_template()
return kwargs
def get_initial(self):
initial = super(AddHostingOption, self).get_initial()
template = self.get_option_template()
if type(template) == DiskSpaceOption:
initial.update({
'diskspace': template.diskspace,
'diskspace_unit': template.diskspace_unit,
})
initial.update(
{
"diskspace": template.diskspace,
"diskspace_unit": template.diskspace_unit,
}
)
elif type(template) == MailboxOption:
initial['number'] = template.number
initial["number"] = template.number
elif type(template) == UserDatabaseOption:
initial['number'] = template.number
initial["number"] = template.number
else:
raise Http404()
return initial
def get_context_data(self, **kwargs):
context = super(AddHostingOption, self).get_context_data(**kwargs)
context['option_template'] = self.get_option_template()
context["option_template"] = self.get_option_template()
return context
def form_valid(self, form):
@ -259,8 +266,8 @@ class AddHostingOption(
hosting_package = self.get_hosting_package()
messages.success(
self.request,
_("Successfully added option {option} to hosting package "
"{package}.").format(
option=option, package=hosting_package.name)
_(
"Successfully added option {option} to hosting package " "{package}."
).format(option=option, package=hosting_package.name),
)
return redirect(hosting_package)

View file

@ -2,4 +2,3 @@
This app takes care of mailboxes and mail addresses.
"""
default_app_config = 'managemails.apps.ManageMailsAppConfig'

View file

@ -1,15 +1,10 @@
from django.utils.html import format_html
from django.contrib import admin
from django import forms
from django.contrib import admin
from django.forms.utils import flatatt
from django.utils.translation import ugettext as _
from django.utils.html import format_html
from django.utils.translation import gettext as _
from .models import (
MailAddress,
MailAddressForward,
MailAddressMailbox,
Mailbox,
)
from .models import MailAddress, MailAddressForward, MailAddressMailbox, Mailbox
PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
@ -17,8 +12,7 @@ PASSWORD_MISMATCH_ERROR = _("Passwords don't match")
class ReadOnlyPasswordHashWidget(forms.Widget):
def render(self, name, value, attrs):
final_attrs = self.build_attrs(attrs)
summary = format_html("<strong>{0}</strong>: {1} ",
_('Hash'), value)
summary = format_html("<strong>{0}</strong>: {1} ", _("Hash"), value)
return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary)
@ -41,22 +35,21 @@ class MailboxCreationForm(forms.ModelForm):
A form for creating mailboxes.
"""
password1 = forms.CharField(label=_('Password'),
widget=forms.PasswordInput)
password2 = forms.CharField(label=_('Password (again)'),
widget=forms.PasswordInput)
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password (again)"), widget=forms.PasswordInput)
class Meta:
model = Mailbox
fields = ('osuser',)
fields = ("osuser",)
def clean_password2(self):
"""
Check that the two password entries match.
"""
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(PASSWORD_MISMATCH_ERROR)
return password2
@ -67,9 +60,8 @@ class MailboxCreationForm(forms.ModelForm):
"""
mailbox = super(MailboxCreationForm, self).save(commit=False)
mailbox.username = Mailbox.objects.get_next_mailbox_name(
mailbox.osuser)
mailbox.set_password(self.cleaned_data['password1'])
mailbox.username = Mailbox.objects.get_next_mailbox_name(mailbox.osuser)
mailbox.set_password(self.cleaned_data["password1"])
if commit:
mailbox.save()
return mailbox
@ -80,14 +72,15 @@ class MailboxChangeForm(forms.ModelForm):
A form for updating mailboxes.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = Mailbox
fields = ('username', 'password', 'osuser', 'active')
fields = ("username", "password", "osuser", "active")
def clean_password(self):
return self.initial['password']
return self.initial["password"]
class ActivationChangeMixin(object):
@ -97,8 +90,8 @@ class ActivationChangeMixin(object):
def deactivate(self, request, queryset):
queryset.update(active=False)
activate.short_description = _('Activate')
deactivate.short_description = _('Deactivate')
activate.short_description = _("Activate")
deactivate.short_description = _("Deactivate")
class MailboxAdmin(ActivationChangeMixin, admin.ModelAdmin):
@ -106,24 +99,20 @@ class MailboxAdmin(ActivationChangeMixin, admin.ModelAdmin):
Custom admin page for mailboxes.
"""
form = MailboxChangeForm
add_form = MailboxCreationForm
actions = ['activate', 'deactivate']
actions = ["activate", "deactivate"]
list_display = ('username', 'osuser', 'active')
list_filter = ('active', 'osuser')
fieldsets = (
(None, {
'fields': ('osuser', 'username', 'password', 'active')}),
)
list_display = ("username", "osuser", "active")
list_filter = ("active", "osuser")
fieldsets = ((None, {"fields": ("osuser", "username", "password", "active")}),)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('osuser', 'password1', 'password2')}),
(None, {"classes": ("wide",), "fields": ("osuser", "password1", "password2")}),
)
search_fields = ('username',)
ordering = ('username',)
search_fields = ("username",)
ordering = ("username",)
filter_horizontal = ()
def get_fieldsets(self, request, obj=None):
@ -138,10 +127,12 @@ class MailboxAdmin(ActivationChangeMixin, admin.ModelAdmin):
"""
defaults = {}
if obj is None:
defaults.update({
'form': self.add_form,
'fields': admin.options.flatten_fieldsets(self.add_fieldsets),
})
defaults.update(
{
"form": self.add_form,
"fields": admin.options.flatten_fieldsets(self.add_fieldsets),
}
)
defaults.update(kwargs)
return super(MailboxAdmin, self).get_form(request, obj, **defaults)
@ -155,10 +146,10 @@ class MailAddressForwardInline(admin.TabularInline):
class MailAddressAdmin(ActivationChangeMixin, admin.ModelAdmin):
actions = ['activate', 'deactivate']
actions = ["activate", "deactivate"]
list_display = ('__str__', 'mailaddressmailbox', 'active')
list_filter = ('active', 'domain')
list_display = ("__str__", "mailaddressmailbox", "active")
list_filter = ("active", "domain")
inlines = [MailAddressMailboxInline, MailAddressForwardInline]

View file

@ -3,9 +3,8 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`managemails` app.
"""
from __future__ import unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class ManageMailsAppConfig(AppConfig):
@ -13,5 +12,6 @@ class ManageMailsAppConfig(AppConfig):
AppConfig for the :py:mod:`managemails` app.
"""
name = 'managemails'
verbose_name = _('Mailboxes and Mail Addresses')
name = "managemails"
verbose_name = _("Mailboxes and Mail Addresses")

View file

@ -2,32 +2,24 @@
This module defines form classes for mailbox and mail address editing.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from crispy_forms.bootstrap import AppendedText
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Div, Layout, Submit
from django import forms
from django.core.validators import validate_email
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.bootstrap import AppendedText
from crispy_forms.layout import (
Div,
Layout,
Submit,
)
from .models import (
MailAddress,
Mailbox,
)
from gvawebcore.forms import PasswordModelFormMixin
from django.utils.translation import gettext_lazy as _
from model_utils import Choices
from gvawebcore.forms import PasswordModelFormMixin
from .models import MailAddress, Mailbox
MAILBOX_OR_FORWARDS = Choices(
(0, 'mailbox', _('Mailbox')),
(1, 'forwards', _('Forwards')),
(0, "mailbox", _("Mailbox")),
(1, "forwards", _("Forwards")),
)
@ -36,17 +28,19 @@ class CreateMailboxForm(PasswordModelFormMixin, forms.ModelForm):
This form is used to create new Mailbox instances.
"""
class Meta:
model = Mailbox
fields = []
def __init__(self, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
self.hosting_package = kwargs.pop("hostingpackage")
super(CreateMailboxForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_action = reverse(
'create_mailbox', kwargs={'package': self.hosting_package.id})
self.helper.add_input(Submit('submit', _('Create mailbox')))
"create_mailbox", kwargs={"package": self.hosting_package.id}
)
self.helper.add_input(Submit("submit", _("Create mailbox")))
def save(self, commit=True):
"""
@ -60,7 +54,7 @@ class CreateMailboxForm(PasswordModelFormMixin, forms.ModelForm):
osuser = self.hosting_package.osuser
self.instance.osuser = osuser
self.instance.username = Mailbox.objects.get_next_mailbox_name(osuser)
self.instance.set_password(self.cleaned_data['password1'])
self.instance.set_password(self.cleaned_data["password1"])
return super(CreateMailboxForm, self).save(commit=commit)
@ -69,20 +63,23 @@ class ChangeMailboxPasswordForm(PasswordModelFormMixin, forms.ModelForm):
This form is used to set a new password for an existing mailbox.
"""
class Meta:
model = Mailbox
fields = []
def __init__(self, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
self.hosting_package = kwargs.pop("hostingpackage")
super(ChangeMailboxPasswordForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_action = reverse(
'change_mailbox_password', kwargs={
'package': self.hosting_package.id,
'slug': self.instance.username,
})
self.helper.add_input(Submit('submit', _('Set password')))
"change_mailbox_password",
kwargs={
"package": self.hosting_package.id,
"slug": self.instance.username,
},
)
self.helper.add_input(Submit("submit", _("Set password")))
def save(self, commit=True):
"""
@ -93,13 +90,13 @@ class ChangeMailboxPasswordForm(PasswordModelFormMixin, forms.ModelForm):
:rtype: :py:class:`managemails.models.Mailbox`
"""
self.instance.set_password(self.cleaned_data['password1'])
self.instance.set_password(self.cleaned_data["password1"])
return super(ChangeMailboxPasswordForm, self).save(commit=commit)
def multiple_email_validator(value):
if value:
for email in [part.strip() for part in value.split(',')]:
for email in [part.strip() for part in value.split(",")]:
validate_email(email)
return value
@ -109,25 +106,26 @@ class MailAddressFieldMixin(forms.Form):
This mixin defines form fields common to mail address forms.
"""
mailbox_or_forwards = forms.TypedChoiceField(
label=_('Mailbox or Forwards'),
label=_("Mailbox or Forwards"),
choices=MAILBOX_OR_FORWARDS,
widget=forms.RadioSelect,
coerce=int,
)
mailbox = forms.ModelChoiceField(
Mailbox.objects,
label=_('Mailbox'),
label=_("Mailbox"),
required=False,
)
# TODO: refactor as separate field class returning a list
forwards = forms.CharField(
label=_('Forwards'),
label=_("Forwards"),
required=False,
error_messages={
'invalid': _(
'Please enter one or more email addresses separated by '
'commas.'),
"invalid": _(
"Please enter one or more email addresses separated by " "commas."
),
},
validators=[multiple_email_validator],
)
@ -138,68 +136,71 @@ class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
This form is used to add a new mail address.
"""
class Meta:
model = MailAddress
fields = ['localpart']
fields = ["localpart"]
def __init__(self, *args, **kwargs):
self.maildomain = kwargs.pop('maildomain')
self.hosting_package = kwargs.pop('hostingpackage')
self.maildomain = kwargs.pop("maildomain")
self.hosting_package = kwargs.pop("hostingpackage")
super(AddMailAddressForm, self).__init__(*args, **kwargs)
self.fields['mailbox'].queryset = Mailbox.objects.unused(
self.fields["mailbox"].queryset = Mailbox.objects.unused(
osuser=self.hosting_package.osuser,
)
self.helper = FormHelper()
self.helper.form_action = reverse(
'add_mailaddress', kwargs={
'package': self.hosting_package.id,
'domain': self.maildomain.domain,
})
"add_mailaddress",
kwargs={
"package": self.hosting_package.id,
"domain": self.maildomain.domain,
},
)
self.helper.layout = Layout(
Div(
Div(
AppendedText('localpart', '@' + self.maildomain.domain),
css_class='col-lg-4 col-md-4 col-xs-12',
AppendedText("localpart", "@" + self.maildomain.domain),
css_class="col-lg-4 col-md-4 col-xs-12",
),
Div(
'mailbox_or_forwards',
css_class='col-lg-2 col-md-2 col-xs-12',
"mailbox_or_forwards",
css_class="col-lg-2 col-md-2 col-xs-12",
),
Div(
'mailbox',
'forwards',
css_class='col-lg-6 col-md-6 col-xs-12',
"mailbox",
"forwards",
css_class="col-lg-6 col-md-6 col-xs-12",
),
css_class='row',
css_class="row",
),
Submit('submit', _('Add mail address')),
Submit("submit", _("Add mail address")),
)
def clean_localpart(self):
localpart = self.cleaned_data['localpart']
localpart = self.cleaned_data["localpart"]
if MailAddress.objects.filter(
domain=self.maildomain,
localpart=localpart,
).exists():
raise forms.ValidationError(
_('This mail address is already in use.'))
validate_email('{0}@{1}'.format(localpart, self.maildomain.domain))
raise forms.ValidationError(_("This mail address is already in use."))
validate_email("{0}@{1}".format(localpart, self.maildomain.domain))
return localpart
def clean(self):
super(AddMailAddressForm, self).clean()
data = self.cleaned_data
if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox:
if not data['mailbox']:
self.add_error('mailbox', _('No mailbox selected'))
elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards:
if 'forwards' not in data or not data['forwards']:
self.add_error('forwards', _('No forward addresses selected'))
if data["mailbox_or_forwards"] == MAILBOX_OR_FORWARDS.mailbox:
if "mailbox" not in data or not data["mailbox"]:
self.add_error("mailbox", _("No mailbox selected"))
elif data["mailbox_or_forwards"] == MAILBOX_OR_FORWARDS.forwards:
if "forwards" not in data or not data["forwards"]:
self.add_error("forwards", _("No forward addresses selected"))
else: # pragma: no cover
# should not happen because of the field's validation
raise forms.ValidationError(
_('Illegal choice for target of the mail address'))
_("Illegal choice for target of the mail address")
)
def save(self, commit=True):
"""
@ -212,11 +213,11 @@ class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
"""
self.instance.domain = self.maildomain
data = self.cleaned_data
target_choice = data['mailbox_or_forwards']
target_choice = data["mailbox_or_forwards"]
if target_choice == MAILBOX_OR_FORWARDS.mailbox:
mabox = self.instance.set_mailbox(data['mailbox'], commit=False)
mabox = self.instance.set_mailbox(data["mailbox"], commit=False)
elif target_choice == MAILBOX_OR_FORWARDS.forwards:
targets = [part.strip() for part in data['forwards'].split(',')]
targets = [part.strip() for part in data["forwards"].split(",")]
fwds = self.instance.set_forward_addresses(targets, commit=False)
mailaddress = super(AddMailAddressForm, self).save(commit)
if commit:
@ -235,53 +236,57 @@ class EditMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
This form is used to edit the targets for a mail address.
"""
class Meta:
model = MailAddress
fields = []
def __init__(self, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
self.maildomain = kwargs.pop('maildomain')
self.hosting_package = kwargs.pop("hostingpackage")
self.maildomain = kwargs.pop("maildomain")
super(EditMailAddressForm, self).__init__(*args, **kwargs)
self.fields['mailbox'].queryset = Mailbox.objects.unused_or_own(
self.fields["mailbox"].queryset = Mailbox.objects.unused_or_own(
self.instance, self.hosting_package.osuser
)
self.helper = FormHelper()
self.helper.form_action = reverse(
'edit_mailaddress', kwargs={
'package': self.hosting_package.id,
'domain': self.maildomain.domain,
'pk': self.instance.id,
})
"edit_mailaddress",
kwargs={
"package": self.hosting_package.id,
"domain": self.maildomain.domain,
"pk": self.instance.id,
},
)
self.helper.layout = Layout(
Div(
Div(
'mailbox_or_forwards',
css_class='col-log-2 col-md-2 col-xs-12',
"mailbox_or_forwards",
css_class="col-log-2 col-md-2 col-xs-12",
),
Div(
'mailbox',
'forwards',
css_class='col-lg-10 col-md-10 col-xs-12',
"mailbox",
"forwards",
css_class="col-lg-10 col-md-10 col-xs-12",
),
css_class='row',
css_class="row",
),
Submit('submit', _('Change mail address targets')),
Submit("submit", _("Change mail address targets")),
)
def clean(self):
data = self.cleaned_data
if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox:
if not data['mailbox']:
self.add_error('mailbox', _('No mailbox selected'))
elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards:
if 'forwards' not in data or not data['forwards']:
self.add_error('forwards', _('No forward addresses selected'))
if data["mailbox_or_forwards"] == MAILBOX_OR_FORWARDS.mailbox:
if not data["mailbox"]:
self.add_error("mailbox", _("No mailbox selected"))
elif data["mailbox_or_forwards"] == MAILBOX_OR_FORWARDS.forwards:
if "forwards" not in data or not data["forwards"]:
self.add_error("forwards", _("No forward addresses selected"))
else: # pragma: no cover
# should not happen because of the field's validation
raise forms.ValidationError(
_('Illegal choice for target of the mail address'))
_("Illegal choice for target of the mail address")
)
def save(self, commit=True):
"""
@ -290,9 +295,9 @@ class EditMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
:param boolean commit:
"""
data = self.cleaned_data
if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox:
self.instance.set_mailbox(data['mailbox'], commit)
elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards:
targets = [part.strip() for part in data['forwards'].split(',')]
if data["mailbox_or_forwards"] == MAILBOX_OR_FORWARDS.mailbox:
self.instance.set_mailbox(data["mailbox"], commit)
elif data["mailbox_or_forwards"] == MAILBOX_OR_FORWARDS.forwards:
targets = [part.strip() for part in data["forwards"].split(",")]
self.instance.set_forward_addresses(targets, commit)
return super(EditMailAddressForm, self).save(commit)

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone
import model_utils.fields
from django.db import migrations, models
@ -8,122 +6,185 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('domains', '0001_initial'),
('osusers', '0001_initial'),
("domains", "0001_initial"),
("osusers", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='MailAddress',
name="MailAddress",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('active', models.BooleanField(default=True)),
('localpart', models.CharField(max_length=128)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
("active", models.BooleanField(default=True)),
("localpart", models.CharField(max_length=128)),
],
options={
'verbose_name': 'Mail address',
'verbose_name_plural': 'Mail addresses',
"verbose_name": "Mail address",
"verbose_name_plural": "Mail addresses",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='MailAddressForward',
name="MailAddressForward",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('target', models.EmailField(max_length=254)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
("target", models.EmailField(max_length=254)),
],
options={
},
options={},
bases=(models.Model,),
),
migrations.CreateModel(
name='MailAddressMailbox',
name="MailAddressMailbox",
fields=[
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('mailaddress', models.OneToOneField(
primary_key=True, serialize=False,
to='managemails.MailAddress', on_delete=models.CASCADE)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"mailaddress",
models.OneToOneField(
primary_key=True,
serialize=False,
to="managemails.MailAddress",
on_delete=models.CASCADE,
),
),
],
options={
},
options={},
bases=(models.Model,),
),
migrations.CreateModel(
name='Mailbox',
name="Mailbox",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('active', models.BooleanField(default=True)),
('username', models.CharField(unique=True, max_length=128)),
('password', models.CharField(max_length=255)),
('osuser', models.ForeignKey(
to='osusers.User', on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
("active", models.BooleanField(default=True)),
("username", models.CharField(unique=True, max_length=128)),
("password", models.CharField(max_length=255)),
(
"osuser",
models.ForeignKey(to="osusers.User", on_delete=models.CASCADE),
),
],
options={
'verbose_name': 'Mailbox',
'verbose_name_plural': 'Mailboxes',
"verbose_name": "Mailbox",
"verbose_name_plural": "Mailboxes",
},
bases=(models.Model,),
),
migrations.AddField(
model_name='mailaddressmailbox',
name='mailbox',
field=models.ForeignKey(
to='managemails.Mailbox', on_delete=models.CASCADE),
model_name="mailaddressmailbox",
name="mailbox",
field=models.ForeignKey(to="managemails.Mailbox", on_delete=models.CASCADE),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='mailaddressmailbox',
unique_together={('mailaddress', 'mailbox')},
name="mailaddressmailbox",
unique_together={("mailaddress", "mailbox")},
),
migrations.AddField(
model_name='mailaddressforward',
name='mailaddress',
model_name="mailaddressforward",
name="mailaddress",
field=models.ForeignKey(
to='managemails.MailAddress', on_delete=models.CASCADE),
to="managemails.MailAddress", on_delete=models.CASCADE
),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='mailaddressforward',
unique_together={('mailaddress', 'target')},
name="mailaddressforward",
unique_together={("mailaddress", "target")},
),
migrations.AddField(
model_name='mailaddress',
name='domain',
field=models.ForeignKey(
to='domains.MailDomain', on_delete=models.CASCADE),
model_name="mailaddress",
name="domain",
field=models.ForeignKey(to="domains.MailDomain", on_delete=models.CASCADE),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='mailaddress',
unique_together={('localpart', 'domain')},
name="mailaddress",
unique_together={("localpart", "domain")},
),
]

View file

@ -1,22 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('managemails', '0001_initial'),
("managemails", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
name='mailaddress',
options={'ordering': ['domain', 'localpart'], 'verbose_name': 'Mail address', 'verbose_name_plural': 'Mail addresses'},
name="mailaddress",
options={
"ordering": ["domain", "localpart"],
"verbose_name": "Mail address",
"verbose_name_plural": "Mail addresses",
},
),
migrations.AlterModelOptions(
name='mailbox',
options={'ordering': ['osuser', 'username'], 'verbose_name': 'Mailbox', 'verbose_name_plural': 'Mailboxes'},
name="mailbox",
options={
"ordering": ["osuser", "username"],
"verbose_name": "Mailbox",
"verbose_name_plural": "Mailboxes",
},
),
]

View file

@ -1,29 +1,33 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('managemails', '0002_auto_20150117_1238'),
("managemails", "0002_auto_20150117_1238"),
]
operations = [
migrations.AlterField(
model_name='mailaddressmailbox',
name='mailaddress',
model_name="mailaddressmailbox",
name="mailaddress",
field=models.OneToOneField(
primary_key=True, serialize=False, to='managemails.MailAddress',
verbose_name='mailaddress', on_delete=models.CASCADE),
primary_key=True,
serialize=False,
to="managemails.MailAddress",
verbose_name="mailaddress",
on_delete=models.CASCADE,
),
preserve_default=True,
),
migrations.AlterField(
model_name='mailaddressmailbox',
name='mailbox',
model_name="mailaddressmailbox",
name="mailbox",
field=models.ForeignKey(
verbose_name='mailbox', to='managemails.Mailbox',
on_delete=models.CASCADE),
verbose_name="mailbox",
to="managemails.Mailbox",
on_delete=models.CASCADE,
),
preserve_default=True,
),
]

View file

@ -1,27 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('managemails', '0003_auto_20150124_2029'),
("managemails", "0003_auto_20150124_2029"),
]
operations = [
migrations.AlterField(
model_name='mailaddress',
name='domain',
model_name="mailaddress",
name="domain",
field=models.ForeignKey(
verbose_name='domain', to='domains.MailDomain',
on_delete=models.CASCADE),
verbose_name="domain", to="domains.MailDomain", on_delete=models.CASCADE
),
preserve_default=True,
),
migrations.AlterField(
model_name='mailaddress',
name='localpart',
field=models.CharField(max_length=128, verbose_name='local part'),
model_name="mailaddress",
name="localpart",
field=models.CharField(max_length=128, verbose_name="local part"),
preserve_default=True,
),
]

View file

@ -2,13 +2,10 @@
This module defines the database models for mail handling.
"""
from __future__ import unicode_literals
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from model_utils.models import TimeStampedModel
from passlib.hash import sha512_crypt
from passlib.handlers.sha2_crypt import sha512_crypt
from domains.models import MailDomain
from fileservertasks.tasks import create_file_mailbox, delete_file_mailbox
@ -100,7 +97,6 @@ class MailboxManager(models.Manager):
return mailbox
@python_2_unicode_compatible
class Mailbox(ActivateAbleMixin, TimeStampedModel):
"""
This is the model class for a mailbox.
@ -151,7 +147,6 @@ class Mailbox(ActivateAbleMixin, TimeStampedModel):
return self.username
@python_2_unicode_compatible
class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
"""
This is the model class for a mail address.
@ -241,7 +236,6 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
return retval
@python_2_unicode_compatible
class MailAddressMailbox(TimeStampedModel, models.Model):
"""
This is the model class to assign a mail address to a mailbox.

View file

@ -1,27 +1,25 @@
from unittest.mock import Mock
from django import forms
from django.contrib.admin import AdminSite
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import ugettext as _
from django.contrib.admin import AdminSite
from django.contrib.auth import get_user_model
from unittest.mock import Mock
from osusers.models import User
from django.utils.translation import gettext as _
from managemails.admin import (
PASSWORD_MISMATCH_ERROR,
ActivationChangeMixin,
MailboxAdmin,
MailboxChangeForm,
MailboxCreationForm,
PASSWORD_MISMATCH_ERROR,
ReadOnlyPasswordHashField,
ReadOnlyPasswordHashWidget,
)
from managemails.models import Mailbox
from osusers.models import User
Customer = get_user_model()

View file

@ -2,21 +2,25 @@
This module provides tests for :py:mod:`managemails.forms`.
"""
from unittest.mock import MagicMock, Mock, patch, ANY
from unittest import skip
from unittest.mock import ANY, MagicMock, Mock, patch
from django.forms import ValidationError
from django.test import TestCase
from django.urls import reverse
import osusers.models
from domains.models import MailDomain
from managemails.forms import (
MAILBOX_OR_FORWARDS,
AddMailAddressForm,
ChangeMailboxPasswordForm,
CreateMailboxForm,
EditMailAddressForm,
MAILBOX_OR_FORWARDS,
MailAddressFieldMixin,
multiple_email_validator,
)
from managemails.models import MailAddress, Mailbox
class CreateMailboxFormTest(TestCase):
@ -131,20 +135,10 @@ class MailAddressFieldMixinTest(TestCase):
class AddMailAddressFormTest(TestCase):
def setUp(self):
self.patcher1 = patch("managemails.forms.Mailbox.objects")
self.patcher2 = patch("managemails.forms.MailAddress.objects")
self.mailbox_objects = self.patcher1.start()
self.mailaddress_objects = self.patcher2.start()
def tearDown(self):
self.patcher2.stop()
self.patcher1.stop()
def test_constructor_needs_hostingpackage(self):
instance = MagicMock()
instance = MailAddress()
with self.assertRaises(KeyError):
AddMailAddressForm(instance=instance, maildomain=MagicMock())
AddMailAddressForm(instance=instance, maildomain=None)
def test_constructor_needs_maildomain(self):
instance = MagicMock()
@ -152,21 +146,20 @@ class AddMailAddressFormTest(TestCase):
AddMailAddressForm(instance=instance, hostingpackage=MagicMock())
def test_constructor(self):
instance = MagicMock()
osuser = Mock(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
instance = MailAddress()
os_user = osusers.models.User(username="testuser")
hosting_package = MagicMock(id=42, osuser=os_user)
mail_domain = MailDomain(domain="example.org")
form = AddMailAddressForm(
instance=instance, hostingpackage=hostingpackage, maildomain=maildomain
instance=instance, hostingpackage=hosting_package, maildomain=mail_domain
)
self.mailbox_objects.unused.assert_called_with(osuser=osuser)
self.assertIn("mailbox_or_forwards", form.fields)
self.assertIn("mailbox", form.fields)
self.assertIn("forwards", form.fields)
self.assertTrue(hasattr(form, "hosting_package"))
self.assertEqual(form.hosting_package, hostingpackage)
self.assertEqual(form.hosting_package, hosting_package)
self.assertTrue(hasattr(form, "maildomain"))
self.assertEqual(form.maildomain, maildomain)
self.assertEqual(form.maildomain, mail_domain)
self.assertTrue(hasattr(form, "helper"))
self.assertEqual(
form.helper.form_action,
@ -176,52 +169,50 @@ class AddMailAddressFormTest(TestCase):
self.assertEqual(form.helper.layout[1].name, "submit")
def test_clean_localpart_valid(self):
instance = MagicMock()
osuser = Mock(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
mail_domain = MailDomain.objects.create(domain="example.org")
instance = MailAddress()
os_user = osusers.models.User(username="testuser")
hosting_package = MagicMock(id=42, osuser=os_user)
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
maildomain=maildomain,
hostingpackage=hosting_package,
maildomain=mail_domain,
data={
"localpart": "test",
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
"forwards": "test2@example.org",
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = False
self.assertTrue(form.is_valid())
self.assertEqual("test", form.clean_localpart())
def test_clean_localpart_duplicate(self):
instance = MagicMock()
osuser = Mock(username="testuser")
mail_domain = MailDomain.objects.create(domain="example.org")
MailAddress.objects.create(localpart="test", domain=mail_domain)
instance = MailAddress()
osuser = osusers.models.User(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
maildomain=maildomain,
maildomain=mail_domain,
data={
"localpart": "test",
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
"forwards": "test2@example.org",
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = True
self.assertFalse(form.is_valid())
self.assertIn("localpart", form.errors)
def test_clean_no_mailbox_choice(self):
instance = MagicMock()
osuser = Mock(username="testuser")
instance = MailAddress()
osuser = osusers.models.User(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
maildomain = MailDomain(domain="example.org")
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
@ -231,68 +222,52 @@ class AddMailAddressFormTest(TestCase):
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = False
self.assertFalse(form.is_valid())
self.assertIn("mailbox", form.errors)
def test_clean_no_forward_address_choice(self):
instance = MagicMock()
osuser = Mock(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
instance = MailAddress()
os_user = osusers.models.User(username="testuser")
hosting_package = MagicMock(id=42, osuser=os_user)
mail_domain = MailDomain(domain="example.org")
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
maildomain=maildomain,
hostingpackage=hosting_package,
maildomain=mail_domain,
data={
"localpart": "test",
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = False
self.assertFalse(form.is_valid())
self.assertIn("forwards", form.errors)
def test_save_with_forwards_no_commit(self):
instance = MagicMock()
osuser = Mock(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
mail_domain = MailDomain.objects.create(domain="example.org")
instance = MailAddress()
os_user = osusers.models.User(username="testuser")
hosting_package = MagicMock(id=42, osuser=os_user)
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
maildomain=maildomain,
hostingpackage=hosting_package,
maildomain=mail_domain,
data={
"localpart": "test",
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
"forwards": "test2@example.org,test3@example.org",
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = False
self.assertTrue(form.is_valid())
address1 = MagicMock(mailaddress="test2@example.org")
address2 = MagicMock(mailaddress="test3@example.org")
instance.set_forward_addresses.return_value = [address1, address2]
form.save(commit=False)
self.assertEqual(maildomain, instance.domain)
instance.set_forward_addresses.assert_called_with(
["test2@example.org", "test3@example.org"], commit=False
)
address1.save.assert_not_called()
address2.save.assert_not_called()
instance.save.assert_not_called()
self.assertEqual(mail_domain, instance.domain)
def test_save_with_forwards_commit(self):
instance = MagicMock()
osuser = Mock(username="testuser")
maildomain = MailDomain.objects.create(domain="example.org")
instance = MailAddress()
osuser = osusers.models.User(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
@ -303,122 +278,95 @@ class AddMailAddressFormTest(TestCase):
"forwards": "test2@example.org,test3@example.org",
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = False
self.assertTrue(form.is_valid())
address1 = MagicMock(mailaddress="test2@example.org")
address2 = MagicMock(mailaddress="test3@example.org")
instance.set_forward_addresses.return_value = [address1, address2]
form.save(commit=True)
self.assertEqual(maildomain, instance.domain)
instance.set_forward_addresses.assert_called_with(
["test2@example.org", "test3@example.org"], commit=False
forwards = list(
instance.mailaddressforward_set.values_list("target", flat=True).order_by(
"target"
)
address1.save.assert_called_with()
address2.save.assert_called_with()
instance.save.assert_called_with()
)
self.assertEqual(len(forwards), 2)
self.assertEqual(forwards, ["test2@example.org", "test3@example.org"])
@skip("does not work because it will create a real mailbox")
def test_save_with_mailbox_no_commit(self):
instance = MagicMock()
osuser = Mock(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
instance = MailAddress()
os_user = osusers.models.User(username="testuser")
hosting_package = MagicMock(id=42, osuser=os_user)
mail_domain = MailDomain.objects.create(domain="example.org")
mail_box = Mailbox.objects.create(osuser=os_user, username="mailbox23")
mail_box.set_password("test")
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
maildomain=maildomain,
hostingpackage=hosting_package,
maildomain=mail_domain,
data={
"localpart": "test",
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
"mailbox": "mailbox23",
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = False
self.assertTrue(form.is_valid())
mailbox = MagicMock(osuser=osuser, username="testuserp01")
instance.set_mailbox.return_value = mailbox
form.save(commit=False)
self.assertEqual(maildomain, instance.domain)
instance.set_mailbox.assert_called_with(ANY, commit=False)
mailbox.save.assert_not_called()
instance.save.assert_not_called()
self.assertEqual(mail_domain, instance.domain)
@skip("does not work because it will create a real mailbox")
def test_save_with_mailbox_commit(self):
instance = MagicMock()
osuser = Mock(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
maildomain=maildomain,
data={
"localpart": "test",
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
"mailbox": "mailbox23",
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = False
self.assertTrue(form.is_valid())
mailbox = MagicMock(osuser=osuser, username="testuserp01")
instance.set_mailbox.return_value = mailbox
form.save(commit=True)
self.assertEqual(maildomain, instance.domain)
instance.set_mailbox.assert_called_with(ANY, commit=False)
instance.set_mailbox.return_value.save.assert_called_with()
mailbox.save.assert_called_with()
instance.save.assert_called_with()
mail_domain = MailDomain.objects.create(domain="example.org")
instance = MailAddress()
os_user = osusers.models.User(username="testuser")
mail_box = Mailbox.objects.create(osuser=os_user, username="mailbox23")
mail_box.set_password("test")
hosting_package = MagicMock(id=42, osuser=os_user)
def test_save_with_other_choice(self):
instance = MagicMock()
osuser = Mock(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
form = AddMailAddressForm(
instance=instance,
hostingpackage=hostingpackage,
maildomain=maildomain,
hostingpackage=hosting_package,
maildomain=mail_domain,
data={
"localpart": "test",
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
"mailbox": "mailbox23",
},
)
self.assertTrue(form.is_valid())
form.save(commit=True)
self.assertEqual(mail_domain, instance.domain)
@skip("does not work because it will create a real mailbox")
def test_save_with_other_choice(self):
mail_domain = MailDomain.objects.create(domain="example.org")
instance = MailAddress()
os_user = osusers.models.User(username="testuser")
hosting_package = MagicMock(id=42, osuser=os_user)
mail_box = Mailbox.objects.create(osuser=os_user, username="mailbox23")
mail_box.set_password("test")
form = AddMailAddressForm(
instance=instance,
hostingpackage=hosting_package,
maildomain=mail_domain,
data={
"localpart": "test",
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
"mailbox": "mailbox23",
},
)
self.mailaddress_objects.filter(
domain=maildomain, localpart="test"
).exists.return_value = False
self.assertTrue(form.is_valid())
form.cleaned_data["mailbox_or_forwards"] = -1
address1 = MagicMock(mailaddress="test2@example.org")
address2 = MagicMock(mailaddress="test3@example.org")
instance.set_forward_addresses.return_value = [address1, address2]
mailbox = MagicMock(osuser=osuser, username="testuserp01")
instance.set_mailbox.return_value = mailbox
form.save(commit=True)
instance.set_mailbox.assert_not_called()
instance.set_forward_addresses.assert_not_called()
address1.save.assert_not_called()
address2.save.assert_not_called()
mailbox.save.assert_not_called()
instance.save.assert_called_with()
class EditMailAddressFormTest(TestCase):
def setUp(self):
self.patcher1 = patch("managemails.forms.Mailbox.objects")
self.patcher2 = patch("managemails.forms.MailAddress.objects")
self.mailbox_objects = self.patcher1.start()
self.mailaddress_objects = self.patcher2.start()
def tearDown(self):
self.patcher2.stop()
self.patcher1.stop()
def test_constructor_needs_hostingpackage(self):
instance = MagicMock()
with self.assertRaises(KeyError):
@ -430,14 +378,13 @@ class EditMailAddressFormTest(TestCase):
EditMailAddressForm(instance=instance, hostingpackage=MagicMock())
def test_constructor(self):
instance = MagicMock(id=23)
osuser = Mock(username="testuser")
instance = MailAddress(id=23)
osuser = osusers.models.User(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
maildomain = MailDomain.objects.create(domain="example.org")
form = EditMailAddressForm(
instance=instance, maildomain=maildomain, hostingpackage=hostingpackage
)
self.mailbox_objects.unused_or_own.assert_called_with(instance, osuser)
self.assertIn("mailbox_or_forwards", form.fields)
self.assertIn("mailbox", form.fields)
self.assertIn("forwards", form.fields)
@ -456,6 +403,7 @@ class EditMailAddressFormTest(TestCase):
self.assertEqual(len(form.helper.layout), 2)
self.assertEqual(form.helper.layout[1].name, "submit")
@skip("needs mailbox refactoring")
def test_clean_no_mailbox_choice(self):
instance = MagicMock(id=23)
osuser = Mock(username="testuser")
@ -470,6 +418,7 @@ class EditMailAddressFormTest(TestCase):
self.assertFalse(form.is_valid())
self.assertIn("mailbox", form.errors)
@skip("needs mailbox refactoring")
def test_clean_no_forward_address_choice(self):
instance = MagicMock(id=23)
osuser = Mock(username="testuser")
@ -485,10 +434,10 @@ class EditMailAddressFormTest(TestCase):
self.assertIn("forwards", form.errors)
def test_save_with_forwards_no_commit(self):
instance = MagicMock(id=23)
osuser = Mock(username="testuser")
maildomain = MailDomain.objects.create(domain="example.org")
instance = MailAddress(id=23, domain=maildomain)
osuser = osusers.models.User(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
form = EditMailAddressForm(
instance=instance,
maildomain=maildomain,
@ -499,25 +448,17 @@ class EditMailAddressFormTest(TestCase):
},
)
self.assertTrue(form.is_valid())
address1 = MagicMock(mailaddress="test2@example.org")
address2 = MagicMock(mailaddress="test3@example.org")
instance.set_forward_addresses.return_value = [address1, address2]
form.save(commit=False)
instance.set_forward_addresses.assert_called_with(
["test2@example.org", "test3@example.org"], False
)
address1.save.assert_not_called()
address2.save.assert_not_called()
instance.save.assert_not_called()
def test_save_with_forwards_commit(self):
instance = MagicMock(id=23)
osuser = Mock(username="testuser")
osuser = osusers.models.User(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
mail_domain = MailDomain.objects.create(domain="example.org")
instance = MailAddress(id=23, domain=mail_domain)
form = EditMailAddressForm(
instance=instance,
maildomain=maildomain,
maildomain=mail_domain,
hostingpackage=hostingpackage,
data={
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.forwards,
@ -525,15 +466,9 @@ class EditMailAddressFormTest(TestCase):
},
)
self.assertTrue(form.is_valid())
address1 = MagicMock(mailaddress="test2@example.org")
address2 = MagicMock(mailaddress="test3@example.org")
instance.set_forward_addresses.return_value = [address1, address2]
form.save(commit=True)
instance.set_forward_addresses.assert_called_with(
["test2@example.org", "test3@example.org"], True
)
instance.save.assert_called_with()
@skip("needs mailbox refactoring")
def test_save_with_mailbox_no_commit(self):
instance = MagicMock(id=23)
osuser = Mock(username="testuser")
@ -556,14 +491,15 @@ class EditMailAddressFormTest(TestCase):
mailbox.save.assert_not_called()
instance.save.assert_not_called()
@skip("needs mailbox refactoring")
def test_save_with_mailbox_commit(self):
instance = MagicMock(id=23)
osuser = Mock(username="testuser")
instance = MailAddress(id=23)
osuser = osusers.models.User(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
mail_domain = MailDomain.objects.create(domain="example.org")
form = EditMailAddressForm(
instance=instance,
maildomain=maildomain,
maildomain=mail_domain,
hostingpackage=hostingpackage,
data={
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
@ -571,22 +507,18 @@ class EditMailAddressFormTest(TestCase):
},
)
self.assertTrue(form.is_valid())
mailbox = MagicMock(osuser=osuser, username="testuserp01")
instance.set_mailbox.return_value = mailbox
self.mailbox_objects.unused_or_own.get.return_value = mailbox
form.save(commit=True)
instance.set_mailbox.assert_called_with(ANY, True)
instance.save.assert_called_with()
@skip("needs mailbox refactoring")
def test_save_with_other_choice(self):
instance = MagicMock(id=23)
osuser = Mock(username="testuser")
hostingpackage = MagicMock(id=42, osuser=osuser)
maildomain = MagicMock(domain="example.org")
mail_domain = MailDomain.objects.create(domain="example.org")
instance = MailAddress(id=23, domain=mail_domain)
os_user = osusers.models.User(username="testuser")
hosting_package = MagicMock(id=42, osuser=os_user)
form = EditMailAddressForm(
instance=instance,
maildomain=maildomain,
hostingpackage=hostingpackage,
maildomain=mail_domain,
hostingpackage=hosting_package,
data={
"mailbox_or_forwards": MAILBOX_OR_FORWARDS.mailbox,
"mailbox": "mailbox23",

View file

@ -3,16 +3,14 @@ This module contains tests for :py:mod:`managemails.models`
"""
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import TestCase, TransactionTestCase
from django.test.utils import override_settings
from django.contrib.auth import get_user_model
from passlib.hash import sha512_crypt
from domains.models import MailDomain
from osusers.models import User
from managemails.models import MailAddress, Mailbox
from osusers.models import User
Customer = get_user_model()
@ -251,7 +249,9 @@ class MailboxManagerTest(TransactionTestCase):
address = MailAddress.objects.create(localpart="test", domain=md)
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)]
assignable = Mailbox.objects.unused_or_own(address, self.user)
self.assertQuerysetEqual(assignable, [repr(mb) for mb in mailboxes])
self.assertQuerysetEqual(
assignable, [repr(mb) for mb in mailboxes], transform=repr
)
def test_unused_or_own_assigned(self):
md = MailDomain.objects.create(domain="example.org")
@ -259,7 +259,9 @@ class MailboxManagerTest(TransactionTestCase):
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)]
address.set_mailbox(mailboxes[0])
assignable = Mailbox.objects.unused_or_own(address, self.user)
self.assertQuerysetEqual(assignable, [repr(mb) for mb in mailboxes])
self.assertQuerysetEqual(
assignable, [repr(mb) for mb in mailboxes], transform=repr
)
def test_unused_or_own_assigned_other(self):
md = MailDomain.objects.create(domain="example.org")
@ -268,7 +270,7 @@ class MailboxManagerTest(TransactionTestCase):
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)]
address2.set_mailbox(mailboxes[0])
assignable = Mailbox.objects.unused_or_own(address, self.user)
self.assertQuerysetEqual(assignable, [repr(mailboxes[1])])
self.assertQuerysetEqual(assignable, [repr(mailboxes[1])], transform=repr)
def test_unused_fresh(self):
mailboxes = Mailbox.objects.unused(self.user)
@ -277,7 +279,7 @@ class MailboxManagerTest(TransactionTestCase):
def test_unused_unassigned(self):
mailbox = Mailbox.objects.create_mailbox(self.user)
mailboxes = Mailbox.objects.unused(self.user)
self.assertQuerysetEqual(mailboxes, [repr(mailbox)])
self.assertQuerysetEqual(mailboxes, [repr(mailbox)], transform=repr)
def test_unused_assigned(self):
md = MailDomain.objects.create(domain="example.org")
@ -285,7 +287,7 @@ class MailboxManagerTest(TransactionTestCase):
mailboxes = [Mailbox.objects.create_mailbox(self.user) for _ in range(2)]
address.set_mailbox(mailboxes[0])
assignable = Mailbox.objects.unused(self.user)
self.assertQuerysetEqual(assignable, [repr(mailboxes[1])])
self.assertQuerysetEqual(assignable, [repr(mailboxes[1])], transform=repr)
def test_create_mailbox_no_password(self):
mailbox = Mailbox.objects.create_mailbox(self.user)

View file

@ -3,9 +3,9 @@ This module defines the URL patterns for mailbox and mail address related
views.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf.urls import url
from django.urls import re_path
from .views import (
AddMailAddress,
@ -16,16 +16,29 @@ from .views import (
)
urlpatterns = [
url(r'^(?P<package>\d+)/mailbox/create$',
CreateMailbox.as_view(), name='create_mailbox'),
url(r'^(?P<package>\d+)/mailbox/(?P<slug>[\w0-9]+)/setpassword$',
ChangeMailboxPassword.as_view(), name='change_mailbox_password'),
url(r'^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/create$',
AddMailAddress.as_view(), name='add_mailaddress'),
url(r'^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/(?P<pk>\d+)'
r'/edit$',
EditMailAddress.as_view(), name='edit_mailaddress'),
url(r'^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/(?P<pk>\d+)'
r'/delete$',
DeleteMailAddress.as_view(), name='delete_mailaddress'),
re_path(
r"^(?P<package>\d+)/mailbox/create$",
CreateMailbox.as_view(),
name="create_mailbox",
),
re_path(
r"^(?P<package>\d+)/mailbox/(?P<slug>[\w0-9]+)/setpassword$",
ChangeMailboxPassword.as_view(),
name="change_mailbox_password",
),
re_path(
r"^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/create$",
AddMailAddress.as_view(),
name="add_mailaddress",
),
re_path(
r"^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/(?P<pk>\d+)" r"/edit$",
EditMailAddress.as_view(),
name="edit_mailaddress",
),
re_path(
r"^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/(?P<pk>\d+)" r"/delete$",
DeleteMailAddress.as_view(),
name="delete_mailaddress",
),
]

View file

@ -2,34 +2,26 @@
This module defines views for mailbox and mail address handling.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.contrib import messages
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import ugettext as _
from django.views.generic.edit import (
CreateView,
DeleteView,
UpdateView,
)
from django.contrib import messages
from django.utils.translation import gettext as _
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from gvawebcore.views import HostingPackageAndCustomerMixin
from domains.models import MailDomain
from gvawebcore.views import HostingPackageAndCustomerMixin
from .forms import (
MAILBOX_OR_FORWARDS,
AddMailAddressForm,
ChangeMailboxPasswordForm,
CreateMailboxForm,
EditMailAddressForm,
MAILBOX_OR_FORWARDS,
)
from .models import (
MailAddress,
MailAddressMailbox,
Mailbox,
)
from .models import MailAddress, MailAddressMailbox, Mailbox
class CreateMailbox(
@ -39,38 +31,42 @@ class CreateMailbox(
This view is used to set up new mailboxes for a customer hosting package.
"""
model = Mailbox
context_object_name = 'mailbox'
template_name_suffix = '_create'
context_object_name = "mailbox"
template_name_suffix = "_create"
form_class = CreateMailboxForm
def dispatch(self, request, *args, **kwargs):
resp = super(CreateMailbox, self).dispatch(request, *args, **kwargs)
if request.method != 'POST':
if request.method != "POST":
if not self.get_hosting_package().may_add_mailbox():
resp = HttpResponseForbidden(
_('You are not allowed to add more mailboxes to this'
' hosting package'))
_(
"You are not allowed to add more mailboxes to this"
" hosting package"
)
)
return resp
def get_context_data(self, **kwargs):
context = super(CreateMailbox, self).get_context_data(**kwargs)
context['hostingpackage'] = self.get_hosting_package()
context['customer'] = self.get_customer_object()
context["hostingpackage"] = self.get_hosting_package()
context["customer"] = self.get_customer_object()
return context
def get_form_kwargs(self):
kwargs = super(CreateMailbox, self).get_form_kwargs()
kwargs['hostingpackage'] = self.get_hosting_package()
kwargs["hostingpackage"] = self.get_hosting_package()
return kwargs
def form_valid(self, form):
mailbox = form.save()
messages.success(
self.request,
_('Mailbox {mailbox} created successfully.').format(
_("Mailbox {mailbox} created successfully.").format(
mailbox=mailbox.username
)
),
)
return redirect(self.get_hosting_package())
@ -82,30 +78,31 @@ class ChangeMailboxPassword(
This view is used to set a new password for an existing mailbox.
"""
context_object_name = 'mailbox'
context_object_name = "mailbox"
form_class = ChangeMailboxPasswordForm
model = Mailbox
slug_field = 'username'
template_name_suffix = '_setpassword'
slug_field = "username"
template_name_suffix = "_setpassword"
def get_context_data(self, **kwargs):
context = super(ChangeMailboxPassword, self).get_context_data(**kwargs)
context['hostingpackage'] = self.get_hosting_package()
context['customer'] = self.get_customer_object()
context["hostingpackage"] = self.get_hosting_package()
context["customer"] = self.get_customer_object()
return context
def get_form_kwargs(self):
kwargs = super(ChangeMailboxPassword, self).get_form_kwargs()
kwargs['hostingpackage'] = self.get_hosting_package()
kwargs["hostingpackage"] = self.get_hosting_package()
return kwargs
def form_valid(self, form):
mailbox = form.save()
messages.success(
self.request,
_('Successfully set new password for mailbox {mailbox}.').format(
_("Successfully set new password for mailbox {mailbox}.").format(
mailbox=mailbox.username
)
),
)
return redirect(self.get_hosting_package())
@ -117,33 +114,37 @@ class AddMailAddress(
This view is used to add a new mail address to a domain.
"""
context_object_name = 'mailaddress'
context_object_name = "mailaddress"
form_class = AddMailAddressForm
model = MailAddress
template_name_suffix = '_create'
template_name_suffix = "_create"
def get_context_data(self, **kwargs):
context = super(AddMailAddress, self).get_context_data(**kwargs)
context['customer'] = self.get_customer_object()
context["customer"] = self.get_customer_object()
return context
def get_maildomain(self):
return get_object_or_404(MailDomain, domain=self.kwargs['domain'])
return get_object_or_404(MailDomain, domain=self.kwargs["domain"])
def get_form_kwargs(self):
kwargs = super(AddMailAddress, self).get_form_kwargs()
kwargs.update({
'hostingpackage': self.get_hosting_package(),
'maildomain': self.get_maildomain(),
})
kwargs.update(
{
"hostingpackage": self.get_hosting_package(),
"maildomain": self.get_maildomain(),
}
)
return kwargs
def form_valid(self, form):
address = form.save()
messages.success(
self.request,
_('Successfully added mail address {mailaddress}').format(
mailaddress=address)
_("Successfully added mail address {mailaddress}").format(
mailaddress=address
),
)
return redirect(self.get_hosting_package())
@ -155,19 +156,22 @@ class DeleteMailAddress(
This view is used to delete a mail address.
"""
context_object_name = 'mailaddress'
context_object_name = "mailaddress"
model = MailAddress
def get_maildomain(self):
return get_object_or_404(MailDomain, domain=self.kwargs['domain'])
return get_object_or_404(MailDomain, domain=self.kwargs["domain"])
def get_context_data(self, **kwargs):
context = super(DeleteMailAddress, self).get_context_data(**kwargs)
context.update({
'customer': self.get_customer_object(),
'hostingpackage': self.get_hosting_package(),
'maildomain': self.get_maildomain(),
})
context.update(
{
"customer": self.get_customer_object(),
"hostingpackage": self.get_hosting_package(),
"maildomain": self.get_maildomain(),
}
)
return context
def get_success_url(self):
@ -182,45 +186,49 @@ class EditMailAddress(
addresses.
"""
context_object_name = 'mailaddress'
context_object_name = "mailaddress"
form_class = EditMailAddressForm
model = MailAddress
template_name_suffix = '_edit'
template_name_suffix = "_edit"
def get_maildomain(self):
return get_object_or_404(MailDomain, domain=self.kwargs['domain'])
return get_object_or_404(MailDomain, domain=self.kwargs["domain"])
def get_context_data(self, **kwargs):
context = super(EditMailAddress, self).get_context_data(**kwargs)
context['customer'] = self.get_customer_object()
context["customer"] = self.get_customer_object()
return context
def get_form_kwargs(self):
kwargs = super(EditMailAddress, self).get_form_kwargs()
kwargs.update({
'hostingpackage': self.get_hosting_package(),
'maildomain': self.get_maildomain(),
})
kwargs.update(
{
"hostingpackage": self.get_hosting_package(),
"maildomain": self.get_maildomain(),
}
)
return kwargs
def get_initial(self):
initial = super(EditMailAddress, self).get_initial()
mailaddress = self.get_object()
if MailAddressMailbox.objects.filter(mailaddress=mailaddress).exists():
initial['mailbox'] = mailaddress.mailaddressmailbox.mailbox
initial['mailbox_or_forwards'] = MAILBOX_OR_FORWARDS.mailbox
initial["mailbox"] = mailaddress.mailaddressmailbox.mailbox
initial["mailbox_or_forwards"] = MAILBOX_OR_FORWARDS.mailbox
elif mailaddress.mailaddressforward_set.exists():
initial['forwards'] = ", ".join(
initial["forwards"] = ", ".join(
fwd.target for fwd in mailaddress.mailaddressforward_set.all()
)
initial['mailbox_or_forwards'] = MAILBOX_OR_FORWARDS.forwards
initial["mailbox_or_forwards"] = MAILBOX_OR_FORWARDS.forwards
return initial
def form_valid(self, form):
mailaddress = form.save()
messages.success(
self.request,
_('Successfully updated mail address {mailaddress} '
'targets.').format(mailaddress=mailaddress)
_("Successfully updated mail address {mailaddress} " "targets.").format(
mailaddress=mailaddress
),
)
return redirect(self.get_hosting_package())

View file

@ -2,4 +2,3 @@
This app is for managing operating system users and groups.
"""
default_app_config = 'osusers.apps.OsusersAppConfig'

View file

@ -8,11 +8,12 @@ The module starts Celery_ tasks.
"""
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from fileservertasks.tasks import set_file_ssh_authorized_keys
from gvawebcore.forms import PASSWORD_MISMATCH_ERROR
from taskresults.models import TaskResult
from .forms import DUPLICATE_SSH_PUBLIC_KEY_FOR_USER, INVALID_SSH_PUBLIC_KEY
from .models import AdditionalGroup, Group, Shadow, SshPublicKey, User

View file

@ -3,10 +3,8 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`osusers` app.
"""
from __future__ import unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class OsusersAppConfig(AppConfig):
@ -14,8 +12,9 @@ class OsusersAppConfig(AppConfig):
AppConfig for the :py:mod:`osusers` app.
"""
name = 'osusers'
verbose_name = _('Operating System Users and Groups')
name = "osusers"
verbose_name = _("Operating System Users and Groups")
def ready(self):
"""

View file

@ -2,14 +2,11 @@
This module defines operating system user related forms.
"""
from __future__ import unicode_literals
from django import forms
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django import forms
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from gvawebcore.forms import PasswordModelFormMixin

View file

@ -1,230 +1,383 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone
import model_utils.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='AdditionalGroup',
name="AdditionalGroup",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
],
options={
'verbose_name': 'Additional group',
'verbose_name_plural': 'Additional groups',
"verbose_name": "Additional group",
"verbose_name_plural": "Additional groups",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='DeleteTaskResult',
name="DeleteTaskResult",
fields=[
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('task_uuid', models.CharField(
max_length=64, serialize=False, primary_key=True)),
('task_name', models.CharField(max_length=255, db_index=True)),
('is_finished', models.BooleanField(default=False)),
('is_success', models.BooleanField(default=False)),
('state', models.CharField(max_length=10)),
('result_body', models.TextField(blank=True)),
('modeltype', models.CharField(max_length=20, db_index=True)),
('modelname', models.CharField(max_length=255)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"task_uuid",
models.CharField(max_length=64, serialize=False, primary_key=True),
),
("task_name", models.CharField(max_length=255, db_index=True)),
("is_finished", models.BooleanField(default=False)),
("is_success", models.BooleanField(default=False)),
("state", models.CharField(max_length=10)),
("result_body", models.TextField(blank=True)),
("modeltype", models.CharField(max_length=20, db_index=True)),
("modelname", models.CharField(max_length=255)),
],
options={
'abstract': False,
"abstract": False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Group',
name="Group",
fields=[
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('groupname', models.CharField(
unique=True, max_length=16, verbose_name='Group name')),
('gid', models.PositiveSmallIntegerField(
unique=True, serialize=False, verbose_name='Group ID',
primary_key=True)),
('descr', models.TextField(
verbose_name='Description', blank=True)),
('passwd', models.CharField(
max_length=128, verbose_name='Group password', blank=True)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"groupname",
models.CharField(
unique=True, max_length=16, verbose_name="Group name"
),
),
(
"gid",
models.PositiveSmallIntegerField(
unique=True,
serialize=False,
verbose_name="Group ID",
primary_key=True,
),
),
("descr", models.TextField(verbose_name="Description", blank=True)),
(
"passwd",
models.CharField(
max_length=128, verbose_name="Group password", blank=True
),
),
],
options={
'verbose_name': 'Group',
'verbose_name_plural': 'Groups',
"verbose_name": "Group",
"verbose_name_plural": "Groups",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='GroupTaskResult',
name="GroupTaskResult",
fields=[
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('task_uuid', models.CharField(
max_length=64, serialize=False, primary_key=True)),
('task_name', models.CharField(max_length=255, db_index=True)),
('is_finished', models.BooleanField(default=False)),
('is_success', models.BooleanField(default=False)),
('state', models.CharField(max_length=10)),
('result_body', models.TextField(blank=True)),
('group', models.ForeignKey(
to='osusers.Group', on_delete=models.CASCADE)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"task_uuid",
models.CharField(max_length=64, serialize=False, primary_key=True),
),
("task_name", models.CharField(max_length=255, db_index=True)),
("is_finished", models.BooleanField(default=False)),
("is_success", models.BooleanField(default=False)),
("state", models.CharField(max_length=10)),
("result_body", models.TextField(blank=True)),
(
"group",
models.ForeignKey(to="osusers.Group", on_delete=models.CASCADE),
),
],
options={
'abstract': False,
"abstract": False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='User',
name="User",
fields=[
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('username', models.CharField(
unique=True, max_length=64, verbose_name='User name')),
('uid', models.PositiveSmallIntegerField(
unique=True, serialize=False, verbose_name='User ID',
primary_key=True)),
('gecos', models.CharField(
max_length=128, verbose_name='Gecos field', blank=True)),
('homedir', models.CharField(
max_length=256, verbose_name='Home directory')),
('shell', models.CharField(
max_length=64, verbose_name='Login shell')),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"username",
models.CharField(
unique=True, max_length=64, verbose_name="User name"
),
),
(
"uid",
models.PositiveSmallIntegerField(
unique=True,
serialize=False,
verbose_name="User ID",
primary_key=True,
),
),
(
"gecos",
models.CharField(
max_length=128, verbose_name="Gecos field", blank=True
),
),
(
"homedir",
models.CharField(max_length=256, verbose_name="Home directory"),
),
("shell", models.CharField(max_length=64, verbose_name="Login shell")),
],
options={
'verbose_name': 'Benutzer',
'verbose_name_plural': 'Users',
"verbose_name": "Benutzer",
"verbose_name_plural": "Users",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Shadow',
name="Shadow",
fields=[
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('user', models.OneToOneField(
primary_key=True, serialize=False, to='osusers.User',
verbose_name='Benutzer', on_delete=models.CASCADE)),
('passwd', models.CharField(
max_length=128, verbose_name='Encrypted password')),
('changedays', models.PositiveSmallIntegerField(
help_text='This is expressed in days since Jan 1, 1970',
null=True, verbose_name='Date of last change', blank=True)),
('minage', models.PositiveSmallIntegerField(
help_text='Minimum number of days before the password can '
'be changed',
null=True, verbose_name='Minimum age', blank=True)),
('maxage', models.PositiveSmallIntegerField(
help_text='Maximum number of days after which the '
'password has to be changed',
null=True, verbose_name='Maximum age', blank=True)),
('gracedays', models.PositiveSmallIntegerField(
help_text='The number of days before the password is '
'going to expire',
null=True, verbose_name='Grace period', blank=True)),
('inactdays', models.PositiveSmallIntegerField(
help_text='The number of days after the password has '
'expired during which the password should still '
'be accepted',
null=True, verbose_name='Inactivity period', blank=True)),
('expiredays', models.PositiveSmallIntegerField(
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"user",
models.OneToOneField(
primary_key=True,
serialize=False,
to="osusers.User",
verbose_name="Benutzer",
on_delete=models.CASCADE,
),
),
(
"passwd",
models.CharField(max_length=128, verbose_name="Encrypted password"),
),
(
"changedays",
models.PositiveSmallIntegerField(
help_text="This is expressed in days since Jan 1, 1970",
null=True,
verbose_name="Date of last change",
blank=True,
),
),
(
"minage",
models.PositiveSmallIntegerField(
help_text="Minimum number of days before the password can "
"be changed",
null=True,
verbose_name="Minimum age",
blank=True,
),
),
(
"maxage",
models.PositiveSmallIntegerField(
help_text="Maximum number of days after which the "
"password has to be changed",
null=True,
verbose_name="Maximum age",
blank=True,
),
),
(
"gracedays",
models.PositiveSmallIntegerField(
help_text="The number of days before the password is "
"going to expire",
null=True,
verbose_name="Grace period",
blank=True,
),
),
(
"inactdays",
models.PositiveSmallIntegerField(
help_text="The number of days after the password has "
"expired during which the password should still "
"be accepted",
null=True,
verbose_name="Inactivity period",
blank=True,
),
),
(
"expiredays",
models.PositiveSmallIntegerField(
default=None,
help_text='The date of expiration of the account, '
'expressed as number of days since Jan 1, 1970',
null=True, verbose_name='Account expiration date',
blank=True)),
help_text="The date of expiration of the account, "
"expressed as number of days since Jan 1, 1970",
null=True,
verbose_name="Account expiration date",
blank=True,
),
),
],
options={
'verbose_name': 'Shadow password',
'verbose_name_plural': 'Shadow passwords',
"verbose_name": "Shadow password",
"verbose_name_plural": "Shadow passwords",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='UserTaskResult',
name="UserTaskResult",
fields=[
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('task_uuid', models.CharField(
max_length=64, serialize=False, primary_key=True)),
('task_name', models.CharField(max_length=255, db_index=True)),
('is_finished', models.BooleanField(default=False)),
('is_success', models.BooleanField(default=False)),
('state', models.CharField(max_length=10)),
('result_body', models.TextField(blank=True)),
('user', models.ForeignKey(
to='osusers.User', on_delete=models.CASCADE)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"task_uuid",
models.CharField(max_length=64, serialize=False, primary_key=True),
),
("task_name", models.CharField(max_length=255, db_index=True)),
("is_finished", models.BooleanField(default=False)),
("is_success", models.BooleanField(default=False)),
("state", models.CharField(max_length=10)),
("result_body", models.TextField(blank=True)),
(
"user",
models.ForeignKey(to="osusers.User", on_delete=models.CASCADE),
),
],
options={
'abstract': False,
"abstract": False,
},
bases=(models.Model,),
),
migrations.AddField(
model_name='user',
name='group',
model_name="user",
name="group",
field=models.ForeignKey(
verbose_name='Group', to='osusers.Group',
on_delete=models.CASCADE),
verbose_name="Group", to="osusers.Group", on_delete=models.CASCADE
),
preserve_default=True,
),
migrations.AddField(
model_name='additionalgroup',
name='group',
field=models.ForeignKey(
to='osusers.Group', on_delete=models.CASCADE),
model_name="additionalgroup",
name="group",
field=models.ForeignKey(to="osusers.Group", on_delete=models.CASCADE),
preserve_default=True,
),
migrations.AddField(
model_name='additionalgroup',
name='user',
field=models.ForeignKey(
to='osusers.User', on_delete=models.CASCADE),
model_name="additionalgroup",
name="user",
field=models.ForeignKey(to="osusers.User", on_delete=models.CASCADE),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='additionalgroup',
unique_together={('user', 'group')},
name="additionalgroup",
unique_together={("user", "group")},
),
]

View file

@ -1,31 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('osusers', '0001_initial'),
("osusers", "0001_initial"),
]
operations = [
migrations.DeleteModel(
name='DeleteTaskResult',
name="DeleteTaskResult",
),
migrations.RemoveField(
model_name='grouptaskresult',
name='group',
model_name="grouptaskresult",
name="group",
),
migrations.DeleteModel(
name='GroupTaskResult',
name="GroupTaskResult",
),
migrations.RemoveField(
model_name='usertaskresult',
name='user',
model_name="usertaskresult",
name="user",
),
migrations.DeleteModel(
name='UserTaskResult',
name="UserTaskResult",
),
]

View file

@ -1,23 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('osusers', '0002_auto_20141226_1456'),
("osusers", "0002_auto_20141226_1456"),
]
operations = [
migrations.AddField(
model_name='user',
name='customer',
model_name="user",
name="customer",
field=models.ForeignKey(
default=1, to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE),
default=1, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
),
preserve_default=False,
),
]

View file

@ -1,25 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osusers', '0003_user_customer'),
("osusers", "0003_user_customer"),
]
operations = [
migrations.AlterModelOptions(
name='user',
options={'verbose_name': 'User', 'verbose_name_plural': 'Users'},
name="user",
options={"verbose_name": "User", "verbose_name_plural": "Users"},
),
migrations.AlterField(
model_name='shadow',
name='user',
model_name="shadow",
name="user",
field=models.OneToOneField(
primary_key=True, serialize=False, to='osusers.User',
verbose_name='User', on_delete=models.CASCADE),
primary_key=True,
serialize=False,
to="osusers.User",
verbose_name="User",
on_delete=models.CASCADE,
),
preserve_default=True,
),
]

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone
import model_utils.fields
from django.db import migrations, models
@ -8,41 +6,64 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osusers', '0004_auto_20150104_1751'),
("osusers", "0004_auto_20150104_1751"),
]
operations = [
migrations.CreateModel(
name='SshPublicKey',
name="SshPublicKey",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('algorithm', models.CharField(
max_length=20, verbose_name='Algorithm')),
('data', models.TextField(
help_text='Base64 encoded key bytes',
verbose_name='Key bytes')),
('comment', models.TextField(
verbose_name='Comment', blank=True)),
('user', models.ForeignKey(
verbose_name='User', to='osusers.User',
on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"algorithm",
models.CharField(max_length=20, verbose_name="Algorithm"),
),
(
"data",
models.TextField(
help_text="Base64 encoded key bytes", verbose_name="Key bytes"
),
),
("comment", models.TextField(verbose_name="Comment", blank=True)),
(
"user",
models.ForeignKey(
verbose_name="User", to="osusers.User", on_delete=models.CASCADE
),
),
],
options={
'verbose_name': 'SSH public key',
'verbose_name_plural': 'SSH public keys',
"verbose_name": "SSH public key",
"verbose_name_plural": "SSH public keys",
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='sshpublickey',
unique_together={('user', 'algorithm', 'data')},
name="sshpublickey",
unique_together={("user", "algorithm", "data")},
),
]

View file

@ -12,7 +12,7 @@ from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.dispatch import Signal
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from model_utils.models import TimeStampedModel
from passlib.hash import sha512_crypt
from passlib.pwd import genword
@ -20,7 +20,7 @@ from passlib.pwd import genword
_LOGGER = logging.getLogger(__name__)
password_set = Signal(providing_args=["instance", "password"])
password_set = Signal()
CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL = _("You can not use a user's primary group.")
@ -365,7 +365,7 @@ class Shadow(TimeStampedModel, models.Model):
:param str password: the password
"""
self.passwd = sha512_crypt.encrypt(password)
self.passwd = sha512_crypt.hash(password)
class AdditionalGroup(TimeStampedModel, models.Model):

View file

@ -6,14 +6,11 @@ The module starts Celery_ tasks.
.. _Celery: http://www.celeryproject.org/
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
import logging
from django.db.models.signals import (
post_delete,
post_save,
)
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from fileservertasks.tasks import (
@ -34,14 +31,7 @@ from ldaptasks.tasks import (
)
from taskresults.models import TaskResult
from .models import (
AdditionalGroup,
Group,
SshPublicKey,
User,
password_set,
)
from .models import AdditionalGroup, Group, SshPublicKey, User, password_set
_LOGGER = logging.getLogger(__name__)
@ -76,11 +66,12 @@ def handle_user_password_set(sender, instance, password, **kwargs):
}
"""
taskresult = TaskResult.objects.create_task_result(
'handle_user_password_set',
set_ldap_user_password.s(instance.username, password))
"handle_user_password_set",
set_ldap_user_password.s(instance.username, password),
)
_LOGGER.info(
'LDAP password change has been requested in task %s',
taskresult.task_id)
"LDAP password change has been requested in task %s", taskresult.task_id
)
@receiver(post_save, sender=Group)
@ -114,14 +105,13 @@ def handle_group_created(sender, instance, created, **kwargs):
"""
if created:
taskresult = TaskResult.objects.create_task_result(
'handle_group_created',
create_ldap_group.s(
instance.groupname, instance.gid, instance.descr))
"handle_group_created",
create_ldap_group.s(instance.groupname, instance.gid, instance.descr),
)
_LOGGER.info(
'LDAP group creation has been requested in task %s',
taskresult.task_id)
_LOGGER.debug(
'group %s has been %s', instance, created and "created" or "updated")
"LDAP group creation has been requested in task %s", taskresult.task_id
)
_LOGGER.debug("group %s has been %s", instance, created and "created" or "updated")
@receiver(post_save, sender=User)
@ -167,18 +157,24 @@ def handle_user_created(sender, instance, created, **kwargs):
"""
if created:
chain = create_ldap_user.s(
instance.username, instance.uid, instance.group.gid,
instance.gecos, instance.homedir, instance.shell, None
) | setup_file_sftp_userdir_chained.s() | (
setup_file_mail_userdir_chained.s())
taskresult = TaskResult.objects.create_task_result(
'handle_user_created', chain)
chain = (
create_ldap_user.s(
instance.username,
instance.uid,
instance.group.gid,
instance.gecos,
instance.homedir,
instance.shell,
None,
)
| setup_file_sftp_userdir_chained.s()
| (setup_file_mail_userdir_chained.s())
)
taskresult = TaskResult.objects.create_task_result("handle_user_created", chain)
_LOGGER.info(
'LDAP user creation has been requested in task %s',
taskresult.task_id)
_LOGGER.debug(
'user %s has been %s', instance, created and "created" or "updated")
"LDAP user creation has been requested in task %s", taskresult.task_id
)
_LOGGER.debug("user %s has been %s", instance, created and "created" or "updated")
@receiver(post_save, sender=AdditionalGroup)
@ -213,12 +209,13 @@ def handle_user_added_to_group(sender, instance, created, **kwargs):
"""
if created:
taskresult = TaskResult.objects.create_task_result(
'handle_user_added_to_group',
add_ldap_user_to_group.s(
instance.user.username, instance.group.groupname))
"handle_user_added_to_group",
add_ldap_user_to_group.s(instance.user.username, instance.group.groupname),
)
_LOGGER.info(
'Adding user to LDAP group has been requested in task %s',
taskresult.task_id)
"Adding user to LDAP group has been requested in task %s",
taskresult.task_id,
)
@receiver(post_save, sender=SshPublicKey)
@ -252,14 +249,11 @@ def handle_ssh_keys_changed(sender, instance, **kwargs):
"""
sig = set_file_ssh_authorized_keys.s(
instance.user.username, [
str(key) for key in
SshPublicKey.objects.filter(user=instance.user)])
taskresult = TaskResult.objects.create_task_result(
'handle_ssh_keys_changed', sig)
_LOGGER.info(
'Change of SSH keys has been requested in task %s',
taskresult.task_id)
instance.user.username,
[str(key) for key in SshPublicKey.objects.filter(user=instance.user)],
)
taskresult = TaskResult.objects.create_task_result("handle_ssh_keys_changed", sig)
_LOGGER.info("Change of SSH keys has been requested in task %s", taskresult.task_id)
# @receiver(post_delete)
@ -299,11 +293,11 @@ def handle_group_deleted(sender, instance, **kwargs):
"""
taskresult = TaskResult.objects.create_task_result(
'handle_group_deleted',
delete_ldap_group.s(instance.groupname))
"handle_group_deleted", delete_ldap_group.s(instance.groupname)
)
_LOGGER.info(
'LDAP group deletion has been requested in task %s',
taskresult.task_id)
"LDAP group deletion has been requested in task %s", taskresult.task_id
)
@receiver(post_delete, sender=User)
@ -348,15 +342,14 @@ def handle_user_deleted(sender, instance, **kwargs):
}
"""
chain = delete_file_mail_userdir.s(
instance.username
) | delete_file_sftp_userdir_chained.s() | delete_ldap_user_chained.s()
_LOGGER.debug('chain signature %s', chain)
taskresult = TaskResult.objects.create_task_result(
'handle_user_deleted', chain)
_LOGGER.info(
'LDAP user deletion has been requested in task %s',
taskresult.task_id)
chain = (
delete_file_mail_userdir.s(instance.username)
| delete_file_sftp_userdir_chained.s()
| delete_ldap_user_chained.s()
)
_LOGGER.debug("chain signature %s", chain)
taskresult = TaskResult.objects.create_task_result("handle_user_deleted", chain)
_LOGGER.info("LDAP user deletion has been requested in task %s", taskresult.task_id)
@receiver(post_delete, sender=AdditionalGroup)
@ -393,9 +386,10 @@ def handle_user_removed_from_group(sender, instance, **kwargs):
"""
taskresult = TaskResult.objects.create_task_result(
'handle_user_removed_from_group',
remove_ldap_user_from_group.s(
instance.user.username, instance.group.groupname))
"handle_user_removed_from_group",
remove_ldap_user_from_group.s(instance.user.username, instance.group.groupname),
)
_LOGGER.info(
'Removing user from LDAP group has been requested in task %s',
taskresult.task_id)
"Removing user from LDAP group has been requested in task %s",
taskresult.task_id,
)

View file

@ -10,8 +10,8 @@ from django.utils import timezone
from passlib.hash import sha512_crypt
from osusers.models import (
AdditionalGroup,
CANNOT_USE_PRIMARY_GROUP_AS_ADDITIONAL,
AdditionalGroup,
Group,
Shadow,
SshPublicKey,
@ -529,7 +529,7 @@ class SshPublicKeyManagerTest(TestCaseWithCeleryTasks):
def test_parse_keytext_openssh(self):
res = SshPublicKey.objects.parse_key_text(EXAMPLE_KEY_4_OPENSSH)
self.assertEquals(len(res), 3)
self.assertEqual(len(res), 3)
self.assertEqual(res[0], "ssh-rsa")
self.assertGreater(len(res[1]), 40)
self.assertEqual(res[2], "")

View file

@ -3,18 +3,16 @@ This module provides tests for :py:mod:`osusers.views`.
"""
from unittest.mock import patch, MagicMock
from unittest.mock import MagicMock, patch
from django.test import TestCase, TransactionTestCase
from django.contrib.auth import get_user_model
from django.test import TestCase, TransactionTestCase
from django.urls import reverse
from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate
from osusers.models import SshPublicKey
from osusers.views import AddSshPublicKey, DeleteSshPublicKey, EditSshPublicKeyComment
User = get_user_model()
TEST_USER = "test"
@ -31,7 +29,6 @@ EXAMPLE_KEY = "".join(
class HostingPackageAwareTestMixin(object):
# noinspection PyMethodMayBeStatic
def _setup_hosting_package(self, customer):
template = HostingPackageTemplate.objects.create(
@ -169,7 +166,7 @@ class DeleteSshPublicKeyTest(HostingPackageAwareTestMixin, TestCase):
kwargs={"package": str(self.package.pk), "pk": str(self.sshkey.pk)},
)
queryset = view.get_queryset()
self.assertQuerysetEqual(queryset, [repr(self.sshkey)])
self.assertQuerysetEqual(queryset, [repr(self.sshkey)], transform=repr)
def test_get_context_data(self):
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
@ -237,7 +234,7 @@ class EditSshPublicKeyCommentTest(HostingPackageAwareTestMixin, TransactionTestC
kwargs={"package": str(self.package.pk), "pk": str(self.sshkey.pk)},
)
queryset = view.get_queryset()
self.assertQuerysetEqual(queryset, [repr(self.sshkey)])
self.assertQuerysetEqual(queryset, [repr(self.sshkey)], transform=repr)
def test_get_form_kwargs(self):
self.client.login(username=TEST_USER, password=TEST_PASSWORD)

View file

@ -2,9 +2,9 @@
This module defines the URL patterns for operating system user related views.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf.urls import url
from django.urls import re_path
from .views import (
AddSshPublicKey,
@ -14,16 +14,30 @@ from .views import (
SetOsUserPassword,
)
urlpatterns = [
url(r'^(?P<slug>[\w0-9@.+-_]+)/setpassword$', SetOsUserPassword.as_view(),
name='set_osuser_password'),
url(r'^(?P<package>\d+)/ssh-keys/$', ListSshPublicKeys.as_view(),
name='list_ssh_keys'),
url(r'^(?P<package>\d+)/ssh-keys/add$', AddSshPublicKey.as_view(),
name='add_ssh_key'),
url(r'^(?P<package>\d+)/ssh-keys/(?P<pk>\d+)/edit-comment$',
EditSshPublicKeyComment.as_view(), name='edit_ssh_key_comment'),
url(r'^(?P<package>\d+)/ssh-keys/(?P<pk>\d+)/delete$',
DeleteSshPublicKey.as_view(), name='delete_ssh_key'),
re_path(
r"^(?P<slug>[\w0-9@.+-_]+)/setpassword$",
SetOsUserPassword.as_view(),
name="set_osuser_password",
),
re_path(
r"^(?P<package>\d+)/ssh-keys/$",
ListSshPublicKeys.as_view(),
name="list_ssh_keys",
),
re_path(
r"^(?P<package>\d+)/ssh-keys/add$",
AddSshPublicKey.as_view(),
name="add_ssh_key",
),
re_path(
r"^(?P<package>\d+)/ssh-keys/(?P<pk>\d+)/edit-comment$",
EditSshPublicKeyComment.as_view(),
name="edit_ssh_key_comment",
),
re_path(
r"^(?P<package>\d+)/ssh-keys/(?P<pk>\d+)/delete$",
DeleteSshPublicKey.as_view(),
name="delete_ssh_key",
),
]

View file

@ -2,20 +2,15 @@
This module defines the views for gnuviechadmin operating system user handling.
"""
from __future__ import unicode_literals, absolute_import
from __future__ import absolute_import
from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic import (
CreateView,
DeleteView,
ListView,
UpdateView,
)
from django.utils.translation import ugettext as _
from django.contrib import messages
from django.utils.translation import gettext as _
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from gvawebcore.views import HostingPackageAndCustomerMixin
from .forms import (
@ -23,10 +18,7 @@ from .forms import (
ChangeOsUserPasswordForm,
EditSshPublicKeyCommentForm,
)
from .models import (
SshPublicKey,
User,
)
from .models import SshPublicKey, User
class SetOsUserPassword(StaffOrSelfLoginRequiredMixin, UpdateView):
@ -34,19 +26,19 @@ class SetOsUserPassword(StaffOrSelfLoginRequiredMixin, UpdateView):
This view is used for setting a new operating system user password.
"""
model = User
slug_field = 'username'
template_name_suffix = '_setpassword'
context_object_name = 'osuser'
slug_field = "username"
template_name_suffix = "_setpassword"
context_object_name = "osuser"
form_class = ChangeOsUserPasswordForm
def get_customer_object(self):
return self.get_object().customer
def get_context_data(self, *args, **kwargs):
context = super(SetOsUserPassword, self).get_context_data(
*args, **kwargs)
context['customer'] = self.get_customer_object()
context = super(SetOsUserPassword, self).get_context_data(*args, **kwargs)
context["customer"] = self.get_customer_object()
return context
def form_valid(self, form):
@ -55,7 +47,8 @@ class SetOsUserPassword(StaffOrSelfLoginRequiredMixin, UpdateView):
self.request,
_("New password for {username} has been set successfully.").format(
username=osuser.username
))
),
)
return redirect(osuser.customerhostingpackage)
@ -67,30 +60,34 @@ class AddSshPublicKey(
operating system user.
"""
model = SshPublicKey
context_object_name = 'key'
template_name_suffix = '_create'
context_object_name = "key"
template_name_suffix = "_create"
form_class = AddSshPublicKeyForm
def get_form_kwargs(self):
kwargs = super(AddSshPublicKey, self).get_form_kwargs()
kwargs['hostingpackage'] = self.get_hosting_package()
kwargs["hostingpackage"] = self.get_hosting_package()
return kwargs
def get_context_data(self, **kwargs):
context = super(AddSshPublicKey, self).get_context_data(**kwargs)
context.update({
'customer': self.get_customer_object(),
'osuser': self.get_hosting_package().osuser.username,
})
context.update(
{
"customer": self.get_customer_object(),
"osuser": self.get_hosting_package().osuser.username,
}
)
return context
def form_valid(self, form):
key = form.save()
messages.success(
self.request,
_('Successfully added new {algorithm} SSH public key.').format(
algorithm=key.algorithm)
_("Successfully added new {algorithm} SSH public key.").format(
algorithm=key.algorithm
),
)
return redirect(self.get_hosting_package())
@ -104,20 +101,22 @@ class ListSshPublicKeys(
via URL parameter 'pattern'.
"""
model = SshPublicKey
context_object_name = 'keys'
context_object_name = "keys"
def get_queryset(self):
return SshPublicKey.objects.filter(
user=self.get_hosting_package().osuser)
return SshPublicKey.objects.filter(user=self.get_hosting_package().osuser)
def get_context_data(self, **kwargs):
context = super(ListSshPublicKeys, self).get_context_data(**kwargs)
context.update({
'hostingpackage': self.get_hosting_package(),
'customer': self.get_customer_object(),
'osuser': self.get_hosting_package().osuser.username,
})
context.update(
{
"hostingpackage": self.get_hosting_package(),
"customer": self.get_customer_object(),
"osuser": self.get_hosting_package().osuser.username,
}
)
return context
@ -131,24 +130,29 @@ class DeleteSshPublicKey(
"""
model = SshPublicKey
context_object_name = 'key'
context_object_name = "key"
def get_queryset(self):
return super(DeleteSshPublicKey, self).get_queryset().filter(
user=self.get_hosting_package().osuser)
return (
super(DeleteSshPublicKey, self)
.get_queryset()
.filter(user=self.get_hosting_package().osuser)
)
def get_context_data(self, **kwargs):
context = super(DeleteSshPublicKey, self).get_context_data(**kwargs)
context.update({
'hostingpackage': self.get_hosting_package(),
'customer': self.get_customer_object(),
'osuser': self.get_hosting_package().osuser.username,
})
context.update(
{
"hostingpackage": self.get_hosting_package(),
"customer": self.get_customer_object(),
"osuser": self.get_hosting_package().osuser.username,
}
)
return context
def get_success_url(self):
return reverse(
'list_ssh_keys', kwargs={'package': self.get_hosting_package().id}
"list_ssh_keys", kwargs={"package": self.get_hosting_package().id}
)
@ -160,31 +164,36 @@ class EditSshPublicKeyComment(
key <osusers.models.SshPublicKey>`.
"""
model = SshPublicKey
context_object_name = 'key'
template_name_suffix = '_edit_comment'
context_object_name = "key"
template_name_suffix = "_edit_comment"
form_class = EditSshPublicKeyCommentForm
def get_queryset(self):
return super(EditSshPublicKeyComment, self).get_queryset().filter(
user=self.get_hosting_package().osuser)
return (
super(EditSshPublicKeyComment, self)
.get_queryset()
.filter(user=self.get_hosting_package().osuser)
)
def get_form_kwargs(self):
kwargs = super(EditSshPublicKeyComment, self).get_form_kwargs()
kwargs['hostingpackage'] = self.get_hosting_package()
kwargs["hostingpackage"] = self.get_hosting_package()
return kwargs
def get_context_data(self, **kwargs):
context = super(EditSshPublicKeyComment, self).get_context_data(
**kwargs)
context.update({
'hostingpackage': self.get_hosting_package(),
'customer': self.get_customer_object(),
'osuser': self.get_hosting_package().osuser.username,
})
context = super(EditSshPublicKeyComment, self).get_context_data(**kwargs)
context.update(
{
"hostingpackage": self.get_hosting_package(),
"customer": self.get_customer_object(),
"osuser": self.get_hosting_package().osuser.username,
}
)
return context
def get_success_url(self):
return reverse(
'list_ssh_keys', kwargs={'package': self.get_hosting_package().id}
"list_ssh_keys", kwargs={"package": self.get_hosting_package().id}
)

View file

@ -4,8 +4,6 @@ results of all `Celery <http://www.celeryproject.org/>`_ tasks that are not
marked as finished yet.
"""
from __future__ import unicode_literals
from django.core.management.base import BaseCommand
from taskresults.models import TaskResult

View file

@ -1,28 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='TaskResult',
name="TaskResult",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('task_id', models.CharField(max_length=36, verbose_name='Task id')),
('task_name', models.CharField(max_length=64, verbose_name='Task name')),
('result', models.TextField(verbose_name='Task result')),
('finished', models.BooleanField(default=False)),
('state', models.CharField(max_length=16, verbose_name='Task state')),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("task_id", models.CharField(max_length=36, verbose_name="Task id")),
(
"task_name",
models.CharField(max_length=64, verbose_name="Task name"),
),
("result", models.TextField(verbose_name="Task result")),
("finished", models.BooleanField(default=False)),
("state", models.CharField(max_length=16, verbose_name="Task state")),
],
options={
'verbose_name': 'Task result',
'verbose_name_plural': 'Task results',
"verbose_name": "Task result",
"verbose_name_plural": "Task results",
},
bases=(models.Model,),
),

View file

@ -1,36 +1,33 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('taskresults', '0001_initial'),
("taskresults", "0001_initial"),
]
operations = [
migrations.RemoveField(
model_name='taskresult',
name='task_name',
model_name="taskresult",
name="task_name",
),
migrations.AddField(
model_name='taskresult',
name='creator',
field=models.TextField(default='migrated', verbose_name='Task creator'),
model_name="taskresult",
name="creator",
field=models.TextField(default="migrated", verbose_name="Task creator"),
preserve_default=False,
),
migrations.AddField(
model_name='taskresult',
name='notes',
field=models.TextField(default='', verbose_name='Task notes'),
model_name="taskresult",
name="notes",
field=models.TextField(default="", verbose_name="Task notes"),
preserve_default=False,
),
migrations.AddField(
model_name='taskresult',
name='signature',
field=models.TextField(default='', verbose_name='Task signature'),
model_name="taskresult",
name="signature",
field=models.TextField(default="", verbose_name="Task signature"),
preserve_default=False,
),
]

View file

@ -1,31 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.1 on 2016-01-09 14:24
from __future__ import unicode_literals
from django.db import migrations
import django.utils.timezone
import model_utils.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('taskresults', '0002_auto_20151011_2248'),
("taskresults", "0002_auto_20151011_2248"),
]
operations = [
migrations.AlterModelOptions(
name='taskresult',
options={'ordering': ['created'], 'verbose_name': 'Task result', 'verbose_name_plural': 'Task results'},
name="taskresult",
options={
"ordering": ["created"],
"verbose_name": "Task result",
"verbose_name_plural": "Task results",
},
),
migrations.AddField(
model_name='taskresult',
name='created',
field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'),
model_name="taskresult",
name="created",
field=model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
migrations.AddField(
model_name='taskresult',
name='modified',
field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'),
model_name="taskresult",
name="modified",
field=model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
]

View file

@ -2,49 +2,44 @@
This model defines the database models to handle Celery AsyncResults.
"""
from __future__ import unicode_literals
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from model_utils.models import TimeStampedModel
from gnuviechadmin.celery import app
from model_utils.models import TimeStampedModel
class TaskResultManager(models.Manager):
def create_task_result(self, creator, signature, notes=''):
def create_task_result(self, creator, signature, notes=""):
sigstr = str(signature)
result = signature.apply_async()
taskresult = self.create(
task_id=result.task_id, creator=creator, signature=sigstr,
notes=notes)
task_id=result.task_id, creator=creator, signature=sigstr, notes=notes
)
return taskresult
@python_2_unicode_compatible
class TaskResult(TimeStampedModel):
task_id = models.CharField(_('Task id'), max_length=36)
signature = models.TextField(_('Task signature'))
creator = models.TextField(_('Task creator'))
notes = models.TextField(_('Task notes'))
result = models.TextField(_('Task result'))
task_id = models.CharField(_("Task id"), max_length=36)
signature = models.TextField(_("Task signature"))
creator = models.TextField(_("Task creator"))
notes = models.TextField(_("Task notes"))
result = models.TextField(_("Task result"))
finished = models.BooleanField(default=False)
state = models.CharField(_('Task state'), max_length=16)
state = models.CharField(_("Task state"), max_length=16)
objects = TaskResultManager()
class Meta:
verbose_name = _('Task result')
verbose_name_plural = _('Task results')
ordering = ['created']
verbose_name = _("Task result")
verbose_name_plural = _("Task results")
ordering = ["created"]
def __str__(self):
return "{creator} ({task_id}): {finished}".format(
creator=self.creator,
task_id=self.task_id,
finished=_('yes') if self.finished else _('no')
finished=_("yes") if self.finished else _("no"),
)
def fetch_result(self):

View file

@ -2,4 +2,3 @@
This app is for managing database users and user databases.
"""
default_app_config = 'userdbs.apps.UserdbsAppConfig'

View file

@ -6,12 +6,9 @@ from __future__ import absolute_import
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from .models import (
DatabaseUser,
UserDatabase,
)
from .models import DatabaseUser, UserDatabase
class DatabaseUserCreationForm(forms.ModelForm):
@ -23,7 +20,7 @@ class DatabaseUserCreationForm(forms.ModelForm):
class Meta:
model = DatabaseUser
fields = ['osuser', 'db_type']
fields = ["osuser", "db_type"]
def save(self, commit=True):
"""
@ -35,8 +32,10 @@ class DatabaseUserCreationForm(forms.ModelForm):
"""
dbuser = DatabaseUser.objects.create_database_user(
osuser=self.cleaned_data['osuser'],
db_type=self.cleaned_data['db_type'], commit=commit)
osuser=self.cleaned_data["osuser"],
db_type=self.cleaned_data["db_type"],
commit=commit,
)
return dbuser
def save_m2m(self):
@ -55,7 +54,7 @@ class UserDatabaseCreationForm(forms.ModelForm):
class Meta:
model = UserDatabase
fields = ['db_user']
fields = ["db_user"]
def save(self, commit=True):
"""
@ -67,7 +66,8 @@ class UserDatabaseCreationForm(forms.ModelForm):
"""
database = UserDatabase.objects.create_userdatabase(
db_user=self.cleaned_data['db_user'], commit=commit)
db_user=self.cleaned_data["db_user"], commit=commit
)
return database
def save_m2m(self):
@ -83,7 +83,8 @@ class DatabaseUserAdmin(admin.ModelAdmin):
<userdbs.models.DatabaseUser>`
"""
actions = ['perform_delete_selected']
actions = ["perform_delete_selected"]
add_form = DatabaseUserCreationForm
def get_form(self, request, obj=None, **kwargs):
@ -101,12 +102,13 @@ class DatabaseUserAdmin(admin.ModelAdmin):
"""
defaults = {}
if obj is None:
defaults.update({
'form': self.add_form,
})
defaults.update(
{
"form": self.add_form,
}
)
defaults.update(kwargs)
return super(DatabaseUserAdmin, self).get_form(
request, obj, **defaults)
return super(DatabaseUserAdmin, self).get_form(request, obj, **defaults)
def get_readonly_fields(self, request, obj=None):
"""
@ -122,7 +124,7 @@ class DatabaseUserAdmin(admin.ModelAdmin):
"""
if obj:
return ['osuser', 'name', 'db_type']
return ["osuser", "name", "db_type"]
return []
def save_model(self, request, obj, form, change):
@ -154,8 +156,8 @@ class DatabaseUserAdmin(admin.ModelAdmin):
"""
for dbuser in queryset.all():
dbuser.delete()
perform_delete_selected.short_description = _(
'Delete selected database users')
perform_delete_selected.short_description = _("Delete selected database users")
def get_actions(self, request):
"""
@ -170,8 +172,8 @@ class DatabaseUserAdmin(admin.ModelAdmin):
"""
actions = super(DatabaseUserAdmin, self).get_actions(request)
if 'delete_selected' in actions: # pragma: no cover
del actions['delete_selected']
if "delete_selected" in actions: # pragma: no cover
del actions["delete_selected"]
return actions
@ -181,7 +183,8 @@ class UserDatabaseAdmin(admin.ModelAdmin):
<userdbs.models.UserDatabase>`
"""
actions = ['perform_delete_selected']
actions = ["perform_delete_selected"]
add_form = UserDatabaseCreationForm
def get_form(self, request, obj=None, **kwargs):
@ -199,12 +202,13 @@ class UserDatabaseAdmin(admin.ModelAdmin):
"""
defaults = {}
if obj is None:
defaults.update({
'form': self.add_form,
})
defaults.update(
{
"form": self.add_form,
}
)
defaults.update(kwargs)
return super(UserDatabaseAdmin, self).get_form(
request, obj, **defaults)
return super(UserDatabaseAdmin, self).get_form(request, obj, **defaults)
def get_readonly_fields(self, request, obj=None):
"""
@ -220,7 +224,7 @@ class UserDatabaseAdmin(admin.ModelAdmin):
"""
if obj:
return ['db_name', 'db_user']
return ["db_name", "db_user"]
return []
def save_model(self, request, obj, form, change):
@ -252,8 +256,8 @@ class UserDatabaseAdmin(admin.ModelAdmin):
"""
for database in queryset.all():
database.delete()
perform_delete_selected.short_description = _(
'Delete selected user databases')
perform_delete_selected.short_description = _("Delete selected user databases")
def get_actions(self, request):
"""
@ -268,8 +272,8 @@ class UserDatabaseAdmin(admin.ModelAdmin):
"""
actions = super(UserDatabaseAdmin, self).get_actions(request)
if 'delete_selected' in actions: # pragma: no cover
del actions['delete_selected']
if "delete_selected" in actions: # pragma: no cover
del actions["delete_selected"]
return actions

View file

@ -3,10 +3,8 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`userdbs` app.
"""
from __future__ import unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class UserdbsAppConfig(AppConfig):
@ -14,8 +12,9 @@ class UserdbsAppConfig(AppConfig):
AppConfig for the :py:mod:`userdbs` app.
"""
name = 'userdbs'
verbose_name = _('Database Users and their Databases')
name = "userdbs"
verbose_name = _("Database Users and their Databases")
def ready(self):
"""

View file

@ -2,32 +2,27 @@
This module defines form classes for user database editing.
"""
from __future__ import absolute_import, unicode_literals
from django import forms
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from __future__ import absolute_import
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Submit,
)
from crispy_forms.layout import Submit
from django import forms
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from .models import (
DB_TYPES,
DatabaseUser,
UserDatabase,
)
from gvawebcore.forms import PasswordModelFormMixin
from .models import DB_TYPES, DatabaseUser, UserDatabase
class AddUserDatabaseForm(forms.ModelForm, PasswordModelFormMixin):
"""
This form is used to create new user database instances.
"""
db_type = forms.TypedChoiceField(
label=_('Database type'),
label=_("Database type"),
choices=DB_TYPES,
widget=forms.RadioSelect,
coerce=int,
@ -38,17 +33,18 @@ class AddUserDatabaseForm(forms.ModelForm, PasswordModelFormMixin):
fields = []
def __init__(self, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
self.available_dbtypes = kwargs.pop('dbtypes')
self.hosting_package = kwargs.pop("hostingpackage")
self.available_dbtypes = kwargs.pop("dbtypes")
super(AddUserDatabaseForm, self).__init__(*args, **kwargs)
self.fields['db_type'].choices = self.available_dbtypes
self.fields["db_type"].choices = self.available_dbtypes
if len(self.available_dbtypes) == 1:
self.fields['db_type'].initial = self.available_dbtypes[0][0]
self.fields['db_type'].widget = forms.HiddenInput()
self.fields["db_type"].initial = self.available_dbtypes[0][0]
self.fields["db_type"].widget = forms.HiddenInput()
self.helper = FormHelper()
self.helper.form_action = reverse(
'add_userdatabase', kwargs={'package': self.hosting_package.id})
self.helper.add_input(Submit('submit', _('Create database')))
"add_userdatabase", kwargs={"package": self.hosting_package.id}
)
self.helper.add_input(Submit("submit", _("Create database")))
def save(self, commit=True):
"""
@ -62,8 +58,11 @@ class AddUserDatabaseForm(forms.ModelForm, PasswordModelFormMixin):
"""
data = self.cleaned_data
self.instance = UserDatabase.objects.create_userdatabase_with_user(
data['db_type'], self.hosting_package.osuser,
password=data['password1'], commit=commit)
data["db_type"],
self.hosting_package.osuser,
password=data["password1"],
commit=commit,
)
return super(AddUserDatabaseForm, self).save(commit)
@ -72,21 +71,24 @@ class ChangeDatabaseUserPasswordForm(forms.ModelForm, PasswordModelFormMixin):
This form is used to change the password of a database user.
"""
class Meta:
model = DatabaseUser
fields = []
def __init__(self, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
self.hosting_package = kwargs.pop("hostingpackage")
super(ChangeDatabaseUserPasswordForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_action = reverse(
'change_dbuser_password', kwargs={
'slug': self.instance.name,
'package': self.hosting_package.id,
})
self.helper.add_input(Submit('submit', _('Set password')))
"change_dbuser_password",
kwargs={
"slug": self.instance.name,
"package": self.hosting_package.id,
},
)
self.helper.add_input(Submit("submit", _("Set password")))
def save(self, commit=True):
self.instance.set_password(self.cleaned_data['password1'])
self.instance.set_password(self.cleaned_data["password1"])
return super(ChangeDatabaseUserPasswordForm, self).save()

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.utils.timezone
import model_utils.fields
from django.db import migrations, models
@ -8,66 +6,110 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osusers', '0004_auto_20150104_1751'),
("osusers", "0004_auto_20150104_1751"),
]
operations = [
migrations.CreateModel(
name='DatabaseUser',
name="DatabaseUser",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('name', models.CharField(
max_length=63, verbose_name='username')),
('db_type', models.PositiveSmallIntegerField(
verbose_name='database type',
choices=[(0, 'PostgreSQL'), (1, 'MySQL')])),
('osuser', models.ForeignKey(
to='osusers.User', on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
("name", models.CharField(max_length=63, verbose_name="username")),
(
"db_type",
models.PositiveSmallIntegerField(
verbose_name="database type",
choices=[(0, "PostgreSQL"), (1, "MySQL")],
),
),
(
"osuser",
models.ForeignKey(to="osusers.User", on_delete=models.CASCADE),
),
],
options={
'verbose_name': 'database user',
'verbose_name_plural': 'database users',
"verbose_name": "database user",
"verbose_name_plural": "database users",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='UserDatabase',
name="UserDatabase",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('created', model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, verbose_name='created',
editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, verbose_name='modified',
editable=False)),
('db_name', models.CharField(
max_length=63, verbose_name='database name')),
('db_user', models.ForeignKey(
verbose_name='database user', to='userdbs.DatabaseUser',
on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
verbose_name="created",
editable=False,
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
verbose_name="modified",
editable=False,
),
),
(
"db_name",
models.CharField(max_length=63, verbose_name="database name"),
),
(
"db_user",
models.ForeignKey(
verbose_name="database user",
to="userdbs.DatabaseUser",
on_delete=models.CASCADE,
),
),
],
options={
'verbose_name': 'user database',
'verbose_name_plural': 'user specific database',
"verbose_name": "user database",
"verbose_name_plural": "user specific database",
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='userdatabase',
unique_together={('db_name', 'db_user')},
name="userdatabase",
unique_together={("db_name", "db_user")},
),
migrations.AlterUniqueTogether(
name='databaseuser',
unique_together={('name', 'db_type')},
name="databaseuser",
unique_together={("name", "db_type")},
),
]

View file

@ -1,24 +1,21 @@
from __future__ import unicode_literals
from django.db import models, transaction
from django.dispatch import Signal
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from model_utils import Choices
from model_utils.models import TimeStampedModel
from osusers.models import User as OsUser
DB_TYPES = Choices(
(0, 'pgsql', _('PostgreSQL')),
(1, 'mysql', _('MySQL')),
(0, "pgsql", _("PostgreSQL")),
(1, "mysql", _("MySQL")),
)
"""
Database type choice enumeration.
"""
password_set = Signal(providing_args=['instance', 'password'])
password_set = Signal()
class DatabaseUserManager(models.Manager):
@ -40,10 +37,10 @@ class DatabaseUserManager(models.Manager):
dbuser_name_format = "{0}db{{0:02d}}".format(osuser.username)
nextname = dbuser_name_format.format(count)
for user in self.values('name').filter(
osuser=osuser, db_type=db_type
).order_by('name'):
if user['name'] == nextname:
for user in (
self.values("name").filter(osuser=osuser, db_type=db_type).order_by("name")
):
if user["name"] == nextname:
count += 1
nextname = dbuser_name_format.format(count)
else:
@ -74,33 +71,29 @@ class DatabaseUserManager(models.Manager):
"""
if username is None:
username = self._get_next_dbuser_name(osuser, db_type)
db_user = DatabaseUser(
osuser=osuser, db_type=db_type, name=username)
db_user = DatabaseUser(osuser=osuser, db_type=db_type, name=username)
if commit:
db_user.save()
return db_user
@python_2_unicode_compatible
class DatabaseUser(TimeStampedModel, models.Model):
osuser = models.ForeignKey(OsUser, on_delete=models.CASCADE)
name = models.CharField(
_('username'), max_length=63)
db_type = models.PositiveSmallIntegerField(
_('database type'), choices=DB_TYPES)
name = models.CharField(_("username"), max_length=63)
db_type = models.PositiveSmallIntegerField(_("database type"), choices=DB_TYPES)
objects = DatabaseUserManager()
class Meta:
unique_together = ['name', 'db_type']
verbose_name = _('database user')
verbose_name_plural = _('database users')
unique_together = ["name", "db_type"]
verbose_name = _("database user")
verbose_name_plural = _("database users")
def __str__(self):
return "%(name)s (%(db_type)s for %(osuser)s)" % {
'name': self.name,
'db_type': self.get_db_type_display(),
'osuser': self.osuser.username,
"name": self.name,
"db_type": self.get_db_type_display(),
"osuser": self.osuser.username,
}
@transaction.atomic
@ -110,8 +103,7 @@ class DatabaseUser(TimeStampedModel, models.Model):
:param str password: new password for the database user
"""
password_set.send(
sender=self.__class__, password=password, instance=self)
password_set.send(sender=self.__class__, password=password, instance=self)
@transaction.atomic
def delete(self, *args, **kwargs):
@ -149,10 +141,8 @@ class UserDatabaseManager(models.Manager):
db_name_format = "{0}_{{0:02d}}".format(db_user.name)
# first db is named the same as the user
nextname = db_user.name
for name in self.values('db_name').filter(db_user=db_user).order_by(
'db_name'
):
if name['db_name'] == nextname:
for name in self.values("db_name").filter(db_user=db_user).order_by("db_name"):
if name["db_name"] == nextname:
count += 1
nextname = db_name_format.format(count)
else:
@ -161,7 +151,8 @@ class UserDatabaseManager(models.Manager):
@transaction.atomic
def create_userdatabase_with_user(
self, db_type, osuser, password=None, commit=True):
self, db_type, osuser, password=None, commit=True
):
"""
Creates a new user database with a new user.
@ -175,7 +166,8 @@ class UserDatabaseManager(models.Manager):
"""
dbuser = DatabaseUser.objects.create_database_user(
osuser, db_type, password=password, commit=commit)
osuser, db_type, password=password, commit=commit
)
database = self.create_userdatabase(dbuser, commit=commit)
return database
@ -198,24 +190,22 @@ class UserDatabaseManager(models.Manager):
return database
@python_2_unicode_compatible
class UserDatabase(TimeStampedModel, models.Model):
# MySQL limits to 64, PostgreSQL to 63 characters
db_name = models.CharField(
_('database name'), max_length=63)
db_name = models.CharField(_("database name"), max_length=63)
db_user = models.ForeignKey(
DatabaseUser, verbose_name=_('database user'),
on_delete=models.CASCADE)
DatabaseUser, verbose_name=_("database user"), on_delete=models.CASCADE
)
objects = UserDatabaseManager()
class Meta:
unique_together = ['db_name', 'db_user']
verbose_name = _('user database')
verbose_name_plural = _('user specific database')
unique_together = ["db_name", "db_user"]
verbose_name = _("user database")
verbose_name_plural = _("user specific database")
def __str__(self):
return "%(db_name)s (%(db_user)s)" % {
'db_name': self.db_name,
'db_user': self.db_user,
"db_name": self.db_name,
"db_user": self.db_user,
}

View file

@ -6,20 +6,26 @@ The module starts Celery_ tasks.
.. _Celery: http://www.celeryproject.org/
"""
from __future__ import unicode_literals
import logging
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from passlib.utils import generate_password
from passlib.pwd import genword
from mysqltasks.tasks import (create_mysql_database, create_mysql_user,
delete_mysql_database, delete_mysql_user,
set_mysql_userpassword)
from pgsqltasks.tasks import (create_pgsql_database, create_pgsql_user,
delete_pgsql_database, delete_pgsql_user,
set_pgsql_userpassword)
from mysqltasks.tasks import (
create_mysql_database,
create_mysql_user,
delete_mysql_database,
delete_mysql_user,
set_mysql_userpassword,
)
from pgsqltasks.tasks import (
create_pgsql_database,
create_pgsql_user,
delete_pgsql_database,
delete_pgsql_user,
set_pgsql_userpassword,
)
from taskresults.models import TaskResult
from .models import DB_TYPES, DatabaseUser, UserDatabase, password_set
@ -64,25 +70,29 @@ def handle_dbuser_password_set(sender, instance, password, **kwargs):
"""
if instance.db_type == DB_TYPES.mysql:
taskresult = TaskResult.objects.create_task_result(
'handle_dbuser_password_set',
"handle_dbuser_password_set",
set_mysql_userpassword.s(instance.name, password),
'mysql password change')
"mysql password change",
)
_LOGGER.info(
'MySQL password change has been requested in task %s',
taskresult.task_id)
"MySQL password change has been requested in task %s", taskresult.task_id
)
elif instance.db_type == DB_TYPES.pgsql:
taskresult = TaskResult.objects.create_task_result(
'handle_dbuser_password_set',
"handle_dbuser_password_set",
set_pgsql_userpassword.s(instance.name, password),
'pgsql password change')
"pgsql password change",
)
_LOGGER.info(
'PostgreSQL password change has been requested in task %s',
taskresult.task_id)
"PostgreSQL password change has been requested in task %s",
taskresult.task_id,
)
else:
_LOGGER.warning(
'Password change has been requested for unknown database %s'
' the request has been ignored.',
instance.db_type)
"Password change has been requested for unknown database %s"
" the request has been ignored.",
instance.db_type,
)
@receiver(post_save, sender=DatabaseUser)
@ -122,32 +132,37 @@ def handle_dbuser_created(sender, instance, created, **kwargs):
"""
if created:
password = kwargs.get('password', generate_password())
password = kwargs.get("password", genword())
# TODO: send GPG encrypted mail with this information
if instance.db_type == DB_TYPES.mysql:
taskresult = TaskResult.objects.create_task_result(
'handle_dbuser_created',
"handle_dbuser_created",
create_mysql_user.s(instance.name, password),
'mysql user creation')
"mysql user creation",
)
_LOGGER.info(
'A new MySQL user %s creation has been requested in task %s',
instance.name, taskresult.task_id)
"A new MySQL user %s creation has been requested in task %s",
instance.name,
taskresult.task_id,
)
elif instance.db_type == DB_TYPES.pgsql:
taskresult = TaskResult.objects.create_task_result(
'handle_dbuser_created',
"handle_dbuser_created",
create_pgsql_user.s(instance.name, password),
'pgsql user creation')
"pgsql user creation",
)
_LOGGER.info(
'A new PostgreSQL user %s creation has been requested in task'
' %s',
instance.name, taskresult.task_id)
"A new PostgreSQL user %s creation has been requested in task" " %s",
instance.name,
taskresult.task_id,
)
else:
_LOGGER.warning(
'created DatabaseUser for unknown database type %s',
instance.db_type)
"created DatabaseUser for unknown database type %s", instance.db_type
)
_LOGGER.debug(
'database user %s has been %s',
instance, created and "created" or "updated")
"database user %s has been %s", instance, created and "created" or "updated"
)
@receiver(post_delete, sender=DatabaseUser)
@ -185,26 +200,33 @@ def handle_dbuser_deleted(sender, instance, **kwargs):
"""
if instance.db_type == DB_TYPES.mysql:
taskresult = TaskResult.objects.create_task_result(
'handle_dbuser_deleted',
"handle_dbuser_deleted",
delete_mysql_user.s(instance.name),
'mysql user deletion')
"mysql user deletion",
)
_LOGGER.info(
'MySQL user %s deletion has been requested in task %s',
instance.name, taskresult.task_id)
"MySQL user %s deletion has been requested in task %s",
instance.name,
taskresult.task_id,
)
elif instance.db_type == DB_TYPES.pgsql:
taskresult = TaskResult.objects.create_task_result(
'handle_dbuser_deleted',
"handle_dbuser_deleted",
delete_pgsql_user.s(instance.name),
'pgsql user deletion')
"pgsql user deletion",
)
_LOGGER.info(
'PostgreSQL user %s deletion has been requested in task %s',
instance.name, taskresult.task_id)
"PostgreSQL user %s deletion has been requested in task %s",
instance.name,
taskresult.task_id,
)
else:
_LOGGER.warning(
'deleted DatabaseUser %s for unknown database type %s',
instance.name, instance.db_type)
_LOGGER.debug(
'database user %s has been deleted', instance)
"deleted DatabaseUser %s for unknown database type %s",
instance.name,
instance.db_type,
)
_LOGGER.debug("database user %s has been deleted", instance)
@receiver(post_save, sender=UserDatabase)
@ -245,31 +267,36 @@ def handle_userdb_created(sender, instance, created, **kwargs):
if created:
if instance.db_user.db_type == DB_TYPES.mysql:
taskresult = TaskResult.objects.create_task_result(
'handle_userdb_created',
create_mysql_database.s(
instance.db_name, instance.db_user.name),
'mysql database creation')
"handle_userdb_created",
create_mysql_database.s(instance.db_name, instance.db_user.name),
"mysql database creation",
)
_LOGGER.info(
'The creation of a new MySQL database %s has been requested in'
' task %s',
instance.db_name, taskresult.task_id)
"The creation of a new MySQL database %s has been requested in"
" task %s",
instance.db_name,
taskresult.task_id,
)
elif instance.db_user.db_type == DB_TYPES.pgsql:
taskresult = TaskResult.objects.create_task_result(
'handle_userdb_created',
create_pgsql_database.s(
instance.db_name, instance.db_user.name),
'pgsql database creation')
"handle_userdb_created",
create_pgsql_database.s(instance.db_name, instance.db_user.name),
"pgsql database creation",
)
_LOGGER.info(
'The creation of a new PostgreSQL database %s has been'
' requested in task %s',
instance.db_name, taskresult.task_id)
"The creation of a new PostgreSQL database %s has been"
" requested in task %s",
instance.db_name,
taskresult.task_id,
)
else:
_LOGGER.warning(
'created UserDatabase for unknown database type %s',
instance.db_user.db_type)
"created UserDatabase for unknown database type %s",
instance.db_user.db_type,
)
_LOGGER.debug(
'database %s has been %s',
instance, created and "created" or "updated")
"database %s has been %s", instance, created and "created" or "updated"
)
@receiver(post_delete, sender=UserDatabase)
@ -307,25 +334,31 @@ def handle_userdb_deleted(sender, instance, **kwargs):
"""
if instance.db_user.db_type == DB_TYPES.mysql:
taskresult = TaskResult.objects.create_task_result(
'handle_userdb_deleted',
"handle_userdb_deleted",
delete_mysql_database.s(instance.db_name, instance.db_user.name),
'mysql database deletion')
"mysql database deletion",
)
_LOGGER.info(
'The deletion of MySQL database %s has been requested in task %s',
instance.db_name, taskresult.task_id)
"The deletion of MySQL database %s has been requested in task %s",
instance.db_name,
taskresult.task_id,
)
elif instance.db_user.db_type == DB_TYPES.pgsql:
taskresult = TaskResult.objects.create_task_result(
'handle_userdb_deleted',
"handle_userdb_deleted",
delete_pgsql_database.s(instance.db_name),
'pgsql database deletion')
"pgsql database deletion",
)
_LOGGER.info(
'The deletion of PostgreSQL database %s has been requested in '
' task %s',
instance.db_name, taskresult.task_id)
"The deletion of PostgreSQL database %s has been requested in " " task %s",
instance.db_name,
taskresult.task_id,
)
else:
_LOGGER.warning(
'deleted UserDatabase %s of unknown type %s',
instance.db_name, instance.db_type)
"deleted UserDatabase %s of unknown type %s",
instance.db_name,
instance.db_type,
)
pass
_LOGGER.debug(
'database %s has been deleted', instance)
_LOGGER.debug("database %s has been deleted", instance)

View file

@ -3,8 +3,6 @@ This module provides tests for the functions in
:py:mod:`userdbs.templatetags.userdb`.
"""
from __future__ import unicode_literals
from unittest import TestCase
from django.utils.translation import gettext as _
@ -20,26 +18,22 @@ class UserdbTemplateTagTests(TestCase):
"""
def test_db_type_icon_class_unknown(self):
self.assertEqual(
db_type_icon_class({'db_type': 'unknown'}),
'icon-database')
self.assertEqual(db_type_icon_class({"db_type": "unknown"}), "icon-database")
def test_db_type_icon_class_mysql(self):
self.assertEqual(
db_type_icon_class({'db_type': DB_TYPES.mysql}),
'icon-mysql')
self.assertEqual(db_type_icon_class({"db_type": DB_TYPES.mysql}), "icon-mysql")
def test_db_type_icon_class_pgsql(self):
self.assertEqual(
db_type_icon_class({'db_type': DB_TYPES.pgsql}),
'icon-postgres')
db_type_icon_class({"db_type": DB_TYPES.pgsql}), "icon-postgres"
)
def test_db_type_name_mysql(self):
self.assertEqual(
db_type_name({'db_type': DB_TYPES.mysql}),
_(DB_TYPES[DB_TYPES.mysql]))
db_type_name({"db_type": DB_TYPES.mysql}), _(DB_TYPES[DB_TYPES.mysql])
)
def test_db_type_name_pgsql(self):
self.assertEqual(
db_type_name({'db_type': DB_TYPES.pgsql}),
_(DB_TYPES[DB_TYPES.pgsql]))
db_type_name({"db_type": DB_TYPES.pgsql}), _(DB_TYPES[DB_TYPES.pgsql])
)

View file

@ -2,21 +2,24 @@
This module defines the URL patterns for user database views.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf.urls import url
from django.urls import re_path
from .views import (
AddUserDatabase,
ChangeDatabaseUserPassword,
DeleteUserDatabase,
)
from .views import AddUserDatabase, ChangeDatabaseUserPassword, DeleteUserDatabase
urlpatterns = [
url(r'^(?P<package>\d+)/create$',
AddUserDatabase.as_view(), name='add_userdatabase'),
url(r'^(?P<package>\d+)/(?P<slug>[\w0-9]+)/setpassword',
ChangeDatabaseUserPassword.as_view(), name='change_dbuser_password'),
url(r'^(?P<package>\d+)/(?P<slug>[\w0-9]+)/delete',
DeleteUserDatabase.as_view(), name='delete_userdatabase'),
re_path(
r"^(?P<package>\d+)/create$", AddUserDatabase.as_view(), name="add_userdatabase"
),
re_path(
r"^(?P<package>\d+)/(?P<slug>[\w0-9]+)/setpassword",
ChangeDatabaseUserPassword.as_view(),
name="change_dbuser_password",
),
re_path(
r"^(?P<package>\d+)/(?P<slug>[\w0-9]+)/delete",
DeleteUserDatabase.as_view(),
name="delete_userdatabase",
),
]

View file

@ -2,30 +2,19 @@
This module defines views for user database handling.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.contrib import messages
from django.core.exceptions import SuspiciousOperation
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.views.generic.edit import (
CreateView,
DeleteView,
UpdateView,
)
from django.contrib import messages
from django.utils.translation import gettext as _
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from gvawebcore.views import HostingPackageAndCustomerMixin
from .forms import (
AddUserDatabaseForm,
ChangeDatabaseUserPasswordForm,
)
from .models import (
DB_TYPES,
DatabaseUser,
UserDatabase,
)
from .forms import AddUserDatabaseForm, ChangeDatabaseUserPasswordForm
from .models import DB_TYPES, DatabaseUser, UserDatabase
class AddUserDatabase(
@ -35,9 +24,10 @@ class AddUserDatabase(
This view is used to setup new user databases.
"""
model = UserDatabase
context_object_name = 'database'
template_name_suffix = '_create'
context_object_name = "database"
template_name_suffix = "_create"
form_class = AddUserDatabaseForm
def _get_dbtypes(self, hostingpackage):
@ -45,29 +35,33 @@ class AddUserDatabase(
db_options = hostingpackage.get_databases()
for opt in db_options:
dbs_of_type = UserDatabase.objects.filter(
db_user__osuser=hostingpackage.osuser,
db_user__db_type=opt['db_type']).count()
if dbs_of_type < opt['number']:
retval.append((opt['db_type'], DB_TYPES[opt['db_type']]))
db_user__osuser=hostingpackage.osuser, db_user__db_type=opt["db_type"]
).count()
if dbs_of_type < opt["number"]:
retval.append((opt["db_type"], DB_TYPES[opt["db_type"]]))
if len(retval) < 1:
raise SuspiciousOperation(
_("The hosting package has no database products assigned."))
_("The hosting package has no database products assigned.")
)
return retval
def get_form_kwargs(self):
kwargs = super(AddUserDatabase, self).get_form_kwargs()
kwargs['hostingpackage'] = self.get_hosting_package()
kwargs['dbtypes'] = self._get_dbtypes(kwargs['hostingpackage'])
kwargs["hostingpackage"] = self.get_hosting_package()
kwargs["dbtypes"] = self._get_dbtypes(kwargs["hostingpackage"])
return kwargs
def form_valid(self, form):
userdatabase = form.save()
messages.success(
self.request,
_('Successfully create new {type} database {dbname} for user '
'{dbuser}.').format(
_(
"Successfully create new {type} database {dbname} for user " "{dbuser}."
).format(
type=userdatabase.db_user.db_type,
dbname=userdatabase.db_name, dbuser=userdatabase.db_user)
dbname=userdatabase.db_name,
dbuser=userdatabase.db_user,
),
)
return redirect(self.get_hosting_package())
@ -79,30 +73,31 @@ class ChangeDatabaseUserPassword(
This view is used to change a database user's password.
"""
model = DatabaseUser
slug_field = 'name'
context_object_name = 'dbuser'
template_name_suffix = '_setpassword'
slug_field = "name"
context_object_name = "dbuser"
template_name_suffix = "_setpassword"
form_class = ChangeDatabaseUserPasswordForm
def get_form_kwargs(self):
kwargs = super(ChangeDatabaseUserPassword, self).get_form_kwargs()
kwargs['hostingpackage'] = self.get_hosting_package()
kwargs["hostingpackage"] = self.get_hosting_package()
return kwargs
def get_context_data(self, **kwargs):
context = super(ChangeDatabaseUserPassword, self).get_context_data(
**kwargs)
context['hostingpackage'] = self.get_hosting_package()
context['customer'] = self.get_customer_object()
context = super(ChangeDatabaseUserPassword, self).get_context_data(**kwargs)
context["hostingpackage"] = self.get_hosting_package()
context["customer"] = self.get_customer_object()
return context
def form_valid(self, form):
db_user = form.save()
messages.success(
self.request,
_('Successfully changed password of database user {dbuser}.'
).format(dbuser=db_user.name)
_("Successfully changed password of database user {dbuser}.").format(
dbuser=db_user.name
),
)
return redirect(self.get_hosting_package())
@ -115,21 +110,24 @@ class DeleteUserDatabase(
no more databases assigned.
"""
model = UserDatabase
slug_field = 'db_name'
context_object_name = 'database'
slug_field = "db_name"
context_object_name = "database"
def get_context_data(self, **kwargs):
context = super(DeleteUserDatabase, self).get_context_data(**kwargs)
context.update({
'hostingpackage': self.get_hosting_package(),
'customer': self.get_customer_object(),
})
context.update(
{
"hostingpackage": self.get_hosting_package(),
"customer": self.get_customer_object(),
}
)
return context
def get_success_url(self):
messages.success(
self.request,
_('Database deleted.'),
_("Database deleted."),
)
return self.get_hosting_package().get_absolute_url()

View file

@ -2,4 +2,3 @@
This app takes care of websites.
"""
default_app_config = 'websites.apps.WebsitesAppConfig'

View file

@ -3,9 +3,8 @@ This module contains the :py:class:`django.apps.AppConfig` instance for the
:py:mod:`websites` app.
"""
from __future__ import unicode_literals
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class WebsitesAppConfig(AppConfig):
@ -13,5 +12,6 @@ class WebsitesAppConfig(AppConfig):
AppConfig for the :py:mod:`websites` app.
"""
name = 'websites'
verbose_name = _('Websites')
name = "websites"
verbose_name = _("Websites")

View file

@ -2,20 +2,17 @@
This module defines form classes for website editing.
"""
from __future__ import absolute_import, unicode_literals
from django import forms
from django.urls import reverse
from django.utils.translation import ugettext as _
from __future__ import absolute_import
from crispy_forms.bootstrap import AppendedText
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
Submit,
)
from crispy_forms.layout import Layout, Submit
from django import forms
from django.urls import reverse
from django.utils.translation import gettext as _
from domains.forms import relative_domain_validator
from .models import Website
@ -24,42 +21,40 @@ class AddWebsiteForm(forms.ModelForm):
This form is used to create new Website instances.
"""
class Meta:
model = Website
fields = ['subdomain', 'wildcard']
fields = ["subdomain", "wildcard"]
def __init__(self, *args, **kwargs):
self.hosting_package = kwargs.pop('hostingpackage')
self.hosting_domain = kwargs.pop('domain')
self.hosting_package = kwargs.pop("hostingpackage")
self.hosting_domain = kwargs.pop("domain")
super(AddWebsiteForm, self).__init__(*args, **kwargs)
self.fields['subdomain'].validators.append(relative_domain_validator)
if Website.objects.filter(
wildcard=True, domain=self.hosting_domain
).exists():
self.fields['wildcard'].widget = forms.HiddenInput()
self.fields["subdomain"].validators.append(relative_domain_validator)
if Website.objects.filter(wildcard=True, domain=self.hosting_domain).exists():
self.fields["wildcard"].widget = forms.HiddenInput()
self.helper = FormHelper()
self.helper.form_action = reverse(
'add_website', kwargs={
'package': self.hosting_package.id,
'domain': self.hosting_domain.domain,
}
"add_website",
kwargs={
"package": self.hosting_package.id,
"domain": self.hosting_domain.domain,
},
)
self.helper.layout = Layout(
AppendedText('subdomain', '.' + self.hosting_domain.domain),
'wildcard',
Submit('submit', _('Add website')),
AppendedText("subdomain", "." + self.hosting_domain.domain),
"wildcard",
Submit("submit", _("Add website")),
)
def clean_subdomain(self):
data = self.cleaned_data['subdomain']
if Website.objects.filter(
domain=self.hosting_domain, subdomain=data
).exists():
data = self.cleaned_data["subdomain"]
if Website.objects.filter(domain=self.hosting_domain, subdomain=data).exists():
raise forms.ValidationError(
_('There is already a website for this subdomain'))
relative_domain_validator(
"{0}.{1}".format(data, self.hosting_domain.domain))
_("There is already a website for this subdomain")
)
relative_domain_validator("{0}.{1}".format(data, self.hosting_domain.domain))
return data
def save(self, commit=True):

View file

@ -1,41 +1,59 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('osusers', '0004_auto_20150104_1751'),
('domains', '0002_auto_20150124_1909'),
("osusers", "0004_auto_20150104_1751"),
("domains", "0002_auto_20150124_1909"),
]
operations = [
migrations.CreateModel(
name='Website',
name="Website",
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True)),
('subdomain', models.CharField(
max_length=64, verbose_name='sub domain')),
('wildcard', models.BooleanField(
default=False, verbose_name='wildcard')),
('domain', models.ForeignKey(
verbose_name='domain', to='domains.HostingDomain',
on_delete=models.CASCADE)),
('osuser', models.ForeignKey(
verbose_name='operating system user', to='osusers.User',
on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
(
"subdomain",
models.CharField(max_length=64, verbose_name="sub domain"),
),
(
"wildcard",
models.BooleanField(default=False, verbose_name="wildcard"),
),
(
"domain",
models.ForeignKey(
verbose_name="domain",
to="domains.HostingDomain",
on_delete=models.CASCADE,
),
),
(
"osuser",
models.ForeignKey(
verbose_name="operating system user",
to="osusers.User",
on_delete=models.CASCADE,
),
),
],
options={
'verbose_name': 'website',
'verbose_name_plural': 'websites',
"verbose_name": "website",
"verbose_name_plural": "websites",
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='website',
unique_together={('domain', 'subdomain')},
name="website",
unique_together={("domain", "subdomain")},
),
]

View file

@ -2,19 +2,17 @@
This module defines the database models for website handling.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.db import models, transaction
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from domains.models import HostingDomain
from osusers.models import User as OsUser
from fileservertasks.tasks import (
create_file_website_hierarchy,
delete_file_website_hierarchy,
)
from osusers.models import User as OsUser
from webtasks.tasks import (
create_web_php_fpm_pool_config,
create_web_vhost_config,
@ -25,25 +23,23 @@ from webtasks.tasks import (
)
@python_2_unicode_compatible
class Website(models.Model):
"""
This is the model for a website.
"""
subdomain = models.CharField(
_('sub domain'), max_length=64)
subdomain = models.CharField(_("sub domain"), max_length=64)
osuser = models.ForeignKey(
OsUser, verbose_name=_('operating system user'),
on_delete=models.CASCADE)
domain = models.ForeignKey(
HostingDomain, models.CASCADE, verbose_name=_('domain'))
wildcard = models.BooleanField(_('wildcard'), default=False)
OsUser, verbose_name=_("operating system user"), on_delete=models.CASCADE
)
domain = models.ForeignKey(HostingDomain, models.CASCADE, verbose_name=_("domain"))
wildcard = models.BooleanField(_("wildcard"), default=False)
class Meta:
unique_together = [('domain', 'subdomain')]
verbose_name = _('website')
verbose_name_plural = _('websites')
unique_together = [("domain", "subdomain")]
verbose_name = _("website")
verbose_name_plural = _("websites")
def __str__(self):
return self.get_fqdn()
@ -58,12 +54,11 @@ class Website(models.Model):
if not self.pk:
fqdn = self.get_fqdn()
if not Website.objects.filter(osuser=self.osuser).exists():
create_web_php_fpm_pool_config.delay(
self.osuser.username).get()
create_file_website_hierarchy.delay(
self.osuser.username, fqdn).get()
create_web_php_fpm_pool_config.delay(self.osuser.username).get()
create_file_website_hierarchy.delay(self.osuser.username, fqdn).get()
create_web_vhost_config.delay(
self.osuser.username, fqdn, self.wildcard).get()
self.osuser.username, fqdn, self.wildcard
).get()
enable_web_vhost.delay(fqdn).get()
return super(Website, self).save(*args, **kwargs)

View file

@ -2,19 +2,21 @@
This module defines the URL patterns for website related views.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.conf.urls import url
from .views import (
AddWebsite,
DeleteWebsite,
)
from django.urls import re_path
from .views import AddWebsite, DeleteWebsite
urlpatterns = [
url(r'^(?P<package>\d+)/(?P<domain>[\w0-9.-]+)/create$',
AddWebsite.as_view(), name='add_website'),
url(r'^(?P<package>\d+)/(?P<domain>[\w0-9.-]+)/(?P<pk>\d+)/delete$',
DeleteWebsite.as_view(), name='delete_website'),
re_path(
r"^(?P<package>\d+)/(?P<domain>[\w0-9.-]+)/create$",
AddWebsite.as_view(),
name="add_website",
),
re_path(
r"^(?P<package>\d+)/(?P<domain>[\w0-9.-]+)/(?P<pk>\d+)/delete$",
DeleteWebsite.as_view(),
name="delete_website",
),
]

View file

@ -2,20 +2,17 @@
This module defines views for website handling.
"""
from __future__ import absolute_import, unicode_literals
from __future__ import absolute_import
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import ugettext as _
from django.views.generic.edit import (
CreateView,
DeleteView,
)
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import gettext as _
from django.views.generic.edit import CreateView, DeleteView
from gvacommon.viewmixins import StaffOrSelfLoginRequiredMixin
from gvawebcore.views import HostingPackageAndCustomerMixin
from domains.models import HostingDomain
from gvawebcore.views import HostingPackageAndCustomerMixin
from .forms import AddWebsiteForm
from .models import Website
@ -27,36 +24,43 @@ class AddWebsite(
This view is used to setup new websites for a customer hosting package.
"""
model = Website
context_object_name = 'website'
template_name_suffix = '_create'
context_object_name = "website"
template_name_suffix = "_create"
form_class = AddWebsiteForm
def get_form_kwargs(self):
kwargs = super(AddWebsite, self).get_form_kwargs()
kwargs.update({
'hostingpackage': self.get_hosting_package(),
'domain': get_object_or_404(
HostingDomain, domain=self.kwargs['domain']),
})
kwargs.update(
{
"hostingpackage": self.get_hosting_package(),
"domain": get_object_or_404(
HostingDomain, domain=self.kwargs["domain"]
),
}
)
return kwargs
def get_context_data(self, **kwargs):
context = super(AddWebsite, self).get_context_data(**kwargs)
context.update({
'customer': self.get_customer_object(),
'domain': get_object_or_404(
HostingDomain, domain=self.kwargs['domain'])
})
context.update(
{
"customer": self.get_customer_object(),
"domain": get_object_or_404(
HostingDomain, domain=self.kwargs["domain"]
),
}
)
return context
def form_valid(self, form):
website = form.save()
messages.success(
self.request,
_('Successfully added website {subdomain}.{domain}').format(
_("Successfully added website {subdomain}.{domain}").format(
subdomain=website.subdomain, domain=website.domain.domain
)
),
)
return redirect(self.get_hosting_package())
@ -68,15 +72,18 @@ class DeleteWebsite(
This view is used to delete websites in a customer hosting package.
"""
context_object_name = 'website'
context_object_name = "website"
model = Website
def get_context_data(self, **kwargs):
context = super(DeleteWebsite, self).get_context_data(**kwargs)
context.update({
'customer': self.get_customer_object(),
'hostingpackage': self.get_hosting_package(),
})
context.update(
{
"customer": self.get_customer_object(),
"hostingpackage": self.get_hosting_package(),
}
)
return context
def get_success_url(self):

163
poetry.lock generated
View file

@ -27,6 +27,24 @@ files = [
[package.dependencies]
vine = ">=5.0.0"
[[package]]
name = "asgiref"
version = "3.6.0"
description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"},
{file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"},
]
[package.dependencies]
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
[[package]]
name = "async-timeout"
version = "4.0.2"
@ -600,22 +618,23 @@ files = [
[[package]]
name = "django"
version = "2.2.28"
version = "3.2.18"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.6"
files = [
{file = "Django-2.2.28-py3-none-any.whl", hash = "sha256:365429d07c1336eb42ba15aa79f45e1c13a0b04d5c21569e7d596696418a6a45"},
{file = "Django-2.2.28.tar.gz", hash = "sha256:0200b657afbf1bc08003845ddda053c7641b9b24951e52acd51f6abda33a7413"},
{file = "Django-3.2.18-py3-none-any.whl", hash = "sha256:4d492d9024c7b3dfababf49f94511ab6a58e2c9c3c7207786f1ba4eb77750706"},
{file = "Django-3.2.18.tar.gz", hash = "sha256:08208dfe892eb64fff073ca743b3b952311104f939e7f6dae954fe72dcc533ba"},
]
[package.dependencies]
asgiref = ">=3.3.2,<4"
pytz = "*"
sqlparse = ">=0.2.2"
[package.extras]
argon2 = ["argon2-cffi (>=16.1.0)"]
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
@ -665,34 +684,34 @@ files = [
[[package]]
name = "django-debug-toolbar"
version = "3.2.4"
version = "3.8.1"
description = "A configurable set of panels that display various debug information about the current request/response."
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
files = [
{file = "django-debug-toolbar-3.2.4.tar.gz", hash = "sha256:644bbd5c428d3283aa9115722471769cac1bec189edf3a0c855fd8ff870375a9"},
{file = "django_debug_toolbar-3.2.4-py3-none-any.whl", hash = "sha256:6b633b6cfee24f232d73569870f19aa86c819d750e7f3e833f2344a9eb4b4409"},
{file = "django_debug_toolbar-3.8.1-py3-none-any.whl", hash = "sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478"},
{file = "django_debug_toolbar-3.8.1.tar.gz", hash = "sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27"},
]
[package.dependencies]
Django = ">=2.2"
sqlparse = ">=0.2.0"
django = ">=3.2.4"
sqlparse = ">=0.2"
[[package]]
name = "django-model-utils"
version = "4.0.0"
version = "4.3.1"
description = "Django model mixins and utilities"
category = "main"
optional = false
python-versions = "*"
python-versions = ">=3.7"
files = [
{file = "django-model-utils-4.0.0.tar.gz", hash = "sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061"},
{file = "django_model_utils-4.0.0-py2.py3-none-any.whl", hash = "sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c"},
{file = "django-model-utils-4.3.1.tar.gz", hash = "sha256:2e2e4f13e4f14613134a9777db7ad4265f59a1d8f1384107bcaa3028fe3c87c1"},
{file = "django_model_utils-4.3.1-py3-none-any.whl", hash = "sha256:8c0b0177bab909a8635b602d960daa67e80607aa5469217857271a60726d7a4b"},
]
[package.dependencies]
Django = ">=2.0.1"
Django = ">=3.2"
[[package]]
name = "docutils"
@ -1122,47 +1141,83 @@ wcwidth = "*"
[[package]]
name = "psycopg2-binary"
version = "2.8.6"
version = "2.9.5"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
python-versions = ">=3.6"
files = [
{file = "psycopg2-binary-2.8.6.tar.gz", hash = "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0"},
{file = "psycopg2_binary-2.8.6-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4"},
{file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db"},
{file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"},
{file = "psycopg2_binary-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25"},
{file = "psycopg2_binary-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c"},
{file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c"},
{file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1"},
{file = "psycopg2_binary-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2"},
{file = "psycopg2_binary-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152"},
{file = "psycopg2_binary-2.8.6-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449"},
{file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859"},
{file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550"},
{file = "psycopg2_binary-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd"},
{file = "psycopg2_binary-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71"},
{file = "psycopg2_binary-2.8.6-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4"},
{file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb"},
{file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da"},
{file = "psycopg2_binary-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2"},
{file = "psycopg2_binary-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a"},
{file = "psycopg2_binary-2.8.6-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679"},
{file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf"},
{file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b"},
{file = "psycopg2_binary-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67"},
{file = "psycopg2_binary-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66"},
{file = "psycopg2_binary-2.8.6-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f"},
{file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77"},
{file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"},
{file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"},
{file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"},
{file = "psycopg2-binary-2.9.5.tar.gz", hash = "sha256:33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3520d7af1ebc838cc6084a3281145d5cd5bdd43fdef139e6db5af01b92596cb7"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cbc554ba47ecca8cd3396ddaca85e1ecfe3e48dd57dc5e415e59551affe568e"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:5d28ecdf191db558d0c07d0f16524ee9d67896edf2b7990eea800abeb23ebd61"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:05b3d479425e047c848b9782cd7aac9c6727ce23181eb9647baf64ffdfc3da41"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e491e6489a6cb1d079df8eaa15957c277fdedb102b6a68cfbf40c4994412fd0"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:9e32cedc389bcb76d9f24ea8a012b3cb8385ee362ea437e1d012ffaed106c17d"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46850a640df62ae940e34a163f72e26aca1f88e2da79148e1862faaac985c302"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-win32.whl", hash = "sha256:3d790f84201c3698d1bfb404c917f36e40531577a6dda02e45ba29b64d539867"},
{file = "psycopg2_binary-2.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:1764546ffeaed4f9428707be61d68972eb5ede81239b46a45843e0071104d0dd"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-macosx_10_9_universal2.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:426c2ae999135d64e6a18849a7d1ad0e1bd007277e4a8f4752eaa40a96b550ff"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cf1d44e710ca3a9ce952bda2855830fe9f9017ed6259e01fcd71ea6287565f5"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024030b13bdcbd53d8a93891a2cf07719715724fc9fee40243f3bd78b4264b8f"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_24_ppc64le.whl", hash = "sha256:af0516e1711995cb08dc19bbd05bec7dbdebf4185f68870595156718d237df3e"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e72c91bda9880f097c8aa3601a2c0de6c708763ba8128006151f496ca9065935"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e67b3c26e9b6d37b370c83aa790bbc121775c57bfb096c2e77eacca25fd0233b"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5fc447058d083b8c6ac076fc26b446d44f0145308465d745fba93a28c14c9e32"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d892bfa1d023c3781a3cab8dd5af76b626c483484d782e8bd047c180db590e4c"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-win32.whl", hash = "sha256:2abccab84d057723d2ca8f99ff7b619285d40da6814d50366f61f0fc385c3903"},
{file = "psycopg2_binary-2.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:bef7e3f9dc6f0c13afdd671008534be5744e0e682fb851584c8c3a025ec09720"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:6e63814ec71db9bdb42905c925639f319c80e7909fb76c3b84edc79dadef8d60"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:212757ffcecb3e1a5338d4e6761bf9c04f750e7d027117e74aa3cd8a75bb6fbd"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f8a9bcab7b6db2e3dbf65b214dfc795b4c6b3bb3af922901b6a67f7cb47d5f8"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:56b2957a145f816726b109ee3d4e6822c23f919a7d91af5a94593723ed667835"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:f95b8aca2703d6a30249f83f4fe6a9abf2e627aa892a5caaab2267d56be7ab69"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:70831e03bd53702c941da1a1ad36c17d825a24fbb26857b40913d58df82ec18b"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:dbc332beaf8492b5731229a881807cd7b91b50dbbbaf7fe2faf46942eda64a24"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:2d964eb24c8b021623df1c93c626671420c6efadbdb8655cb2bd5e0c6fa422ba"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:95076399ec3b27a8f7fa1cc9a83417b1c920d55cf7a97f718a94efbb96c7f503"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-win32.whl", hash = "sha256:3fc33295cfccad697a97a76dec3f1e94ad848b7b163c3228c1636977966b51e2"},
{file = "psycopg2_binary-2.9.5-cp36-cp36m-win_amd64.whl", hash = "sha256:02551647542f2bf89073d129c73c05a25c372fc0a49aa50e0de65c3c143d8bd0"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:63e318dbe52709ed10d516a356f22a635e07a2e34c68145484ed96a19b0c4c68"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7e518a0911c50f60313cb9e74a169a65b5d293770db4770ebf004245f24b5c5"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9d38a4656e4e715d637abdf7296e98d6267df0cc0a8e9a016f8ba07e4aa3eeb"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:68d81a2fe184030aa0c5c11e518292e15d342a667184d91e30644c9d533e53e1"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:7ee3095d02d6f38bd7d9a5358fcc9ea78fcdb7176921528dd709cc63f40184f5"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:46512486be6fbceef51d7660dec017394ba3e170299d1dc30928cbedebbf103a"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b911dfb727e247340d36ae20c4b9259e4a64013ab9888ccb3cbba69b77fd9636"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:422e3d43b47ac20141bc84b3d342eead8d8099a62881a501e97d15f6addabfe9"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c5682a45df7d9642eff590abc73157c887a68f016df0a8ad722dcc0f888f56d7"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-win32.whl", hash = "sha256:b8104f709590fff72af801e916817560dbe1698028cd0afe5a52d75ceb1fce5f"},
{file = "psycopg2_binary-2.9.5-cp37-cp37m-win_amd64.whl", hash = "sha256:7b3751857da3e224f5629400736a7b11e940b5da5f95fa631d86219a1beaafec"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:043a9fd45a03858ff72364b4b75090679bd875ee44df9c0613dc862ca6b98460"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9ffdc51001136b699f9563b1c74cc1f8c07f66ef7219beb6417a4c8aaa896c28"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c15ba5982c177bc4b23a7940c7e4394197e2d6a424a2d282e7c236b66da6d896"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc85b3777068ed30aff8242be2813038a929f2084f69e43ef869daddae50f6ee"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:215d6bf7e66732a514f47614f828d8c0aaac9a648c46a831955cb103473c7147"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:7d07f552d1e412f4b4e64ce386d4c777a41da3b33f7098b6219012ba534fb2c2"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a0adef094c49f242122bb145c3c8af442070dc0e4312db17e49058c1702606d4"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:00475004e5ed3e3bf5e056d66e5dcdf41a0dc62efcd57997acd9135c40a08a50"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7d88db096fa19d94f433420eaaf9f3c45382da2dd014b93e4bf3215639047c16"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:902844f9c4fb19b17dfa84d9e2ca053d4a4ba265723d62ea5c9c26b38e0aa1e6"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-win32.whl", hash = "sha256:4e7904d1920c0c89105c0517dc7e3f5c20fb4e56ba9cdef13048db76947f1d79"},
{file = "psycopg2_binary-2.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:a36a0e791805aa136e9cbd0ffa040d09adec8610453ee8a753f23481a0057af5"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9c38d3869238e9d3409239bc05bc27d6b7c99c2a460ea337d2814b35fb4fea1b"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5c6527c8efa5226a9e787507652dd5ba97b62d29b53c371a85cd13f957fe4d42"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e59137cdb970249ae60be2a49774c6dfb015bd0403f05af1fe61862e9626642d"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:d4c7b3a31502184e856df1f7bbb2c3735a05a8ce0ade34c5277e1577738a5c91"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:b9a794cef1d9c1772b94a72eec6da144c18e18041d294a9ab47669bc77a80c1d"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c5e65c6ac0ae4bf5bef1667029f81010b6017795dcb817ba5c7b8a8d61fab76f"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:74eddec4537ab1f701a1647214734bc52cee2794df748f6ae5908e00771f180a"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:01ad49d68dd8c5362e4bfb4158f2896dc6e0c02e87b8a3770fc003459f1a4425"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-win32.whl", hash = "sha256:937880290775033a743f4836aa253087b85e62784b63fd099ee725d567a48aa1"},
{file = "psycopg2_binary-2.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1"},
]
[[package]]
@ -1735,4 +1790,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata]
lock-version = "2.0"
python-versions = "^3.7"
content-hash = "45edcf8e776501a35fd1621b430bb434206d3429455c8637a71ea652445bda6a"
content-hash = "37ecfcb75a397eb82b3afbdf901a356d735fb1a941e7067c4b74fb2fe0227c84"

View file

@ -8,14 +8,14 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.7"
django = "<3"
psycopg2-binary = "<2.9"
django = "<4"
psycopg2-binary = "^2.9"
celery = "^5.2.7"
django-allauth = "^0.52.0"
django-braces = "^1.15.0"
django-crispy-forms = "<2"
django-debug-toolbar = "<3.8"
django-model-utils = "<4.1"
django-debug-toolbar = "^3.8"
django-model-utils = "^4.1"
gvacommon = {version = "^0.5.0", source = "gnuviech"}
passlib = "^1.7.4"
redis = "^4.5.1"
@ -38,6 +38,7 @@ url = "https://pypi.gnuviech-server.de/simple"
default = false
secondary = false
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"