mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
@@ -10,3 +10,5 @@ DEV
|
||||
.idea
|
||||
.ropeproject
|
||||
modoboa.iml
|
||||
.tox
|
||||
modoboa_test
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ language: python
|
||||
cache: pip
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.4"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
@@ -42,7 +43,7 @@ before_script:
|
||||
script:
|
||||
- python ./tests.py
|
||||
- cd test_project
|
||||
- coverage run --source ../modoboa manage.py test modoboa.core modoboa.admin.tests modoboa.limits modoboa.relaydomains
|
||||
- coverage run --source ../modoboa manage.py test modoboa.core modoboa.lib modoboa.admin modoboa.limits modoboa.relaydomains
|
||||
|
||||
after_success:
|
||||
- codecov
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from modoboa.core.commands import handle_command_line
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
python-ldap
|
||||
django-auth-ldap
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
default_app_config = "modoboa.admin.apps.AdminConfig"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Admin API."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import http
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"""Modoboa admin settings."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from modoboa.lib.form_utils import YesNoField, SeparatorField
|
||||
from modoboa.lib.sysutils import exec_cmd
|
||||
@@ -127,6 +130,7 @@ class AdminParametersForm(param_forms.AdminParametersForm):
|
||||
except OSError:
|
||||
hide_fields = True
|
||||
else:
|
||||
version = force_text(version)
|
||||
if code or not version.strip().startswith("2"):
|
||||
hide_fields = True
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""AppConfig for admin."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
from .app_settings import load_admin_settings
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Admin constants."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
DNSBL_PROVIDERS = [
|
||||
"aspews.ext.sorbs.net",
|
||||
"b.barracudacentral.org",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""
|
||||
Fixtures factories.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import factory
|
||||
|
||||
from modoboa.core.factories import PermissionFactory, UserFactory
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .domain import (
|
||||
DomainFormGeneral, DomainFormOptions, DomainForm, DomainWizard
|
||||
)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""Forms related to accounts management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
from functools import reduce
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Forms related to aliases management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django import forms
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
"""Forms related to domains management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import QueryDict
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from modoboa.core import signals as core_signals
|
||||
from modoboa.core.models import User
|
||||
@@ -94,7 +99,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
if label is not None:
|
||||
self.add_error(
|
||||
"name", _("A %s with this name already exists")
|
||||
% unicode(label)
|
||||
% force_text(label)
|
||||
)
|
||||
condition = (
|
||||
self.cleaned_data["quota"] != 0 and
|
||||
@@ -116,7 +121,8 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
if default_mailbox_quota == 0:
|
||||
self.add_error("default_mailbox_quota", msg)
|
||||
self.aliases = []
|
||||
for k in cleaned_data.keys():
|
||||
copied_data = cleaned_data.copy()
|
||||
for k in copied_data.keys():
|
||||
if not k.startswith("aliases"):
|
||||
continue
|
||||
if cleaned_data[k] == "":
|
||||
@@ -133,7 +139,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
|
||||
if label is not None:
|
||||
self.add_error(
|
||||
k, _("A %s with this name already exists")
|
||||
% unicode(label)
|
||||
% force_text(label)
|
||||
)
|
||||
else:
|
||||
self.aliases.append(cleaned_data[k])
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
@@ -23,12 +25,12 @@ class ExportDataForm(forms.Form):
|
||||
def clean_sepchar(self):
|
||||
if self.cleaned_data["sepchar"] == "":
|
||||
return ";"
|
||||
return str(self.cleaned_data["sepchar"])
|
||||
return self.cleaned_data["sepchar"]
|
||||
|
||||
def clean_filename(self):
|
||||
if self.cleaned_data["filename"] == "":
|
||||
return self.fields["filename"].initial
|
||||
return str(self.cleaned_data["filename"])
|
||||
return self.cleaned_data["filename"]
|
||||
|
||||
|
||||
class ExportDomainsForm(ExportDataForm):
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Forms related to forwards management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Forms related to import operations."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
@@ -27,7 +29,7 @@ class ImportDataForm(forms.Form):
|
||||
def clean_sepchar(self):
|
||||
if self.cleaned_data["sepchar"] == "":
|
||||
return ";"
|
||||
return str(self.cleaned_data["sepchar"])
|
||||
return self.cleaned_data["sepchar"]
|
||||
|
||||
|
||||
class ImportIdentitiesForm(ImportDataForm):
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Django signal handlers for admin."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import signals
|
||||
from django.dispatch import receiver
|
||||
@@ -174,7 +176,7 @@ def account_auto_created(sender, user, **kwargs):
|
||||
@receiver(core_signals.account_exported)
|
||||
def export_admin_domains(sender, user, **kwargs):
|
||||
"""Export administered domains too."""
|
||||
result = [user.mailbox.quota] if hasattr(user, "mailbox") else [""]
|
||||
result = [user.mailbox.quota] if hasattr(user, "mailbox") else [u""]
|
||||
if user.role != "DomainAdmins":
|
||||
return result
|
||||
return result + [
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
"""Internal library for admin."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import wraps
|
||||
from itertools import chain
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""A management command to apply mailbox operations."""
|
||||
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
@@ -79,7 +81,7 @@ class Command(BaseCommand):
|
||||
if not code:
|
||||
return False
|
||||
with open(path, 'w') as fp:
|
||||
print >> fp, os.getpid()
|
||||
print(os.getpid(), file=fp)
|
||||
return True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Modoboa main management command."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from subcommand.base import SubcommandCommand
|
||||
|
||||
from .subcommands._mx import CheckMXRecords
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Django management command to export admin objects."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import csv
|
||||
import sys
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Django management command to import admin objects."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import csv
|
||||
import os
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Management command to check defined domains."""
|
||||
|
||||
from datetime import datetime
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
from datetime import timedelta
|
||||
import ipaddress
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Management command to check and fix known problems."""
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.encoding import smart_str
|
||||
|
||||
from modoboa.lib.permissions import grant_access_to_object
|
||||
from modoboa.lib.permissions import get_object_owner
|
||||
@@ -110,7 +113,7 @@ class Repair(BaseCommand):
|
||||
# Load known problems from extensions.
|
||||
for ext in settings.MODOBOA_APPS:
|
||||
try:
|
||||
__import__(ext, locals(), globals(), ["known_problems"])
|
||||
__import__(ext, locals(), globals(), [smart_str("known_problems")])
|
||||
except ImportError:
|
||||
pass
|
||||
for func in known_problems:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Admin models."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .base import AdminObject
|
||||
from .domain import Domain, MXRecord, DNSBLResult
|
||||
from .domain_alias import DomainAlias
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
"""Models related to aliases management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import random
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible, smart_text
|
||||
from django.utils.encoding import (
|
||||
python_2_unicode_compatible, smart_text, force_str, force_bytes
|
||||
)
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
from reversion import revisions as reversion
|
||||
@@ -61,8 +65,8 @@ class Alias(AdminObject):
|
||||
def generate_random_address(cls):
|
||||
"""Generate a random address (local part)."""
|
||||
m = hashlib.md5()
|
||||
for x in random.sample(xrange(10000000), 60):
|
||||
m.update(str(x))
|
||||
for x in random.sample(range(10000000), 60):
|
||||
m.update(force_bytes(x))
|
||||
return m.hexdigest()[:20]
|
||||
|
||||
@property
|
||||
@@ -109,7 +113,6 @@ class Alias(AdminObject):
|
||||
is defined on the associated domain
|
||||
|
||||
"""
|
||||
to_create = []
|
||||
for address in set(address_list):
|
||||
if not address:
|
||||
continue
|
||||
@@ -193,10 +196,11 @@ class Alias(AdminObject):
|
||||
self.post_create(user)
|
||||
|
||||
def to_csv(self, csvwriter):
|
||||
row = ["alias", self.address.encode("utf-8"), self.enabled]
|
||||
row = ["alias", force_str(self.address), self.enabled]
|
||||
row += self.recipients
|
||||
csvwriter.writerow(row)
|
||||
|
||||
|
||||
reversion.register(Alias)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Base admin models."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
"""Models related to domains management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from functools import reduce
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.manager import Manager
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import python_2_unicode_compatible, smart_text
|
||||
from django.utils.encoding import (
|
||||
python_2_unicode_compatible, smart_text, force_text
|
||||
)
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
@@ -278,7 +283,10 @@ class Domain(AdminObject):
|
||||
def to_csv(self, csvwriter):
|
||||
"""Export domain and domain aliases to CSV format."""
|
||||
csvwriter.writerow([
|
||||
"domain", self.name, self.quota, self.default_mailbox_quota,
|
||||
"domain",
|
||||
force_text(self.name),
|
||||
self.quota,
|
||||
self.default_mailbox_quota,
|
||||
self.enabled
|
||||
])
|
||||
for dalias in self.domainalias_set.all():
|
||||
@@ -293,6 +301,7 @@ class Domain(AdminObject):
|
||||
for domalias in self.domainalias_set.all():
|
||||
domalias.post_create(creator)
|
||||
|
||||
|
||||
reversion.register(Domain)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Models related to domain aliases management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible, smart_text
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Models related to mailboxes management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import pwd
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Map file definitions for postfix."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class DomainsMap(object):
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"""Admin serializers."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from rest_framework import serializers
|
||||
from passwords import validators
|
||||
@@ -75,7 +78,7 @@ class DomainAliasSerializer(serializers.ModelSerializer):
|
||||
object_type="domain_aliases")
|
||||
except lib_exceptions.ModoboaException as inst:
|
||||
raise serializers.ValidationError({
|
||||
"domain": unicode(inst)})
|
||||
"domain": force_text(inst)})
|
||||
domain_alias.save(creator=creator)
|
||||
return domain_alias
|
||||
|
||||
@@ -252,7 +255,7 @@ class WritableAccountSerializer(AccountSerializer):
|
||||
object_type="mailboxes")
|
||||
except lib_exceptions.ModoboaException as inst:
|
||||
raise serializers.ValidationError({
|
||||
"domain": unicode(inst)})
|
||||
"domain": force_text(inst)})
|
||||
quota = data.pop("quota", None)
|
||||
mb = admin_models.Mailbox(
|
||||
user=account, address=address, domain=domain, **data)
|
||||
@@ -345,7 +348,7 @@ class AliasSerializer(serializers.ModelSerializer):
|
||||
sender=self.__class__, context=self.domain,
|
||||
object_type="mailbox_aliases")
|
||||
except lib_exceptions.ModoboaException as inst:
|
||||
raise serializers.ValidationError(unicode(inst))
|
||||
raise serializers.ValidationError(force_text(inst))
|
||||
recipients = validated_data.pop("recipients", None)
|
||||
alias = admin_models.Alias(domain=self.domain, **validated_data)
|
||||
alias.save(creator=creator)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Modoboa admin signals."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.dispatch
|
||||
|
||||
check_extra_account_form = django.dispatch.Signal(
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""Admin extension tags."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from unittest import skipIf
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -241,12 +243,12 @@ class AccountTestCase(ModoTestCase):
|
||||
url = reverse("admin:account_detail", args=[account.pk])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Summary", response.content)
|
||||
self.assertIn("Administered domains", response.content)
|
||||
self.assertNotIn("Resources usage", response.content)
|
||||
self.assertIn("Summary", response.content.decode())
|
||||
self.assertIn("Administered domains", response.content.decode())
|
||||
self.assertNotIn("Resources usage", response.content.decode())
|
||||
self.set_global_parameter("enable_admin_limits", True, app="limits")
|
||||
response = self.client.get(url)
|
||||
self.assertIn("Resources usage", response.content)
|
||||
self.assertIn("Resources usage", response.content.decode())
|
||||
|
||||
|
||||
@skipIf(NO_LDAP, "No ldap module installed")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
"""Admin test cases."""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -263,8 +265,8 @@ class AliasTestCase(ModoTestCase):
|
||||
url = reverse("admin:alias_detail", args=[account.pk])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Summary", response.content)
|
||||
self.assertIn("Recipients", response.content)
|
||||
self.assertIn("Summary", response.content.decode())
|
||||
self.assertIn("Recipients", response.content.decode())
|
||||
|
||||
def test_domainadmin_restrictions(self):
|
||||
"""Check that restrictions are applied."""
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# coding: utf-8
|
||||
"""Admin API related tests."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
||||
@@ -146,7 +148,7 @@ class DomainAliasAPITestCase(ModoAPITestCase):
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
dalias = json.loads(response.content)
|
||||
dalias = response.json()
|
||||
dalias = models.DomainAlias.objects.filter(
|
||||
pk=dalias["pk"]).first()
|
||||
self.assertEqual(dalias.target, target)
|
||||
@@ -215,17 +217,17 @@ class AccountAPITestCase(ModoAPITestCase):
|
||||
url = reverse("api:account-list")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = json.loads(response.content)
|
||||
response = response.json()
|
||||
self.assertEqual(len(response), 5)
|
||||
|
||||
response = self.client.get("{}?domain=test.com".format(url))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = json.loads(response.content)
|
||||
response = response.json()
|
||||
self.assertEqual(len(response), 2)
|
||||
|
||||
response = self.client.get("{}?domain=pouet.com".format(url))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = json.loads(response.content)
|
||||
response = response.json()
|
||||
self.assertEqual(len(response), 0)
|
||||
|
||||
def test_create_account(self):
|
||||
@@ -234,7 +236,7 @@ class AccountAPITestCase(ModoAPITestCase):
|
||||
response = self.client.post(url, self.ACCOUNT_DATA, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
account = json.loads(response.content)
|
||||
account = response.json()
|
||||
user = core_models.User.objects.filter(pk=account["pk"]).first()
|
||||
self.assertIsNot(user, None)
|
||||
self.assertIsNot(user.mailbox, None)
|
||||
@@ -258,7 +260,7 @@ class AccountAPITestCase(ModoAPITestCase):
|
||||
self.assertEqual(response.status_code, 201)
|
||||
domain = admin_models.Domain.objects.get(name="test.com")
|
||||
admin = core_models.User.objects.get(
|
||||
pk=json.loads(response.content)["pk"])
|
||||
pk=response.json()["pk"])
|
||||
self.assertIn(admin, domain.admins)
|
||||
|
||||
data["username"] = "domain_admin"
|
||||
@@ -273,7 +275,7 @@ class AccountAPITestCase(ModoAPITestCase):
|
||||
url = reverse("api:account-list")
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
account = json.loads(response.content)
|
||||
account = response.json()
|
||||
user = core_models.User.objects.filter(pk=account["pk"]).first()
|
||||
self.assertIsNot(user, None)
|
||||
self.assertIsNot(user.mailbox, None)
|
||||
@@ -300,7 +302,7 @@ class AccountAPITestCase(ModoAPITestCase):
|
||||
url = reverse("api:account-list")
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
errors = json.loads(response.content)
|
||||
errors = response.json()
|
||||
self.assertIn("password", errors)
|
||||
|
||||
def test_create_account_as_domadmin(self):
|
||||
@@ -318,7 +320,7 @@ class AccountAPITestCase(ModoAPITestCase):
|
||||
url = reverse("api:account-list")
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
errors = json.loads(response.content)
|
||||
errors = response.json()
|
||||
self.assertIn("domain", errors)
|
||||
|
||||
def test_create_account_bad_master_user(self):
|
||||
@@ -328,7 +330,7 @@ class AccountAPITestCase(ModoAPITestCase):
|
||||
url = reverse("api:account-list")
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
errors = json.loads(response.content)
|
||||
errors = response.json()
|
||||
self.assertIn("master_user", errors)
|
||||
|
||||
def test_update_account(self):
|
||||
@@ -430,12 +432,12 @@ class AccountAPITestCase(ModoAPITestCase):
|
||||
response = self.client.get(
|
||||
"{}?email={}".format(url, "user@test.com"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = json.loads(response.content)
|
||||
content = response.json()
|
||||
self.assertTrue(content["exists"])
|
||||
response = self.client.get(
|
||||
"{}?email={}".format(url, "pipo@test.com"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = json.loads(response.content)
|
||||
content = response.json()
|
||||
self.assertFalse(content["exists"])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
@@ -485,12 +487,12 @@ class AliasAPITestCase(ModoAPITestCase):
|
||||
url = reverse("api:alias-list")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = json.loads(response.content)
|
||||
response = response.json()
|
||||
self.assertEqual(len(response), 3)
|
||||
|
||||
response = self.client.get("{}?domain=test.com".format(url))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = json.loads(response.content)
|
||||
response = response.json()
|
||||
self.assertEqual(len(response), 3)
|
||||
|
||||
def test_get_alias(self):
|
||||
@@ -499,7 +501,7 @@ class AliasAPITestCase(ModoAPITestCase):
|
||||
url = reverse("api:alias-detail", args=[al.pk])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = json.loads(response.content)
|
||||
response = response.json()
|
||||
self.assertEqual(response["recipients"], ["user@test.com"])
|
||||
|
||||
def test_create_alias(self):
|
||||
@@ -508,7 +510,7 @@ class AliasAPITestCase(ModoAPITestCase):
|
||||
response = self.client.post(url, self.ALIAS_DATA, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
alias = json.loads(response.content)
|
||||
alias = json.loads(response.content.decode('utf-8'))
|
||||
alias = models.Alias.objects.filter(pk=alias["pk"]).first()
|
||||
domadmin = core_models.User.objects.get(username="admin@test.com")
|
||||
self.assertTrue(domadmin.can_access(alias))
|
||||
@@ -539,7 +541,7 @@ class AliasAPITestCase(ModoAPITestCase):
|
||||
data["address"] = "alias_fromapi@test2.com"
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
errors = json.loads(response.content)
|
||||
errors = response.json()
|
||||
self.assertIn("address", errors)
|
||||
|
||||
def test_update_alias(self):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""Domain related test cases."""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
"""Domain related test cases."""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
@@ -211,7 +211,7 @@ class DomainTestCase(ModoTestCase):
|
||||
"""Test the 'domain_flat_list' view."""
|
||||
response = self.client.get(reverse("admin:domain_flat_list"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = json.loads(response.content)
|
||||
content = response.json()
|
||||
self.assertIn("test.com", content)
|
||||
self.assertIn("test2.com", content)
|
||||
|
||||
@@ -222,23 +222,24 @@ class DomainTestCase(ModoTestCase):
|
||||
url = reverse("admin:domain_detail", args=[domain.pk])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Summary", response.content)
|
||||
self.assertIn("Administrators", response.content)
|
||||
self.assertNotIn("Resources usage", response.content)
|
||||
self.assertIn("Summary", response.content.decode())
|
||||
self.assertIn("Administrators", response.content.decode())
|
||||
self.assertNotIn("Resources usage", response.content.decode())
|
||||
self.set_global_parameter("enable_domain_limits", True, app="limits")
|
||||
response = self.client.get(url)
|
||||
self.assertIn("Resources usage", response.content)
|
||||
self.assertIn("Resources usage", response.content.decode())
|
||||
|
||||
def test_statitics_widget(self):
|
||||
"""Test statistics display in dashboard."""
|
||||
url = reverse("core:dashboard")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Global statistics", response.content)
|
||||
self.assertIn("Global statistics", response.content.decode('utf-8'))
|
||||
|
||||
self.client.force_login(
|
||||
User.objects.get(username="admin@test.com"))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn("Global statistics", response.content)
|
||||
self.assertIn("Per-domain statistics", response.content)
|
||||
content = response.content.decode('utf-8')
|
||||
self.assertNotIn("Global statistics", content)
|
||||
self.assertIn("Per-domain statistics", content)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""Export related test cases."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
|
||||
@@ -36,8 +39,8 @@ class ExportTestCase(ModoTestCase):
|
||||
)
|
||||
|
||||
def assertListEqual(self, list1, list2):
|
||||
list1 = list1.split("\r\n")
|
||||
list2 = list2.split("\r\n")
|
||||
list1 = force_text(list1).split("\r\n")
|
||||
list2 = force_text(list2).split("\r\n")
|
||||
self.assertEqual(len(list1), len(list2))
|
||||
for entry in list1:
|
||||
if not entry:
|
||||
@@ -83,7 +86,7 @@ class ExportTestCase(ModoTestCase):
|
||||
response = self.__export_identities(
|
||||
idtfilter="account", grpfilter="SuperAdmins"
|
||||
)
|
||||
elements = response.content.strip().split(";")
|
||||
elements = response.content.decode().strip().split(";")
|
||||
self.assertEqual(len(elements), 9)
|
||||
elements[2] = ""
|
||||
self.assertEqual(
|
||||
@@ -102,6 +105,6 @@ class ExportTestCase(ModoTestCase):
|
||||
def test_export_aliases(self):
|
||||
response = self.__export_identities(idtfilter="alias")
|
||||
self.assertEqual(
|
||||
response.content.strip(),
|
||||
response.content.decode().strip(),
|
||||
"alias;alias@test.com;True;user@test.com\r\nalias;forward@test.com;True;user@external.com\r\nalias;postmaster@test.com;True;test@truc.fr;toto@titi.com"
|
||||
)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
@@ -23,7 +26,7 @@ class ImportTestCase(ModoTestCase):
|
||||
def test_domains_import(self):
|
||||
response = self.client.get(reverse("admin:domain_import"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Provide a CSV", response.content)
|
||||
self.assertIn("Provide a CSV", response.content.decode())
|
||||
|
||||
f = ContentFile(b"""domain; domain1.com; 1000; 100; True
|
||||
domain; domain2.com; 1000; 200; False
|
||||
@@ -80,14 +83,14 @@ domainalias;test.alias;test.com;True
|
||||
"sourcefile": f
|
||||
}
|
||||
)
|
||||
self.assertIn('Object already exists: domainalias', resp.content)
|
||||
self.assertIn('Object already exists: domainalias', resp.content.decode())
|
||||
|
||||
def test_identities_import(self):
|
||||
response = self.client.get(reverse("admin:identity_import"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Provide a CSV", response.content)
|
||||
self.assertIn("Provide a CSV", response.content.decode())
|
||||
|
||||
f = ContentFile(b"""
|
||||
f = ContentFile("""
|
||||
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 0
|
||||
account; Truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 5; test.com
|
||||
alias; alias1@test.com; True; user1@test.com
|
||||
@@ -176,7 +179,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; ; t
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True}
|
||||
)
|
||||
self.assertIn('wrong quota value', resp.content)
|
||||
self.assertIn('wrong quota value', resp.content.decode())
|
||||
|
||||
def test_import_domain_by_domainadmin(self):
|
||||
"""Check if a domain admin is not allowed to import a domain."""
|
||||
@@ -189,7 +192,7 @@ domain; domain2.com; 1000; 200; False
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True}
|
||||
)
|
||||
self.assertIn("You are not allowed to import domains", resp.content)
|
||||
self.assertIn("You are not allowed to import domains", resp.content.decode())
|
||||
f = ContentFile(b"""
|
||||
domainalias; domalias1.com; test.com; True
|
||||
""", name="identities.csv")
|
||||
@@ -198,7 +201,7 @@ domainalias; domalias1.com; test.com; True
|
||||
{"sourcefile": f, "crypt_password": True}
|
||||
)
|
||||
self.assertIn(
|
||||
"You are not allowed to import domain aliases", resp.content)
|
||||
"You are not allowed to import domain aliases", resp.content.decode())
|
||||
|
||||
def test_import_quota_too_big(self):
|
||||
self.client.logout()
|
||||
@@ -210,7 +213,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 40
|
||||
reverse("admin:identity_import"),
|
||||
{"sourcefile": f, "crypt_password": True}
|
||||
)
|
||||
self.assertIn("Domain quota exceeded", resp.content)
|
||||
self.assertIn("Domain quota exceeded", resp.content.decode())
|
||||
|
||||
def test_import_missing_quota(self):
|
||||
f = ContentFile(b"""
|
||||
@@ -227,7 +230,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com
|
||||
)
|
||||
|
||||
def test_import_duplicate(self):
|
||||
f = ContentFile(b"""
|
||||
f = ContentFile("""
|
||||
account; admin@test.com; toto; Admin; ; True; DomainAdmins; admin@test.com; 0; test.com
|
||||
account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0; test.com
|
||||
""", name="identities.csv")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Management command tests."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Test map files generation."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""DNSBL related tests."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core import management
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from modoboa.core.models import User
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
"""Repair command tests"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core import management
|
||||
|
||||
from modoboa.lib.permissions import get_object_owner
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""User related tests."""
|
||||
|
||||
import json
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
@@ -28,7 +28,7 @@ class ForwardTestCase(ModoTestCase):
|
||||
)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = json.loads(response.content)
|
||||
content = response.json()
|
||||
self.assertIn("user@extdomain.com", content["content"])
|
||||
self.assertNotIn("user@test.com", content["content"])
|
||||
forward = Alias.objects.get(address="user@test.com", internal=False)
|
||||
@@ -41,5 +41,5 @@ class ForwardTestCase(ModoTestCase):
|
||||
self.ajax_post(url, {"dest": "", "keepcopies": False})
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = json.loads(response.content)
|
||||
content = response.json()
|
||||
self.assertNotIn("user@extdomain.com", content["content"])
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# pylint: disable=invalid-name, protected-access
|
||||
"""Admin urls."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Admin API urls."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from rest_framework import routers
|
||||
|
||||
from . import api
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Shortcuts."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .alias import AliasDetailView
|
||||
from .identity import AccountDetailView
|
||||
from .dns import DNSBLDomainDetailView, MXDomainDetailView
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Alias related views."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import IntegrityError
|
||||
from django.shortcuts import render
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""DNSBL related views."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.views import generic
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""Domain related views."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Export related views."""
|
||||
|
||||
import cStringIO
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import csv
|
||||
|
||||
from rfc6266 import build_header
|
||||
@@ -12,6 +13,8 @@ from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils import six
|
||||
|
||||
from ..forms import ExportIdentitiesForm, ExportDomainsForm
|
||||
from ..lib import get_domains, get_identities
|
||||
@@ -47,8 +50,10 @@ def export_identities(request):
|
||||
if request.method == "POST":
|
||||
form = ExportIdentitiesForm(request.POST)
|
||||
form.is_valid()
|
||||
fp = cStringIO.StringIO()
|
||||
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
|
||||
fp = six.StringIO()
|
||||
csvwriter = csv.writer(
|
||||
fp, delimiter=smart_str(form.cleaned_data["sepchar"])
|
||||
)
|
||||
identities = get_identities(
|
||||
request.user, **request.session['identities_filters'])
|
||||
for ident in identities:
|
||||
@@ -75,8 +80,10 @@ def export_domains(request):
|
||||
if request.method == "POST":
|
||||
form = ExportDomainsForm(request.POST)
|
||||
form.is_valid()
|
||||
fp = cStringIO.StringIO()
|
||||
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
|
||||
fp = six.StringIO()
|
||||
csvwriter = csv.writer(
|
||||
fp, delimiter=smart_str(form.cleaned_data["sepchar"])
|
||||
)
|
||||
for dom in get_domains(request.user,
|
||||
**request.session['domains_filters']):
|
||||
dom.to_csv(csvwriter)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Identity related views."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
"""Import related views."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import csv
|
||||
|
||||
from io import StringIO
|
||||
|
||||
from django.contrib.auth.decorators import (
|
||||
login_required, permission_required, user_passes_test
|
||||
)
|
||||
@@ -9,6 +13,8 @@ from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.encoding import smart_str, force_text
|
||||
from django.utils import six
|
||||
|
||||
from reversion import revisions as reversion
|
||||
|
||||
@@ -34,8 +40,16 @@ def importdata(request, formclass=ImportDataForm):
|
||||
form = formclass(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
try:
|
||||
reader = csv.reader(request.FILES['sourcefile'],
|
||||
delimiter=form.cleaned_data['sepchar'])
|
||||
if six.PY2:
|
||||
infile = request.FILES['sourcefile']
|
||||
else:
|
||||
infile = StringIO(
|
||||
force_text(request.FILES['sourcefile'].read())
|
||||
)
|
||||
reader = csv.reader(
|
||||
infile,
|
||||
delimiter=smart_str(form.cleaned_data['sepchar'])
|
||||
)
|
||||
except csv.Error as inst:
|
||||
error = str(inst)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""SimpleUsers views."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""App config for core."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
default_app_config = "modoboa.core.apps.CoreConfig"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Core config for admin."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.models import signals
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import Context, Template
|
||||
from django.utils.encoding import smart_str
|
||||
|
||||
|
||||
class Command(object):
|
||||
@@ -64,7 +67,7 @@ def scan_for_commands(dirname=""):
|
||||
continue
|
||||
cmdname = f.replace(".py", "")
|
||||
cmdmod = __import__("modoboa.core.commands", globals(), locals(),
|
||||
[cmdname])
|
||||
[smart_str(cmdname)])
|
||||
cmdmod = getattr(cmdmod, cmdname)
|
||||
if "_" in cmdname:
|
||||
cmdclassname = "".join(
|
||||
@@ -94,7 +97,7 @@ def handle_command_line():
|
||||
(args, remaining) = parser.parse_known_args()
|
||||
|
||||
if args.command not in commands:
|
||||
print >> sys.stderr, "Unknown command '%s'" % args.command
|
||||
print("Unknown command '%s'" % args.command, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
commands[args.command](commands, verbose=args.verbose).run(remaining)
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
|
||||
"""A shortcut to deploy a fresh modoboa instance."""
|
||||
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import getpass
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from os.path import isfile
|
||||
|
||||
try:
|
||||
import pip
|
||||
except ImportError:
|
||||
@@ -17,6 +21,7 @@ except ImportError:
|
||||
import django
|
||||
from django.core import management
|
||||
from django.template import Context, Template
|
||||
from django.utils.encoding import smart_str
|
||||
import dj_database_url
|
||||
|
||||
from modoboa.core.commands import Command
|
||||
@@ -105,9 +110,11 @@ class DeployCommand(Command):
|
||||
output = None
|
||||
if p.returncode:
|
||||
if output:
|
||||
print >> sys.stderr, "\n".join(
|
||||
[l for l in output if l is not None])
|
||||
print >> sys.stderr, "%s failed, check your configuration" % cmd
|
||||
print(
|
||||
"\n".join([l.decode() for l in output if l is not None]),
|
||||
file=sys.stderr
|
||||
)
|
||||
print("%s failed, check your configuration" % cmd, file=sys.stderr)
|
||||
|
||||
def ask_db_info(self, name='default'):
|
||||
"""Prompt the user for database information
|
||||
@@ -117,10 +124,11 @@ class DeployCommand(Command):
|
||||
|
||||
:param name: the connection name
|
||||
"""
|
||||
print "Configuring database connection: %s" % name
|
||||
print("Configuring database connection: %s" % name)
|
||||
info = {
|
||||
'conn_name': name,
|
||||
'ENGINE': raw_input('Database type (mysql, postgres or sqlite3): ')
|
||||
'ENGINE': eval(
|
||||
input('Database type (mysql, postgres or sqlite3): '))
|
||||
}
|
||||
if info['ENGINE'] not in ['mysql', 'postgres', 'sqlite3']:
|
||||
raise RuntimeError('Unsupported database engine')
|
||||
@@ -135,15 +143,15 @@ class DeployCommand(Command):
|
||||
else:
|
||||
info['ENGINE'] = 'django.db.backends.mysql'
|
||||
default_port = 3306
|
||||
info['HOST'] = raw_input("Database host (default: 'localhost'): ")
|
||||
info['PORT'] = raw_input(
|
||||
"Database port (default: '%s'): " % default_port)
|
||||
info['HOST'] = eval(input("Database host (default: 'localhost'): "))
|
||||
info['PORT'] = eval(
|
||||
input("Database port (default: '%s'): " % default_port))
|
||||
# leave port setting empty, if default value is supplied and
|
||||
# leave it to django
|
||||
if info['PORT'] == default_port:
|
||||
info['PORT'] = ''
|
||||
info['NAME'] = raw_input('Database name: ')
|
||||
info['USER'] = raw_input('Username: ')
|
||||
info['NAME'] = eval(input('Database name: '))
|
||||
info['USER'] = eval(input('Username: '))
|
||||
info['PASSWORD'] = getpass.getpass('Password: ')
|
||||
return info
|
||||
|
||||
@@ -192,7 +200,7 @@ class DeployCommand(Command):
|
||||
# In case the user fails to supply a valid database url,
|
||||
# fallback to manual mode
|
||||
if not info:
|
||||
print "There was a problem with your database-url. \n"
|
||||
print("There was a problem with your database-url. \n")
|
||||
info = self.ask_db_info(conn_name)
|
||||
# If we set this earlier, our fallback method will never
|
||||
# be triggered
|
||||
@@ -205,9 +213,8 @@ class DeployCommand(Command):
|
||||
if parsed_args.domain:
|
||||
allowed_host = parsed_args.domain
|
||||
else:
|
||||
allowed_host = raw_input(
|
||||
'What will be the hostname used to access Modoboa? '
|
||||
)
|
||||
allowed_host = eval(
|
||||
input('What will be the hostname used to access Modoboa? '))
|
||||
if not allowed_host:
|
||||
allowed_host = "localhost"
|
||||
extra_settings = []
|
||||
@@ -225,7 +232,9 @@ class DeployCommand(Command):
|
||||
os.path.join(os.path.dirname(__file__), "../../bower_components")
|
||||
)
|
||||
|
||||
mod = __import__(parsed_args.name, globals(), locals(), ['settings'])
|
||||
mod = __import__(
|
||||
parsed_args.name, globals(), locals(), [smart_str('settings')]
|
||||
)
|
||||
tpl = self._render_template(
|
||||
"%s/settings.py.tpl" % self._templates_dir, {
|
||||
'db_connections': connections,
|
||||
@@ -247,7 +256,8 @@ class DeployCommand(Command):
|
||||
)
|
||||
os.mkdir("%s/media" % parsed_args.name)
|
||||
|
||||
os.unlink("%s/settings.pyc" % path)
|
||||
if isfile("%s/settings.pyc" % path):
|
||||
os.unlink("%s/settings.pyc" % path)
|
||||
self._exec_django_command(
|
||||
"migrate", parsed_args.name, '--noinput'
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import sys
|
||||
|
||||
from . import Command
|
||||
@@ -16,7 +18,7 @@ class HelpCommand(Command):
|
||||
|
||||
def handle(self, parsed_args):
|
||||
if parsed_args.name not in self._commands:
|
||||
print >>sys.stderr, "Unknown command: %s" % parsed_args.name
|
||||
print("Unknown command: %s" % parsed_args.name, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
cmd = self._commands[parsed_args.name](self._commands)
|
||||
cmd._parser.prog = "modoboa-admin.py %s" % parsed_args.name
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
"""Core application constants."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
SIMPLEUSERS_ROLE = ("SimpleUsers", ugettext_lazy("Simple user"))
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""Custom context processors."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from . import signals
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Development settings
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
BOWER_COMPONENTS_ROOT = os.path.join(
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Extension management."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.utils.encoding import smart_str
|
||||
|
||||
|
||||
class ModoExtension(object):
|
||||
@@ -76,7 +79,7 @@ class ExtensionsPool(object):
|
||||
|
||||
def load_extension(self, name):
|
||||
"""Load a registered extension."""
|
||||
__import__(name, locals(), globals(), ["modo_extension"])
|
||||
__import__(name, locals(), globals(), [smart_str('modo_extension')])
|
||||
extinstance = self.get_extension(name)
|
||||
if extinstance is None:
|
||||
return None
|
||||
@@ -100,7 +103,7 @@ class ExtensionsPool(object):
|
||||
def get_urls(self, category="app"):
|
||||
"""Get all urls defined by extensions."""
|
||||
result = []
|
||||
for ext_name in self.extensions.keys():
|
||||
for ext_name in list(self.extensions.keys()):
|
||||
ext = self.get_extension(ext_name)
|
||||
options = {}
|
||||
if category == "api":
|
||||
@@ -122,7 +125,7 @@ class ExtensionsPool(object):
|
||||
def list_all(self):
|
||||
"""List all defined extensions."""
|
||||
result = []
|
||||
for extname, extdef in self.extensions.iteritems():
|
||||
for extname, extdef in list(self.extensions.items()):
|
||||
if not extdef["show"]:
|
||||
continue
|
||||
infos = self.get_extension_infos(extname)
|
||||
@@ -130,4 +133,5 @@ class ExtensionsPool(object):
|
||||
result += [infos]
|
||||
return sorted(result, key=lambda i: i["name"])
|
||||
|
||||
|
||||
exts_pool = ExtensionsPool()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Factories for core application."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import factory
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
@@ -11,7 +13,7 @@ class PermissionFactory(factory.DjangoModelFactory):
|
||||
|
||||
"""A base factory to handle permissions."""
|
||||
|
||||
class Meta:
|
||||
class Meta(object):
|
||||
abstract = True
|
||||
|
||||
@factory.post_generation
|
||||
@@ -25,7 +27,7 @@ class GroupFactory(factory.DjangoModelFactory):
|
||||
|
||||
"""A factory to create Group instances."""
|
||||
|
||||
class Meta:
|
||||
class Meta(object):
|
||||
model = Group
|
||||
|
||||
name = "DefaultGroup"
|
||||
@@ -35,7 +37,7 @@ class UserFactory(PermissionFactory):
|
||||
|
||||
"""A factory to create User instances."""
|
||||
|
||||
class Meta:
|
||||
class Meta(object):
|
||||
model = models.User
|
||||
django_get_or_create = ("username", )
|
||||
|
||||
@@ -55,7 +57,7 @@ class UserFactory(PermissionFactory):
|
||||
class LogFactory(factory.DjangoModelFactory):
|
||||
"""Factory for Log."""
|
||||
|
||||
class Meta:
|
||||
class Meta(object):
|
||||
model = models.Log
|
||||
|
||||
logger = "modoboa.admin"
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
"""Core forms."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
@@ -44,7 +46,7 @@ class ProfileForm(forms.ModelForm):
|
||||
widget=forms.PasswordInput(attrs={"class": "form-control"})
|
||||
)
|
||||
|
||||
class Meta:
|
||||
class Meta(object):
|
||||
model = User
|
||||
fields = ("first_name", "last_name", "language",
|
||||
"phone_number", "secondary_email")
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"""Modoboa core signal handlers."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import signals
|
||||
from django.dispatch import receiver
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from django.contrib.sites import models as sites_models
|
||||
@@ -44,7 +47,7 @@ def post_revision_commit(sender, **kwargs):
|
||||
action = _("modified")
|
||||
level = "warning"
|
||||
message = _("%(object)s '%(name)s' %(action)s by user %(user)s") % {
|
||||
"object": unicode(version.content_type).capitalize(),
|
||||
"object": smart_text(version.content_type).capitalize(),
|
||||
"name": version.object_repr, "action": action,
|
||||
"user": current_user
|
||||
}
|
||||
@@ -68,7 +71,7 @@ def log_object_removal(sender, instance, **kwargs):
|
||||
return
|
||||
logger = logging.getLogger("modoboa.admin")
|
||||
msg = _("%(object)s '%(name)s' %(action)s by user %(user)s") % {
|
||||
"object": unicode(version.content_type).capitalize(),
|
||||
"object": smart_text(version.content_type).capitalize(),
|
||||
"name": version.object_repr, "action": _("deleted"),
|
||||
"user": get_request().user.username
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# coding: utf-8
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
@@ -27,7 +29,7 @@ class Command(BaseCommand):
|
||||
def __vprint(self, msg):
|
||||
if not self.verbose:
|
||||
return
|
||||
print msg
|
||||
print(msg)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if options["debug"]:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Management command to communicate with public API."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from modoboa.admin import models as admin_models
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Management command to generate/update postfix map files."""
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
@@ -9,6 +10,7 @@ from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.template import Context, Template
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
import dj_database_url
|
||||
|
||||
@@ -67,7 +69,7 @@ class Command(BaseCommand):
|
||||
fname not in self.__checksums)
|
||||
if condition:
|
||||
return True
|
||||
with open(path) as fp:
|
||||
with open(path, mode="rb") as fp:
|
||||
checksum = hashlib.md5(fp.read()).hexdigest()
|
||||
return checksum == self.__checksums[fname]["checksum"]
|
||||
|
||||
@@ -134,14 +136,14 @@ query = {{ query|safe }}
|
||||
context["dbhost"] = mapcontent["hosts"]
|
||||
content = self.get_template(context["dbtype"]).render(
|
||||
Context(
|
||||
dict(context.items(),
|
||||
dict(list(context.items()),
|
||||
query=getattr(mapobject, context["dbtype"]))
|
||||
)
|
||||
)
|
||||
fullpath = os.path.join(destdir, mapobject.filename)
|
||||
with open(fullpath, "w") as fp:
|
||||
fp.write(content)
|
||||
return hashlib.md5(content).hexdigest()
|
||||
return hashlib.md5(force_bytes(content)).hexdigest()
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Command entry point."""
|
||||
@@ -160,6 +162,6 @@ query = {{ query|safe }}
|
||||
force_overwrite=options["force_overwrite"])
|
||||
checksums[mapobject.filename] = checksum
|
||||
with open(self.__checksums_file, "w") as fp:
|
||||
for fname, checksum in checksums.items():
|
||||
for fname, checksum in list(checksums.items()):
|
||||
fp.write("{}:{}:{}\n".format(
|
||||
fname, context["dbtype"], checksum))
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
@@ -57,7 +61,7 @@ class Command(BaseCommand):
|
||||
|
||||
extensions.exts_pool.load_all()
|
||||
|
||||
groups = constants.PERMISSIONS.keys()
|
||||
groups = list(constants.PERMISSIONS.keys())
|
||||
for groupname in groups:
|
||||
group, created = Group.objects.get_or_create(name=groupname)
|
||||
results = signals.extra_role_permissions.send(
|
||||
@@ -70,7 +74,7 @@ class Command(BaseCommand):
|
||||
continue
|
||||
add_permissions_to_group(group, permissions)
|
||||
|
||||
for extname in extensions.exts_pool.extensions.keys():
|
||||
for extname in list(extensions.exts_pool.extensions.keys()):
|
||||
extension = extensions.exts_pool.get_extension(extname)
|
||||
extension.load_initial_data()
|
||||
signals.initial_data_loaded.send(
|
||||
|
||||
@@ -5,6 +5,8 @@ A simple managemenent command to update the default site.
|
||||
See `https://docs.djangoproject.com/en/dev/ref/contrib/sites/`_.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Core middlewares."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Mocks used for testing."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import httmock
|
||||
|
||||
|
||||
|
||||
+17
-14
@@ -1,12 +1,16 @@
|
||||
"""Core models."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from email.header import Header
|
||||
import re
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import python_2_unicode_compatible, smart_text
|
||||
from django.utils.encoding import (
|
||||
python_2_unicode_compatible, smart_bytes, smart_text, force_str, force_text
|
||||
)
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
|
||||
@@ -91,7 +95,7 @@ class User(PermissionsMixin):
|
||||
USERNAME_FIELD = 'username'
|
||||
REQUIRED_FIELDS = ['email']
|
||||
|
||||
class Meta:
|
||||
class Meta(object):
|
||||
ordering = ["username"]
|
||||
index_together = [
|
||||
['email', 'is_active']
|
||||
@@ -119,8 +123,7 @@ class User(PermissionsMixin):
|
||||
load_core_settings()
|
||||
scheme = param_tools.get_global_parameter(
|
||||
"password_scheme", raise_exception=False)
|
||||
if isinstance(raw_value, unicode):
|
||||
raw_value = raw_value.encode("utf-8")
|
||||
raw_value = smart_bytes(raw_value)
|
||||
return get_password_hasher(scheme.upper())().encrypt(raw_value)
|
||||
|
||||
def set_password(self, raw_value, curvalue=None):
|
||||
@@ -152,8 +155,7 @@ class User(PermissionsMixin):
|
||||
match = self.password_expr.match(self.password)
|
||||
if match is None:
|
||||
return False
|
||||
if isinstance(raw_value, unicode):
|
||||
raw_value = raw_value.encode("utf-8")
|
||||
raw_value = force_str(raw_value)
|
||||
scheme = match.group(1)
|
||||
val2 = match.group(2)
|
||||
hasher = get_password_hasher(scheme)
|
||||
@@ -410,13 +412,13 @@ class User(PermissionsMixin):
|
||||
"""
|
||||
row = [
|
||||
"account",
|
||||
self.username.encode("utf-8"),
|
||||
self.password.encode("utf-8"),
|
||||
self.first_name.encode("utf-8"),
|
||||
self.last_name.encode("utf-8"),
|
||||
self.is_active,
|
||||
self.role,
|
||||
self.email.encode("utf-8")
|
||||
force_text(self.username),
|
||||
force_text(self.password),
|
||||
force_text(self.first_name),
|
||||
force_text(self.last_name),
|
||||
force_text(self.is_active),
|
||||
force_text(self.role),
|
||||
force_text(self.email)
|
||||
]
|
||||
results = signals.account_exported.send(
|
||||
sender=self.__class__, user=self)
|
||||
@@ -424,6 +426,7 @@ class User(PermissionsMixin):
|
||||
row += result[1]
|
||||
csvwriter.writerow(row)
|
||||
|
||||
|
||||
reversion.register(User)
|
||||
|
||||
|
||||
@@ -455,7 +458,7 @@ class ObjectAccess(models.Model):
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
is_owner = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
class Meta(object):
|
||||
unique_together = (("user", "content_type", "object_id"),)
|
||||
|
||||
def __unicode__(self):
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""
|
||||
Password hashers for Modoboa.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from modoboa.core.password_hashers.base import (
|
||||
PLAINHasher, CRYPTHasher, MD5Hasher, SHA256Hasher
|
||||
)
|
||||
|
||||
@@ -4,6 +4,8 @@ Advanced (ie. stronger) password hashers.
|
||||
This module relies on `passlib` to provide more secure hashers.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from passlib.hash import bcrypt, md5_crypt, sha256_crypt, sha512_crypt
|
||||
|
||||
from modoboa.parameters import tools as param_tools
|
||||
|
||||
@@ -3,12 +3,17 @@ Base password hashers.
|
||||
|
||||
Contains weak hashers (the original ones) available with Modoboa.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import crypt
|
||||
import hashlib
|
||||
import base64
|
||||
import string
|
||||
from random import Random
|
||||
import string
|
||||
|
||||
from django.utils.crypto import constant_time_compare
|
||||
from django.utils.encoding import force_text, force_bytes
|
||||
|
||||
|
||||
class PasswordHasher(object):
|
||||
@@ -46,8 +51,8 @@ class PasswordHasher(object):
|
||||
:rtype: str
|
||||
:return: encrypted password
|
||||
"""
|
||||
pwhash = self._b64encode(self._encrypt(clearvalue))
|
||||
return '%s%s' % (self.scheme, pwhash)
|
||||
pwhash = self._b64encode(self._encrypt(force_text(clearvalue)))
|
||||
return '%s%s' % (self.scheme, force_text(pwhash))
|
||||
|
||||
def verify(self, clearvalue, hashed_value):
|
||||
"""Verify a password against a hashed value.
|
||||
@@ -88,7 +93,7 @@ class CRYPTHasher(PasswordHasher):
|
||||
|
||||
def _encrypt(self, clearvalue, salt=None):
|
||||
if salt is None:
|
||||
salt = "".join(Random().sample(string.letters + string.digits, 2))
|
||||
salt = "".join(Random().sample(string.ascii_letters + string.digits, 2))
|
||||
return crypt.crypt(clearvalue, salt)
|
||||
|
||||
|
||||
@@ -104,7 +109,7 @@ class MD5Hasher(PasswordHasher):
|
||||
return '{MD5}'
|
||||
|
||||
def _encrypt(self, clearvalue, salt=None):
|
||||
obj = hashlib.md5(clearvalue)
|
||||
obj = hashlib.md5(force_bytes(clearvalue))
|
||||
return obj.hexdigest()
|
||||
|
||||
|
||||
@@ -120,7 +125,7 @@ class SHA256Hasher(PasswordHasher):
|
||||
return '{SHA256}'
|
||||
|
||||
def _encrypt(self, clearvalue, salt=None):
|
||||
return hashlib.sha256(clearvalue).digest()
|
||||
return hashlib.sha256(force_bytes(clearvalue)).digest()
|
||||
|
||||
def _b64encode(self, pwhash):
|
||||
"""Encode :keyword:`pwhash` using base64.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Modoboa core signals."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.dispatch
|
||||
|
||||
account_auto_created = django.dispatch.Signal(providing_args=["user"])
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# coding: utf-8
|
||||
"""Custom tags for Core application."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import reduce
|
||||
import os
|
||||
import pkg_resources
|
||||
import re
|
||||
@@ -10,6 +13,7 @@ from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import get_language, ugettext as _
|
||||
|
||||
@@ -241,7 +245,7 @@ def display_messages(msgs):
|
||||
level = "info"
|
||||
for m in msgs:
|
||||
level = m.tags
|
||||
text += unicode(m) + "\\\n"
|
||||
text += smart_text(m) + "\\\n"
|
||||
|
||||
if level == "info":
|
||||
level = "success"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""A stupid extension used for tests."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from modoboa.core import extensions
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user