Merge pull request #1034 from modoboa/feature/domain_quota

Domain quota support
This commit is contained in:
Antoine Nguyen
2017-02-16 08:52:38 +01:00
committed by GitHub
27 changed files with 426 additions and 162 deletions
+22 -4
View File
@@ -71,6 +71,15 @@ class AdminParametersForm(param_forms.AdminParametersForm):
)
)
default_mailbox_quota = forms.IntegerField(
label=ugettext_lazy("Default mailbox quota"),
initial=0,
help_text=ugettext_lazy(
"Default mailbox quota (in MB) applied to freshly created domains "
"with no value specified. A value of 0 means no quota."
)
)
auto_account_removal = YesNoField(
label=ugettext_lazy("Automatic account removal"),
initial=False,
@@ -96,7 +105,8 @@ class AdminParametersForm(param_forms.AdminParametersForm):
def __init__(self, *args, **kwargs):
super(AdminParametersForm, self).__init__(*args, **kwargs)
self.field_widths = {
"default_domain_quota": 2
"default_domain_quota": 2,
"default_mailbox_quota": 2
}
hide_fields = False
dpath = None
@@ -127,11 +137,19 @@ class AdminParametersForm(param_forms.AdminParametersForm):
def clean_default_domain_quota(self):
"""Ensure quota is a positive integer."""
if self.cleaned_data['default_domain_quota'] < 0:
if self.cleaned_data["default_domain_quota"] < 0:
raise forms.ValidationError(
ugettext_lazy('Must be a positive integer')
ugettext_lazy("Must be a positive integer")
)
return self.cleaned_data['default_domain_quota']
return self.cleaned_data["default_domain_quota"]
def clean_default_mailbox_quota(self):
"""Ensure quota is a positive integer."""
if self.cleaned_data["default_mailbox_quota"] < 0:
raise forms.ValidationError(
ugettext_lazy("Must be a positive integer")
)
return self.cleaned_data["default_mailbox_quota"]
def load_admin_settings():
+4 -3
View File
@@ -16,7 +16,8 @@ class DomainFactory(PermissionFactory):
django_get_or_create = ("name", )
type = "domain"
quota = 10
quota = 0
default_mailbox_quota = 10
enabled = True
@@ -64,7 +65,7 @@ def populate_database():
2 domains, 1 domain admin, 2 users
"""
dom = DomainFactory(name="test.com")
dom = DomainFactory(name="test.com", quota=50)
admin = UserFactory(
username="admin@test.com", groups=('DomainAdmins', ),
password='{PLAIN}toto'
@@ -96,7 +97,7 @@ def populate_database():
dom.add_admin(admin)
dom2 = DomainFactory.create(name='test2.com', quota=0)
dom2 = DomainFactory.create(name='test2.com', default_mailbox_quota=0)
admin = UserFactory.create(
username='admin@test2.com', groups=('DomainAdmins',),
password='{PLAIN}toto'
+2 -2
View File
@@ -187,7 +187,6 @@ class AccountFormGeneral(forms.ModelForm):
class AccountFormMail(forms.Form, DynamicForm):
"""Form to handle mail part."""
email = lib_fields.UTF8EmailField(
@@ -280,8 +279,9 @@ class AccountFormMail(forms.Form, DynamicForm):
Check if quota is >= 0 only when the domain value is not used.
"""
cleaned_data = super(AccountFormMail, self).clean()
use_default_domain_quota = cleaned_data["quota_act"]
condition = (
not cleaned_data["quota_act"] and
not use_default_domain_quota and
cleaned_data["quota"] is not None and
cleaned_data["quota"] < 0)
if condition:
+8 -22
View File
@@ -33,14 +33,6 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
type = forms.ChoiceField(
label=ugettext_lazy("Type"),
)
quota = forms.IntegerField(
label=ugettext_lazy("Quota"),
required=False,
help_text=ugettext_lazy(
"Default quota in MB applied to mailboxes. Leave empty to use the "
"default value."
)
)
aliases = DomainNameField(
label=ugettext_lazy("Alias(es)"),
required=False,
@@ -53,14 +45,18 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
class Meta:
model = Domain
fields = (
"name", "type", "quota", "aliases", "enabled", "enable_dns_checks")
"name", "type", "quota", "default_mailbox_quota", "aliases",
"enabled", "enable_dns_checks")
def __init__(self, *args, **kwargs):
self.oldname = None
if "instance" in kwargs:
self.oldname = kwargs["instance"].name
super(DomainFormGeneral, self).__init__(*args, **kwargs)
params = dict(param_tools.get_global_parameters("admin"))
self.fields["quota"].initial = params["default_domain_quota"]
self.fields["default_mailbox_quota"].initial = (
params["default_mailbox_quota"])
extra_domain_types = reduce(
lambda a, b: a + b,
[result[1] for result in signals.extra_domain_types.send(
@@ -68,7 +64,8 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
)
self.fields["type"].choices = DOMAIN_TYPES + extra_domain_types
self.field_widths = {
"quota": 3
"quota": 3,
"default_mailbox_quota": 3
}
if len(args) and isinstance(args[0], QueryDict):
@@ -79,14 +76,6 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
name = "aliases_%d" % (pos + 1)
self._create_field(forms.CharField, name, dalias.name, 3)
def clean_quota(self):
"""Check quota value."""
if self.cleaned_data["quota"] is None:
return param_tools.get_global_parameter("default_domain_quota")
if self.cleaned_data["quota"] < 0:
raise forms.ValidationError(_("Must be a positive integer"))
return self.cleaned_data["quota"]
def clean(self):
"""Custom fields validation.
@@ -164,7 +153,6 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
class DomainFormOptions(forms.Form):
"""A form containing options for domain creation."""
create_dom_admin = YesNoField(
@@ -271,7 +259,6 @@ class DomainFormOptions(forms.Form):
class DomainForm(TabForms):
"""Domain edition form."""
def __init__(self, request, *args, **kwargs):
@@ -341,7 +328,6 @@ class DomainForm(TabForms):
class DomainWizard(WizardForm):
"""Domain creation wizard."""
def __init__(self, request):
+3 -2
View File
@@ -27,7 +27,7 @@ def update_domain_mxs_and_mailboxes(sender, instance, **kwargs):
if kwargs.get("created"):
return
instance.mailbox_set.filter(use_domain_quota=True).update(
quota=instance.quota)
quota=instance.default_mailbox_quota)
if instance.old_mail_homes is None:
return
qset = (
@@ -155,7 +155,8 @@ def account_auto_created(sender, user, **kwargs):
domname, [(models.DomainAlias, _("domain alias"))])
if label is not None:
return
domain = models.Domain(name=domname, enabled=True, quota=0)
domain = models.Domain(
name=domname, enabled=True, default_mailbox_quota=0)
domain.save(creator=sadmins[0])
for su in sadmins[1:]:
permissions.grant_access_to_object(su, domain)
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-15 08:48
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('admin', '0008_domain_enable_dns_checks'),
]
operations = [
migrations.RenameField(
model_name='domain',
old_name='quota',
new_name='default_mailbox_quota',
),
]
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-15 12:28
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('admin', '0009_auto_20170215_0948'),
]
operations = [
migrations.AddField(
model_name='domain',
name='quota',
field=models.PositiveIntegerField(default=0, help_text='Quota in MB shared between mailboxes. A value of 0 means no quota.'),
),
migrations.AlterField(
model_name='domain',
name='default_mailbox_quota',
field=models.PositiveIntegerField(default=0, help_text='Default quota in MB applied to mailboxes. A value of 0 means no quota.', verbose_name='Default mailbox quota'),
),
migrations.AlterField(
model_name='mailbox',
name='quota',
field=models.PositiveIntegerField(default=0),
),
]
+50 -6
View File
@@ -42,7 +42,21 @@ class Domain(AdminObject):
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
help_text=ugettext_lazy("The domain name"))
quota = models.IntegerField()
quota = models.PositiveIntegerField(
default=0,
help_text=ugettext_lazy(
"Quota in MB shared between mailboxes. A value of 0 means "
"no quota."
)
)
default_mailbox_quota = models.PositiveIntegerField(
verbose_name=ugettext_lazy("Default mailbox quota"),
default=0,
help_text=ugettext_lazy(
"Default quota in MB applied to mailboxes. A value of 0 means "
"no quota."
)
)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
help_text=ugettext_lazy("Check to activate this domain"),
@@ -143,6 +157,23 @@ class Domain(AdminObject):
else:
return "success"
@cached_property
def quota_usage(self):
"""Return current quota usage."""
if not self.quota:
return 0
if not self.mailbox_set.exists():
return 0
return self.mailbox_set.aggregate(
total=models.Sum("quota"))["total"]
@cached_property
def quota_usage_in_percent(self):
"""Return quota usage in percent."""
if not self.quota_usage:
return 0
return int(self.quota_usage / float(self.quota) * 100)
def add_admin(self, account):
"""Add a new administrator to this domain.
@@ -210,12 +241,12 @@ class Domain(AdminObject):
The expected fields order is the following::
"domain", name, quota, enabled
"domain", name, quota, default_mailbox_quota, enabled
:param ``core.User`` user: user creating the domain
:param str row: a list containing domain's definition
"""
if len(row) < 4:
if len(row) < 5:
raise BadRequest(_("Invalid line"))
self.name = row[1].strip()
if Domain.objects.filter(name=self.name).exists():
@@ -224,12 +255,25 @@ class Domain(AdminObject):
self.quota = int(row[2].strip())
except ValueError:
raise BadRequest(
_("Invalid quota value for domain '%s'") % self.name)
self.enabled = (row[3].strip() in ["True", "1", "yes", "y"])
_("Invalid quota value for domain '{}'")
.format(self.name)
)
try:
self.default_mailbox_quota = int(row[3].strip())
except ValueError:
raise BadRequest(
_("Invalid default mailbox quota value for domain '{}'")
.format(self.name)
)
self.enabled = (row[4].strip() in ["True", "1", "yes", "y"])
self.save(creator=user)
def to_csv(self, csvwriter):
csvwriter.writerow(["domain", self.name, self.quota, self.enabled])
"""Export domain and domain aliases to CSV format."""
csvwriter.writerow([
"domain", self.name, self.quota, self.default_mailbox_quota,
self.enabled
])
for dalias in self.domainalias_set.all():
dalias.to_csv(csvwriter)
+13 -10
View File
@@ -81,7 +81,7 @@ class Mailbox(AdminObject):
help_text=ugettext_lazy(
"Mailbox address (without the @domain.tld part)")
)
quota = models.PositiveIntegerField()
quota = models.PositiveIntegerField(default=0)
use_domain_quota = models.BooleanField(default=False)
domain = models.ForeignKey(Domain)
user = models.OneToOneField(User)
@@ -218,7 +218,7 @@ class Mailbox(AdminObject):
MailboxOperation.objects.create(type="delete", argument=self.mail_home)
def set_quota(self, value=None, override_rules=False):
"""Set or update quota's value for this mailbox.
"""Set or update quota value for this mailbox.
A value equal to 0 means the mailbox won't have any quota. The
following cases allow people to define such behaviour:
@@ -228,20 +228,23 @@ class Mailbox(AdminObject):
:param integer value: the quota's value
:param bool override_rules: allow to override defined quota rules
"""
old_quota = self.quota
if value is None:
if self.use_domain_quota:
self.quota = self.domain.quota
self.quota = self.domain.default_mailbox_quota
else:
self.quota = 0
elif int(value) > self.domain.quota and not override_rules:
raise lib_exceptions.BadRequest(
_("Quota is greater than the allowed domain's limit (%dM)") %
self.domain.quota
)
else:
self.quota = value
if not self.quota and self.domain.quota and not override_rules:
raise lib_exceptions.BadRequest(_("A quota is required"))
if self.quota == 0:
if self.domain.quota and not override_rules:
raise lib_exceptions.BadRequest(_("A quota is required"))
elif self.domain.quota:
quota_usage = self.domain.quota_usage
if old_quota:
quota_usage -= old_quota
if quota_usage + self.quota > self.domain.quota:
raise lib_exceptions.BadRequest(_("Domain quota exceeded"))
def get_quota(self):
"""Get quota limit.
+5 -4
View File
@@ -23,7 +23,9 @@ class DomainSerializer(serializers.ModelSerializer):
class Meta:
model = models.Domain
fields = ("pk", "name", "quota", "enabled", "type", )
fields = (
"pk", "name", "quota", "default_mailbox_quota", "enabled", "type",
)
def create(self, validated_data):
"""Set permissions."""
@@ -236,11 +238,10 @@ class WritableAccountSerializer(AccountSerializer):
except lib_exceptions.ModoboaException as inst:
raise serializers.ValidationError({
"domain": unicode(inst)})
quota = data.pop("quota", None)
mb = admin_models.Mailbox(
user=account, address=address, domain=domain, **data)
mb.set_quota(
data.get("quota"), creator.has_perm("admin.add_domain")
)
mb.set_quota(quota, creator.has_perm("admin.add_domain"))
mb.save(creator=creator)
account.email = full_address
return mb
@@ -20,21 +20,50 @@
<div class="panel-heading">
<h3 class="panel-title">{% trans "Summary" %}</h3>
</div>
<div class="panel-body">
<table class="table">
{% if enable_mx_checks or enable_dnsbl_checks %}
<div class="row">
<span class="title">{% trans "DNS status" %}</span>
{% include "admin/_domain_dnschecks_status.html" %}
</div>
<tr>
<th>{% trans "DNS status" %}</th>
<td>
{% include "admin/_domain_dnschecks_status.html" %}
</td>
</tr>
{% endif %}
<div class="row">
<span class="title">{% trans "Creation date" %}</span><span>{{ domain.creation }}</span>
</div>
<div class="row"><span class="title">{% trans "Last modification date" %}</span><span>{{ domain.last_modification }}</span></div>
<div class="row"><span class="title">{% trans "Domain aliases" %}</span><span>{{ domain.domainalias_count }}</span></div>
<div class="row"><span class="title"><a href="{% url 'admin:identity_list' %}#list/?searchquery=@{{ object.name }}">{% trans "Mailboxes" %}</a></span><span>{{ domain.mailbox_count }}</span></div>
<div class="row"><span class="title"><a href="{% url 'admin:identity_list' %}#list/?searchquery=@{{ object.name }}">{% trans "Mailbox aliases" %}</a></span><span>{{ domain.mbalias_count }}</span></div>
</div>
<tr>
<th width="40%">{% trans "Creation date" %}</th>
<td>{{ domain.creation }}</td>
</tr>
<tr>
<th>{% trans "Last modification date" %}</th>
<td>{{ domain.last_modification }}</td>
</tr>
<tr>
<th>{% trans "Domain aliases" %}</th>
<td>{{ domain.domainalias_count }}</td>
</tr>
<tr>
<th><a href="{% url 'admin:identity_list' %}#list/?searchquery=@{{ object.name }}">{% trans "Mailboxes" %}</a></th>
<td>{{ domain.mailbox_count }}</td>
</tr>
<tr>
<th><a href="{% url 'admin:identity_list' %}#list/?searchquery=@{{ object.name }}">{% trans "Mailbox aliases" %}</a></th>
<td>{{ domain.mbalias_count }}</td>
</tr>
<tr>
<th>{% trans "Quota" %}</a></th>
<td>{{ domain.quota }} {% trans "MB" %}</td>
</tr>
{% if domain.quota %}
<tr>
<th>{% trans "Usage" %}</a></th>
<td>{% include "common/progressbar.html" with id="quotabar" value=domain.quota_usage_in_percent %}</td>
</tr>
{% endif %}
<tr>
<th>{% trans "Default mailbox quota" %}</a></th>
<td>{{ domain.default_mailbox_quota }} {% trans "MB" %}</td>
</tr>
</table>
</div>
{% if object.domainalias_set.exists %}
@@ -58,24 +87,26 @@
<div class="col-xs-12 col-sm-6">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">{% trans "Administrators" %}</h3></div>
<div class="panel-body">
<table id="domadmins" class="table table-condensed">
<table id="domadmins" class="table">
<thead>
<tr><th>{% trans "Username" %}</th><th>{% trans "Name" %}</th><th></th></tr>
{% for da in domain.admins %}
<tr>
<td>{{ da.username }}</td>
<td>{{ da.fullname }}</td>
<td name="actions">
{% if perms.admin.change_domain %}
<a name="removeperm" href="{% url 'admin:permission_remove' %}?domid={{ domain.id }}&daid={{ da.id }}" title="{% trans 'Remove this permission' %}"><span class="fa fa-trash"></span></a>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="3">{% trans "No domain administrator defined yet." %}</td></tr>
{% endfor %}
</table>
</div>
</thead>
<tbody>
{% for da in domain.admins %}
<tr>
<td>{{ da.username }}</td>
<td>{{ da.fullname }}</td>
<td name="actions">
{% if perms.admin.change_domain %}
<a name="removeperm" href="{% url 'admin:permission_remove' %}?domid={{ domain.id }}&daid={{ da.id }}" title="{% trans 'Remove this permission' %}"><span class="fa fa-trash"></span></a>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="3">{% trans "No domain administrator defined yet." %}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% for template in templates.right %}
@@ -2,6 +2,7 @@
{% render_field form.name %}
{% render_field form.type %}
{% render_field form.quota appended_text=_("MB") %}
{% render_field form.default_mailbox_quota appended_text=_("MB") %}
{% render_fields_group form "aliases" %}
{% render_field form.enabled %}
{% render_field form.enable_dns_checks %}
@@ -6,7 +6,7 @@
{% blocktrans trimmed %}
Provide a CSV file where lines respect one of the following formats:
<ul>
<li><em>domain; name; quota; enabled</em></li>
<li><em>domain; name; quota; default mailbox quota; enabled</em></li>
<li><em>domainalias; name; targeted domain; enabled</em></li>
<li><em>relaydomain; name; target host; target port; service; enabled; verify recipients</em></li>
</ul>
+26 -5
View File
@@ -165,17 +165,34 @@ class AccountTestCase(ModoTestCase):
self._set_quota("user@test.com", 0)
def test_set_nul_quota_as_domainadmin(self):
"""Check cases where a domain admin set unlimited quota."""
self.client.logout()
self.assertTrue(
self.client.login(username="admin@test.com", password="toto")
)
# Fails because domain has a quota
self._set_quota("user@test.com", 0, 400)
self.client.logout()
self.assertTrue(
self.client.login(username="admin@test2.com", password="toto")
)
# Ok because domain has no quota
self._set_quota("user@test2.com", 0)
def test_domain_quota(self):
"""Check domain quota."""
dom = models.Domain.objects.get(name="test.com")
dom.quota = 100
dom.save(update_fields=["quota"])
# 2 x 10MB
self.assertEqual(dom.quota_usage, 20)
self._set_quota("user@test.com", 80)
del dom.quota_usage
# 10 + 80 < 100 => ok
self.assertEqual(dom.quota_usage, 90)
# 30 + 80 > 100 => failure
self._set_quota("admin@test.com", 30, 400)
def test_master_user(self):
"""Validate the master user mode."""
values = {
@@ -250,11 +267,15 @@ class PermissionsTestCase(ModoTestCase):
cls.reseller = core_factories.UserFactory(username="reseller")
cls.reseller.role = "Resellers"
cls.user = User.objects.get(username="user@test.com")
cls.values = dict(
username=cls.user.username, role="DomainAdmins",
is_active=cls.user.is_active, email="user@test.com",
quota_act=True
)
def setUp(self):
"""Initiate test context."""
super(PermissionsTestCase, self).setUp()
self.values = {
"username": self.user.username, "role": "DomainAdmins",
"is_active": self.user.is_active, "email": "user@test.com",
"quota_act": True
}
def tearDown(self):
self.client.logout()
+13 -8
View File
@@ -43,18 +43,20 @@ class DomainAPITestCase(ModoAPITestCase):
def test_create_domain(self):
"""Check domain creation."""
url = reverse("external_api:domain-list")
response = self.client.post(url, {"name": "test3.com", "quota": 10})
response = self.client.post(
url, {"name": "test3.com", "quota": 0, "default_mailbox_quota": 10}
)
self.assertEqual(response.status_code, 201)
self.assertTrue(
models.Domain.objects.filter(name="test3.com").exists())
response = self.client.post(url, {})
self.assertEqual(response.status_code, 400)
self.assertIn("name", response.data)
self.assertIn("quota", response.data)
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
response = self.client.post(url, {"name": "test4.com", "quota": 10})
response = self.client.post(
url, {"name": "test4.com", "default_mailbox_quota": 10})
self.assertEqual(response.status_code, 403)
def test_update_domain(self):
@@ -64,15 +66,17 @@ class DomainAPITestCase(ModoAPITestCase):
domain__name="test.com", address="user").update(
use_domain_quota=True)
url = reverse("external_api:domain-detail", args=[domain.pk])
response = self.client.put(url, {"name": "test.com", "quota": 1000})
response = self.client.put(
url, {"name": "test.com", "default_mailbox_quota": 1000})
self.assertEqual(response.status_code, 200)
domain.refresh_from_db()
self.assertEqual(domain.quota, 1000)
self.assertEqual(domain.default_mailbox_quota, 1000)
mb = models.Mailbox.objects.get(
domain__name="test.com", address="user")
self.assertEqual(mb.quota, 1000)
response = self.client.put(url, {"name": "test42.com", "quota": 1000})
response = self.client.put(
url, {"name": "test42.com", "default_mailbox_quota": 1000})
self.assertEqual(response.status_code, 200)
self.assertTrue(
models.Mailbox.objects.filter(
@@ -267,7 +271,8 @@ class AccountAPITestCase(ModoAPITestCase):
user = core_models.User.objects.filter(pk=account["pk"]).first()
self.assertIsNot(user, None)
self.assertIsNot(user.mailbox, None)
self.assertEqual(user.mailbox.quota, user.mailbox.domain.quota)
self.assertEqual(
user.mailbox.quota, user.mailbox.domain.default_mailbox_quota)
def test_create_existing_account(self):
"""Check if unicity is respected."""
@@ -297,7 +302,7 @@ class AccountAPITestCase(ModoAPITestCase):
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
data = copy.deepcopy(self.ACCOUNT_DATA)
data["mailbox"]["quota"] = 20
data["mailbox"]["quota"] = 1000
url = reverse("external_api:account-list")
response = self.client.post(url, data, format="json")
self.assertEqual(response.status_code, 400)
+28 -34
View File
@@ -25,35 +25,37 @@ class DomainTestCase(ModoTestCase):
def test_create(self):
"""Test the creation of a domain."""
values = {
"name": "pouet.com", "quota": 100, "create_dom_admin": False,
"type": "domain", "stepid": "step3"
"name": "pouet.com", "quota": 1000, "default_mailbox_quota": 100,
"create_dom_admin": False, "type": "domain", "stepid": "step3"
}
self.ajax_post(reverse("admin:domain_add"), values)
dom = Domain.objects.get(name="pouet.com")
self.assertEqual(dom.name, "pouet.com")
self.assertEqual(dom.quota, 100)
self.assertEqual(dom.quota, 1000)
self.assertEqual(dom.default_mailbox_quota, 100)
self.assertEqual(dom.enabled, False)
self.assertFalse(dom.admins)
def test_create_utf8(self):
"""Test the creation of a domain with non-ASCII characters."""
values = {
"name": "pouét.com", "quota": 100, "create_dom_admin": False,
"type": "domain", "stepid": "step3"
"name": "pouét.com", "quota": 1000, "default_mailbox_quota": 100,
"create_dom_admin": False, "type": "domain", "stepid": "step3"
}
self.ajax_post(reverse("admin:domain_add"), values)
dom = Domain.objects.get(name=u"pouét.com")
self.assertEqual(dom.name, u"pouét.com")
self.assertEqual(dom.quota, 100)
self.assertEqual(dom.quota, 1000)
self.assertEqual(dom.default_mailbox_quota, 100)
self.assertEqual(dom.enabled, False)
self.assertFalse(dom.admins)
def test_create_with_template(self):
"""Test the creation of a domain with a template."""
values = {
"name": "pouet.com", "quota": 100, "create_dom_admin": True,
"dom_admin_username": "toto", "create_aliases": True,
"type": "domain", "stepid": 'step3'
"name": "pouet.com", "quota": 1000, "default_mailbox_quota": 100,
"create_dom_admin": True, "dom_admin_username": "toto",
"create_aliases": True, "type": "domain", "stepid": 'step3'
}
self.ajax_post(reverse("admin:domain_add"), values)
dom = Domain.objects.get(name="pouet.com")
@@ -67,9 +69,9 @@ class DomainTestCase(ModoTestCase):
self.assertTrue(da.can_access(al))
values = {
"name": "pouet2.com", "quota": 100, "create_dom_admin": True,
"dom_admin_username": "postmaster", "create_aliases": True,
"type": "domain", "stepid": "step3"
"name": "pouet2.com", "quota": 1000, "default_mailbox_quota": 100,
"create_dom_admin": True, "dom_admin_username": "postmaster",
"create_aliases": True, "type": "domain", "stepid": "step3"
}
self.ajax_post(reverse("admin:domain_add"), values)
self.assertTrue(
@@ -81,9 +83,9 @@ class DomainTestCase(ModoTestCase):
def test_create_with_template_and_empty_quota(self):
"""Test the creation of a domain with a template and no quota"""
values = {
"name": "pouet.com", "quota": 0, "create_dom_admin": True,
"dom_admin_username": "toto", "create_aliases": True,
"type": "domain", "stepid": 'step3'
"name": "pouet.com", "quota": 0, "default_mailbox_quota": 0,
"create_dom_admin": True, "dom_admin_username": "toto",
"create_aliases": True, "type": "domain", "stepid": 'step3'
}
self.ajax_post(
reverse("admin:domain_add"),
@@ -104,9 +106,9 @@ class DomainTestCase(ModoTestCase):
password = "Toto1000"
self.set_global_parameter("default_password", password, app="core")
values = {
"name": "pouet.com", "quota": 0, "create_dom_admin": True,
"dom_admin_username": "toto", "create_aliases": True,
"type": "domain", "stepid": 'step3'
"name": "pouet.com", "quota": 0, "default_mailbox_quota": 0,
"create_dom_admin": True, "dom_admin_username": "toto",
"create_aliases": True, "type": "domain", "stepid": 'step3'
}
self.ajax_post(
reverse("admin:domain_add"),
@@ -125,20 +127,12 @@ class DomainTestCase(ModoTestCase):
self.client.login(username="toto@pouet.com", password=password))
def test_create_using_default_quota(self):
self.set_global_parameter("default_domain_quota", 50)
values = {
"name": "pouet.com", "create_dom_admin": True,
"dom_admin_username": "toto", "create_aliases": True,
"type": "domain", "stepid": 'step3'
}
self.ajax_post(
reverse("admin:domain_add"),
values
)
dom = Domain.objects.get(name="pouet.com")
self.assertEqual(dom.quota, 50)
da = User.objects.get(username="toto@pouet.com")
self.assertEqual(da.mailbox.quota, 50)
"""Check that default value is used for creation."""
self.set_global_parameter("default_domain_quota", 500)
self.set_global_parameter("default_mailbox_quota", 50)
response = self.client.get(reverse("admin:domain_add"))
self.assertContains(response, "value=\"500\"")
self.assertContains(response, "value=\"50\"")
def test_modify(self):
"""Test the modification of a domain
@@ -146,8 +140,8 @@ class DomainTestCase(ModoTestCase):
Rename 'test.com' domain to 'pouet.com'
"""
values = {
"name": "pouet.com", "quota": 100, "type": "domain",
"enabled": True
"name": "pouet.com", "quota": 1000, "default_mailbox_quota": 100,
"type": "domain", "enabled": True
}
dom = Domain.objects.get(name="test.com")
self.ajax_post(
+6 -5
View File
@@ -38,11 +38,12 @@ class DomainAliasTestCase(ModoTestCase):
def test_form(self):
dom = Domain.objects.get(name="test.com")
values = dict(
name=dom.name, quota=dom.quota, enabled=dom.enabled,
aliases="domalias.net", aliases_1="domaliasé.com",
type="domain"
)
values = {
"name": dom.name, "quota": dom.quota,
"default_mailbox_quota": dom.default_mailbox_quota,
"enabled": dom.enabled, "aliases": "domalias.net",
"aliases_1": "domaliasé.com", "type": "domain"
}
self.ajax_post(
reverse("admin:domain_change",
args=[dom.id]),
+28 -3
View File
@@ -5,10 +5,10 @@ from django.core.urlresolvers import reverse
from modoboa.lib.tests import ModoTestCase
from .. import factories
from .. import models
class ExportTestCase(ModoTestCase):
"""Test case for export operations."""
@classmethod
@@ -17,6 +17,14 @@ class ExportTestCase(ModoTestCase):
super(ExportTestCase, cls).setUpTestData()
factories.populate_database()
def __export_domains(self, domfilter=""):
self.client.get(
"{}?domfilter={}".format(
reverse("admin:_domain_list"), domfilter))
return self.client.post(
reverse("admin:domain_export"), {"filename": "test.csv"}
)
def __export_identities(self, idtfilter="", grpfilter=""):
self.client.get(
reverse("admin:_identity_list") +
@@ -28,12 +36,29 @@ class ExportTestCase(ModoTestCase):
)
def assertListEqual(self, list1, list2):
list2 = list2.split('\r\n')
for entry in list1.split('\r\n'):
list1 = list1.split("\r\n")
list2 = list2.split("\r\n")
self.assertEqual(len(list1), len(list2))
for entry in list1:
if not entry:
continue
self.assertIn(entry, list2)
def test_export_domains(self):
"""Check domain export."""
dom = models.Domain.objects.get(name="test.com")
factories.DomainAliasFactory(name="alias.test", target=dom)
response = self.__export_domains()
expected_response = [
"domain;test.com;50;10;True",
"domainalias;alias.test;test.com;True",
"domain;test2.com;0;0;True",
]
self.assertListEqual(
"\r\n".join(expected_response),
response.content.strip()
)
def test_export_identities(self):
response = self.__export_identities()
self.assertListEqual(
+26 -9
View File
@@ -25,8 +25,8 @@ class ImportTestCase(ModoTestCase):
self.assertEqual(response.status_code, 200)
self.assertIn("Provide a CSV", response.content)
f = ContentFile(b"""domain; domain1.com; 100; True
domain; domain2.com; 200; False
f = ContentFile(b"""domain; domain1.com; 1000; 100; True
domain; domain2.com; 1000; 200; False
domainalias; domalias1.com; domain1.com; True
""", name="domains.csv")
self.client.post(
@@ -36,7 +36,8 @@ domainalias; domalias1.com; domain1.com; True
)
admin = User.objects.get(username="admin")
dom = Domain.objects.get(name="domain1.com")
self.assertEqual(dom.quota, 100)
self.assertEqual(dom.quota, 1000)
self.assertEqual(dom.default_mailbox_quota, 100)
self.assertTrue(dom.enabled)
self.assertTrue(admin.is_owner(dom))
domalias = DomainAlias.objects.get(name="domalias1.com")
@@ -44,12 +45,28 @@ domainalias; domalias1.com; domain1.com; True
self.assertTrue(dom.enabled)
self.assertTrue(admin.is_owner(domalias))
dom = Domain.objects.get(name="domain2.com")
self.assertEqual(dom.quota, 200)
self.assertEqual(dom.default_mailbox_quota, 200)
self.assertFalse(dom.enabled)
self.assertTrue(admin.is_owner(dom))
def test_domain_import_bad_syntax(self):
"""Check errors handling."""
url = reverse("admin:domain_import")
f = ContentFile("domain; domain1.com; 100; True",
name="domains.csv")
response = self.client.post(url, {"sourcefile": f})
self.assertContains(response, "Invalid line")
f = ContentFile("domain; domain1.com; XX; 100; True",
name="domains.csv")
response = self.client.post(url, {"sourcefile": f})
self.assertContains(response, "Invalid quota value")
f = ContentFile("domain; domain1.com; 100; XX; True",
name="domains.csv")
response = self.client.post(url, {"sourcefile": f})
self.assertContains(response, "Invalid default mailbox quota")
def test_import_domains_with_conflict(self):
f = ContentFile(b"""domain;test.alias;10;True
f = ContentFile(b"""domain;test.alias;100;10;True
domainalias;test.alias;test.com;True
""", name="domains.csv")
resp = self.client.post(
@@ -160,7 +177,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; ; t
self.client.logout()
self.client.login(username="admin@test.com", password="toto")
f = ContentFile(b"""
domain; domain2.com; 200; False
domain; domain2.com; 1000; 200; False
""", name="identities.csv")
resp = self.client.post(
reverse("admin:identity_import"),
@@ -181,13 +198,13 @@ domainalias; domalias1.com; test.com; True
self.client.logout()
self.client.login(username="admin@test.com", password="toto")
f = ContentFile(b"""
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 20
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 40
""", name="identities.csv")
resp = self.client.post(
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
self.assertIn('Quota is greater than the allowed', resp.content)
self.assertIn("Domain quota exceeded", resp.content)
def test_import_missing_quota(self):
f = ContentFile(b"""
@@ -200,7 +217,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com
account = User.objects.get(username="user1@test.com")
self.assertEqual(
account.mailbox.quota,
account.mailbox.domain.quota
account.mailbox.domain.default_mailbox_quota
)
def test_import_duplicate(self):
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-15 09:34
from __future__ import unicode_literals
from django.db import migrations
def rename_settings(apps, schema_editor):
"""Rename settings."""
LocalConfig = apps.get_model("core", "LocalConfig")
lc = LocalConfig.objects.first()
condition = (
lc and
"admin" in lc._parameters and
"default_domain_quota" in lc._parameters["admin"]
)
if condition:
lc._parameters["admin"]["default_mailbox_quota"] = (
lc._parameters["admin"]["default_domain_quota"])
del lc._parameters["admin"]["default_domain_quota"]
lc.save(update_fields=["_parameters"])
class Migration(migrations.Migration):
dependencies = [
('core', '0011_auto_20161104_2217'),
]
operations = [
migrations.RunPython(rename_settings)
]
+1
View File
@@ -115,6 +115,7 @@ class SettingsTestCase(ModoTestCase):
"admin-auto_create_domain_and_mailbox": "True",
"admin-enable_dnsbl_checks": "True",
"admin-default_domain_quota": "0",
"admin-default_mailbox_quota": "0",
"core-ldap_server_address": "localhost",
"core-authentication_type": "local",
"core-ldap_admin_groups": "",
-2
View File
@@ -21,7 +21,6 @@ from modoboa.lib.web_utils import render_to_json_response
class WizardStep(object):
"""A wizard step."""
def __init__(self, uid, formclass, title, formtpl=None, new_args=None):
@@ -66,7 +65,6 @@ class WizardStep(object):
class WizardForm(object):
"""Custom wizard."""
__metaclass__ = abc.ABCMeta
@@ -34,6 +34,7 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase):
domain = Domain.objects.get(name="test.com")
values = {
"name": domain.name, "quota": domain.quota,
"default_mailbox_quota": domain.default_mailbox_quota,
"enabled": domain.enabled, "type": "domain",
"mailboxes_limit": 3, "mailbox_aliases_limit": 3,
"domain_aliases_limit": 3, "domain_admins_limit": 3
@@ -62,6 +63,7 @@ class DomainLimitsTestCase(lib_tests.ModoTestCase):
self.assertFalse(limit.is_exceeded())
values = {
"name": domain.name, "quota": domain.quota,
"default_mailbox_quota": domain.default_mailbox_quota,
"enabled": domain.enabled, "type": "domain",
"mailboxes_limit": 2, "mailbox_aliases_limit": 2,
"domain_aliases_limit": 2, "domain_admins_limit": 2,
+3 -3
View File
@@ -147,8 +147,8 @@ class UserLimitImportTestCase(LimitImportTestCase):
username=self.reseller.username, password="toto")
limit = self.reseller.userobjectlimit_set.get(name="domains")
self.assertFalse(limit.is_exceeded())
f = ContentFile(b"""domain; domain1.com; 100; True
domain; domain2.com; 200; False
f = ContentFile(b"""domain; domain1.com; 1000; 100; True
domain; domain2.com; 1000; 200; False
""", name="domains.csv")
self.client.post(
reverse("admin:domain_import"), {
@@ -157,7 +157,7 @@ domain; domain2.com; 200; False
)
self.assertTrue(limit.is_exceeded())
f = ContentFile(b"""domain; domain3.com; 100; True
f = ContentFile(b"""domain; domain3.com; 1000; 100; True
""", name="domains.csv")
response = self.client.post(
reverse("admin:domain_import"), {
+7 -5
View File
@@ -78,7 +78,8 @@ class ResourceTestCase(lib_tests.ModoTestCase):
def _create_domain(self, name, status=200, withtpl=False):
values = {
"name": name, "quota": 100, "create_dom_admin": False,
"name": name, "quota": 100, "default_mailbox_quota": 10,
"create_dom_admin": False,
"create_aliases": False, "stepid": "step3", "type": "domain"
}
if withtpl:
@@ -95,16 +96,17 @@ class ResourceTestCase(lib_tests.ModoTestCase):
def _domain_alias_operation(self, optype, domain, name, status=200):
dom = Domain.objects.get(name=domain)
values = {
'name': dom.name, 'quota': dom.quota, 'enabled': dom.enabled,
"type": "domain"
"name": dom.name, "quota": dom.quota, "enabled": dom.enabled,
"type": "domain",
"default_mailbox_quota": dom.default_mailbox_quota,
}
aliases = [alias.name for alias in dom.domainalias_set.all()]
if optype == 'add':
if optype == "add":
aliases.append(name)
else:
aliases.remove(name)
for cpt, alias in enumerate(aliases):
fname = 'aliases' if not cpt else 'aliases_%d' % cpt
fname = "aliases" if not cpt else "aliases_%d" % cpt
values[fname] = alias
self.ajax_post(
reverse("admin:domain_change", args=[dom.id]),
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-15 08:48
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('relaydomains', '0005_auto_20161105_1426'),
]
operations = [
migrations.AlterField(
model_name='relaydomain',
name='last_modification',
field=models.DateTimeField(auto_now=True, default=django.utils.timezone.now),
preserve_default=False,
),
]
+12 -4
View File
@@ -23,7 +23,8 @@ class Operations(object):
values = {
"name": name, "create_dom_admin": False, "type": "relaydomain",
"target_host": "external.host.tld", "target_port": 25,
"service": srv.id, "enabled": True, "stepid": "step3"
"service": srv.id, "enabled": True, "stepid": "step3",
"quota": 0, "default_mailbox_quota": 0
}
return self.ajax_post(
reverse("admin:domain_add"),
@@ -35,7 +36,8 @@ class Operations(object):
values = {
"name": rdom.domain.name, "target_host": rdom.target_host,
"target_port": rdom.target_port,
"type": "relaydomain", "service": rdom.service.id
"type": "relaydomain", "service": rdom.service.id,
"quota": 0, "default_mailbox_quota": 0
}
aliases = [alias.name for alias in rdom.domain.domainalias_set.all()]
if optype == 'add':
@@ -144,7 +146,8 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
values = {
"name": "relaydomain.org", "target_host": self.rdom.target_host,
"target_port": 4040, "type": "relaydomain", "enabled": True,
"service": self.rdom.service.id
"service": self.rdom.service.id,
"quota": 0, "default_mailbox_quota": 0
}
self.ajax_post(
reverse("admin:domain_change", args=[self.rdom.domain.id]),
@@ -159,6 +162,8 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
values = {
"name": "relaydomain.tld",
"type": "domain",
"quota": 0,
"default_mailbox_quota": 0,
"enabled": True,
"target_host": self.rdom.target_host,
"target_port": self.rdom.target_port,
@@ -176,6 +181,8 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
"name": "relaydomain.tld",
"type": "relaydomain",
"enabled": True,
"quota": 0,
"default_mailbox_quota": 0
}
self.ajax_post(
reverse("admin:domain_change", args=[domain_pk]), values)
@@ -195,7 +202,8 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
"name": "relaydomain.org", "target_host": self.rdom.target_host,
"target_port": self.rdom.target_port,
"type": "relaydomain", "service": self.rdom.service.id,
"aliases": "relaydomainalias.net"
"aliases": "relaydomainalias.net",
"quota": 0, "default_mailbox_quota": 0
}
self.ajax_post(
reverse('admin:domain_change',