diff --git a/.gitignore b/.gitignore index 530d709e6..a018c094f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ DEV .idea .ropeproject modoboa.iml +.tox +modoboa_test diff --git a/.travis.yml b/.travis.yml index 0a580df42..fbfb621d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/bin/modoboa-admin.py b/bin/modoboa-admin.py index 9309b6cc8..a637e52c6 100644 --- a/bin/modoboa-admin.py +++ b/bin/modoboa-admin.py @@ -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__": diff --git a/ldap-requirements.txt b/ldap-requirements.txt index 926c4e036..212b582a2 100644 --- a/ldap-requirements.txt +++ b/ldap-requirements.txt @@ -1,2 +1 @@ -python-ldap django-auth-ldap diff --git a/modoboa/__init__.py b/modoboa/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/__init__.py +++ b/modoboa/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/admin/__init__.py b/modoboa/admin/__init__.py index 4002eb9dd..5882e6d25 100644 --- a/modoboa/admin/__init__.py +++ b/modoboa/admin/__init__.py @@ -1 +1,3 @@ +from __future__ import unicode_literals + default_app_config = "modoboa.admin.apps.AdminConfig" diff --git a/modoboa/admin/api.py b/modoboa/admin/api.py index 27432a663..b4a5c491d 100644 --- a/modoboa/admin/api.py +++ b/modoboa/admin/api.py @@ -1,5 +1,7 @@ """Admin API.""" +from __future__ import unicode_literals + from django import http from django.contrib.contenttypes.models import ContentType diff --git a/modoboa/admin/app_settings.py b/modoboa/admin/app_settings.py index 433a21618..889e4755c 100644 --- a/modoboa/admin/app_settings.py +++ b/modoboa/admin/app_settings.py @@ -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: diff --git a/modoboa/admin/apps.py b/modoboa/admin/apps.py index 281d2d5ba..f799fa177 100644 --- a/modoboa/admin/apps.py +++ b/modoboa/admin/apps.py @@ -1,5 +1,7 @@ """AppConfig for admin.""" +from __future__ import unicode_literals + from django.apps import AppConfig from .app_settings import load_admin_settings diff --git a/modoboa/admin/constants.py b/modoboa/admin/constants.py index 944da5e2f..7a78b2cc0 100644 --- a/modoboa/admin/constants.py +++ b/modoboa/admin/constants.py @@ -1,5 +1,7 @@ """Admin constants.""" +from __future__ import unicode_literals + DNSBL_PROVIDERS = [ "aspews.ext.sorbs.net", "b.barracudacentral.org", diff --git a/modoboa/admin/factories.py b/modoboa/admin/factories.py index ad7eb13a3..2aa5112c9 100644 --- a/modoboa/admin/factories.py +++ b/modoboa/admin/factories.py @@ -1,6 +1,9 @@ """ Fixtures factories. """ + +from __future__ import unicode_literals + import factory from modoboa.core.factories import PermissionFactory, UserFactory diff --git a/modoboa/admin/forms/__init__.py b/modoboa/admin/forms/__init__.py index dbb5a36ef..b7811a0d9 100644 --- a/modoboa/admin/forms/__init__.py +++ b/modoboa/admin/forms/__init__.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from .domain import ( DomainFormGeneral, DomainFormOptions, DomainForm, DomainWizard ) diff --git a/modoboa/admin/forms/account.py b/modoboa/admin/forms/account.py index e34c5f537..53e2ff5fe 100644 --- a/modoboa/admin/forms/account.py +++ b/modoboa/admin/forms/account.py @@ -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 diff --git a/modoboa/admin/forms/alias.py b/modoboa/admin/forms/alias.py index 6c7641145..12b306442 100644 --- a/modoboa/admin/forms/alias.py +++ b/modoboa/admin/forms/alias.py @@ -1,5 +1,7 @@ """Forms related to aliases management.""" +from __future__ import unicode_literals + from collections import OrderedDict from django import forms diff --git a/modoboa/admin/forms/domain.py b/modoboa/admin/forms/domain.py index 9bcf1ce13..bac68ec72 100644 --- a/modoboa/admin/forms/domain.py +++ b/modoboa/admin/forms/domain.py @@ -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]) diff --git a/modoboa/admin/forms/export.py b/modoboa/admin/forms/export.py index f1f8a868b..25e9402a3 100644 --- a/modoboa/admin/forms/export.py +++ b/modoboa/admin/forms/export.py @@ -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): diff --git a/modoboa/admin/forms/forward.py b/modoboa/admin/forms/forward.py index 5cd7b1b7c..ddccba0f4 100644 --- a/modoboa/admin/forms/forward.py +++ b/modoboa/admin/forms/forward.py @@ -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 diff --git a/modoboa/admin/forms/import_.py b/modoboa/admin/forms/import_.py index 0091e4ba8..790260919 100644 --- a/modoboa/admin/forms/import_.py +++ b/modoboa/admin/forms/import_.py @@ -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): diff --git a/modoboa/admin/handlers.py b/modoboa/admin/handlers.py index 35ed3ee9b..ff9d7c954 100644 --- a/modoboa/admin/handlers.py +++ b/modoboa/admin/handlers.py @@ -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 + [ diff --git a/modoboa/admin/lib.py b/modoboa/admin/lib.py index 368757300..4ad7a9d67 100644 --- a/modoboa/admin/lib.py +++ b/modoboa/admin/lib.py @@ -2,6 +2,8 @@ """Internal library for admin.""" +from __future__ import unicode_literals + from functools import wraps from itertools import chain diff --git a/modoboa/admin/management/__init__.py b/modoboa/admin/management/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/admin/management/__init__.py +++ b/modoboa/admin/management/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/admin/management/commands/__init__.py b/modoboa/admin/management/commands/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/admin/management/commands/__init__.py +++ b/modoboa/admin/management/commands/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/admin/management/commands/handle_mailbox_operations.py b/modoboa/admin/management/commands/handle_mailbox_operations.py index 16eae65f4..8888cb9eb 100644 --- a/modoboa/admin/management/commands/handle_mailbox_operations.py +++ b/modoboa/admin/management/commands/handle_mailbox_operations.py @@ -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): diff --git a/modoboa/admin/management/commands/modo.py b/modoboa/admin/management/commands/modo.py index 4205f2e28..5088ace23 100644 --- a/modoboa/admin/management/commands/modo.py +++ b/modoboa/admin/management/commands/modo.py @@ -1,5 +1,7 @@ """Modoboa main management command.""" +from __future__ import unicode_literals + from subcommand.base import SubcommandCommand from .subcommands._mx import CheckMXRecords diff --git a/modoboa/admin/management/commands/subcommands/__init__.py b/modoboa/admin/management/commands/subcommands/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/admin/management/commands/subcommands/__init__.py +++ b/modoboa/admin/management/commands/subcommands/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/admin/management/commands/subcommands/_export.py b/modoboa/admin/management/commands/subcommands/_export.py index 4786e3462..6a5f0c121 100644 --- a/modoboa/admin/management/commands/subcommands/_export.py +++ b/modoboa/admin/management/commands/subcommands/_export.py @@ -1,5 +1,7 @@ """Django management command to export admin objects.""" +from __future__ import unicode_literals + import csv import sys diff --git a/modoboa/admin/management/commands/subcommands/_import.py b/modoboa/admin/management/commands/subcommands/_import.py index b5fb3c49c..8e199eabf 100644 --- a/modoboa/admin/management/commands/subcommands/_import.py +++ b/modoboa/admin/management/commands/subcommands/_import.py @@ -1,5 +1,7 @@ """Django management command to import admin objects.""" +from __future__ import unicode_literals + import csv import os diff --git a/modoboa/admin/management/commands/subcommands/_mx.py b/modoboa/admin/management/commands/subcommands/_mx.py index 38cac067f..740f514d4 100644 --- a/modoboa/admin/management/commands/subcommands/_mx.py +++ b/modoboa/admin/management/commands/subcommands/_mx.py @@ -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 diff --git a/modoboa/admin/management/commands/subcommands/_repair.py b/modoboa/admin/management/commands/subcommands/_repair.py index 29af55e65..1406b683d 100644 --- a/modoboa/admin/management/commands/subcommands/_repair.py +++ b/modoboa/admin/management/commands/subcommands/_repair.py @@ -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: diff --git a/modoboa/admin/migrations/__init__.py b/modoboa/admin/migrations/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/admin/migrations/__init__.py +++ b/modoboa/admin/migrations/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/admin/models/__init__.py b/modoboa/admin/models/__init__.py index 07a1569ea..35e093302 100644 --- a/modoboa/admin/models/__init__.py +++ b/modoboa/admin/models/__init__.py @@ -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 diff --git a/modoboa/admin/models/alias.py b/modoboa/admin/models/alias.py index 0c3223d18..b137dfa08 100644 --- a/modoboa/admin/models/alias.py +++ b/modoboa/admin/models/alias.py @@ -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) diff --git a/modoboa/admin/models/base.py b/modoboa/admin/models/base.py index b58ab912a..f3fb46848 100644 --- a/modoboa/admin/models/base.py +++ b/modoboa/admin/models/base.py @@ -1,5 +1,7 @@ """Base admin models.""" +from __future__ import unicode_literals + from django.db import models from django.utils import timezone diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py index 16a59ab6d..516378605 100644 --- a/modoboa/admin/models/domain.py +++ b/modoboa/admin/models/domain.py @@ -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) diff --git a/modoboa/admin/models/domain_alias.py b/modoboa/admin/models/domain_alias.py index 516be06fc..c9047886d 100644 --- a/modoboa/admin/models/domain_alias.py +++ b/modoboa/admin/models/domain_alias.py @@ -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 diff --git a/modoboa/admin/models/mailbox.py b/modoboa/admin/models/mailbox.py index b527ab0d0..bd50a3363 100644 --- a/modoboa/admin/models/mailbox.py +++ b/modoboa/admin/models/mailbox.py @@ -1,5 +1,7 @@ """Models related to mailboxes management.""" +from __future__ import unicode_literals + import os import pwd diff --git a/modoboa/admin/postfix_maps.py b/modoboa/admin/postfix_maps.py index a6e5a4c4a..1af7112fa 100644 --- a/modoboa/admin/postfix_maps.py +++ b/modoboa/admin/postfix_maps.py @@ -1,5 +1,7 @@ """Map file definitions for postfix.""" +from __future__ import unicode_literals + class DomainsMap(object): diff --git a/modoboa/admin/serializers.py b/modoboa/admin/serializers.py index 5a0608874..878df8b05 100644 --- a/modoboa/admin/serializers.py +++ b/modoboa/admin/serializers.py @@ -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) diff --git a/modoboa/admin/signals.py b/modoboa/admin/signals.py index c915f0c18..79f2ed116 100644 --- a/modoboa/admin/signals.py +++ b/modoboa/admin/signals.py @@ -1,5 +1,7 @@ """Modoboa admin signals.""" +from __future__ import unicode_literals + import django.dispatch check_extra_account_form = django.dispatch.Signal( diff --git a/modoboa/admin/templatetags/__init__.py b/modoboa/admin/templatetags/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/admin/templatetags/__init__.py +++ b/modoboa/admin/templatetags/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/admin/templatetags/admin_tags.py b/modoboa/admin/templatetags/admin_tags.py index 38948c6a7..60285fa0a 100644 --- a/modoboa/admin/templatetags/admin_tags.py +++ b/modoboa/admin/templatetags/admin_tags.py @@ -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 diff --git a/modoboa/admin/tests/__init__.py b/modoboa/admin/tests/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/admin/tests/__init__.py +++ b/modoboa/admin/tests/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/admin/tests/test_account.py b/modoboa/admin/tests/test_account.py index b2ae52cdc..b0927329b 100644 --- a/modoboa/admin/tests/test_account.py +++ b/modoboa/admin/tests/test_account.py @@ -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") diff --git a/modoboa/admin/tests/test_alias.py b/modoboa/admin/tests/test_alias.py index 4ad07d09d..b75310b24 100644 --- a/modoboa/admin/tests/test_alias.py +++ b/modoboa/admin/tests/test_alias.py @@ -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.""" diff --git a/modoboa/admin/tests/test_api.py b/modoboa/admin/tests/test_api.py index 8a824b08b..44f146bcb 100644 --- a/modoboa/admin/tests/test_api.py +++ b/modoboa/admin/tests/test_api.py @@ -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): diff --git a/modoboa/admin/tests/test_domain.py b/modoboa/admin/tests/test_domain.py index e71368f4f..f069ca13d 100644 --- a/modoboa/admin/tests/test_domain.py +++ b/modoboa/admin/tests/test_domain.py @@ -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) diff --git a/modoboa/admin/tests/test_domain_alias.py b/modoboa/admin/tests/test_domain_alias.py index f5f21efcc..76f92fb16 100644 --- a/modoboa/admin/tests/test_domain_alias.py +++ b/modoboa/admin/tests/test_domain_alias.py @@ -1,5 +1,7 @@ # coding: utf-8 +from __future__ import unicode_literals + from django.core.urlresolvers import reverse from modoboa.lib.tests import ModoTestCase diff --git a/modoboa/admin/tests/test_export.py b/modoboa/admin/tests/test_export.py index f35eb8a9f..5c5f7debd 100644 --- a/modoboa/admin/tests/test_export.py +++ b/modoboa/admin/tests/test_export.py @@ -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" ) diff --git a/modoboa/admin/tests/test_import_.py b/modoboa/admin/tests/test_import_.py index d468144f6..dfb612bd3 100644 --- a/modoboa/admin/tests/test_import_.py +++ b/modoboa/admin/tests/test_import_.py @@ -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") diff --git a/modoboa/admin/tests/test_mailbox_operations.py b/modoboa/admin/tests/test_mailbox_operations.py index d27d99233..bfcf9491e 100644 --- a/modoboa/admin/tests/test_mailbox_operations.py +++ b/modoboa/admin/tests/test_mailbox_operations.py @@ -1,5 +1,7 @@ """Management command tests.""" +from __future__ import unicode_literals + import os import shutil import tempfile diff --git a/modoboa/admin/tests/test_mapfiles.py b/modoboa/admin/tests/test_mapfiles.py index 255968353..ff0bd2ba1 100644 --- a/modoboa/admin/tests/test_mapfiles.py +++ b/modoboa/admin/tests/test_mapfiles.py @@ -1,5 +1,7 @@ """Test map files generation.""" +from __future__ import unicode_literals + import os from django.conf import settings diff --git a/modoboa/admin/tests/test_mx.py b/modoboa/admin/tests/test_mx.py index 1aa59656e..aa455f500 100644 --- a/modoboa/admin/tests/test_mx.py +++ b/modoboa/admin/tests/test_mx.py @@ -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 diff --git a/modoboa/admin/tests/test_password_schemes.py b/modoboa/admin/tests/test_password_schemes.py index 113c5d90b..160d1e0b2 100644 --- a/modoboa/admin/tests/test_password_schemes.py +++ b/modoboa/admin/tests/test_password_schemes.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.core.urlresolvers import reverse from modoboa.core.models import User diff --git a/modoboa/admin/tests/test_repair.py b/modoboa/admin/tests/test_repair.py index f18178f3f..aa4ce47fd 100644 --- a/modoboa/admin/tests/test_repair.py +++ b/modoboa/admin/tests/test_repair.py @@ -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 diff --git a/modoboa/admin/tests/test_user.py b/modoboa/admin/tests/test_user.py index 25eebb8c5..2eccfc900 100644 --- a/modoboa/admin/tests/test_user.py +++ b/modoboa/admin/tests/test_user.py @@ -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"]) diff --git a/modoboa/admin/urls.py b/modoboa/admin/urls.py index 1934ced58..63958d202 100644 --- a/modoboa/admin/urls.py +++ b/modoboa/admin/urls.py @@ -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 diff --git a/modoboa/admin/urls_api.py b/modoboa/admin/urls_api.py index 3c1051aed..1f0f9f0d4 100644 --- a/modoboa/admin/urls_api.py +++ b/modoboa/admin/urls_api.py @@ -1,5 +1,7 @@ """Admin API urls.""" +from __future__ import unicode_literals + from rest_framework import routers from . import api diff --git a/modoboa/admin/views/__init__.py b/modoboa/admin/views/__init__.py index e63b5b5e1..8c499c185 100644 --- a/modoboa/admin/views/__init__.py +++ b/modoboa/admin/views/__init__.py @@ -1,5 +1,7 @@ """Shortcuts.""" +from __future__ import unicode_literals + from .alias import AliasDetailView from .identity import AccountDetailView from .dns import DNSBLDomainDetailView, MXDomainDetailView diff --git a/modoboa/admin/views/alias.py b/modoboa/admin/views/alias.py index 1ec4aaf23..20b1c4635 100644 --- a/modoboa/admin/views/alias.py +++ b/modoboa/admin/views/alias.py @@ -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 diff --git a/modoboa/admin/views/dns.py b/modoboa/admin/views/dns.py index c7381d0a6..591f3f3e5 100644 --- a/modoboa/admin/views/dns.py +++ b/modoboa/admin/views/dns.py @@ -1,5 +1,7 @@ """DNSBL related views.""" +from __future__ import unicode_literals + from django.views import generic from django.utils.translation import ugettext as _ diff --git a/modoboa/admin/views/domain.py b/modoboa/admin/views/domain.py index 3348ec93d..39eba12f2 100644 --- a/modoboa/admin/views/domain.py +++ b/modoboa/admin/views/domain.py @@ -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 diff --git a/modoboa/admin/views/export.py b/modoboa/admin/views/export.py index 4e9430aa0..89e6dabb0 100644 --- a/modoboa/admin/views/export.py +++ b/modoboa/admin/views/export.py @@ -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) diff --git a/modoboa/admin/views/identity.py b/modoboa/admin/views/identity.py index b77773c5b..a38bc430d 100644 --- a/modoboa/admin/views/identity.py +++ b/modoboa/admin/views/identity.py @@ -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 diff --git a/modoboa/admin/views/import_.py b/modoboa/admin/views/import_.py index bf749f78f..44921471c 100644 --- a/modoboa/admin/views/import_.py +++ b/modoboa/admin/views/import_.py @@ -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) diff --git a/modoboa/admin/views/user.py b/modoboa/admin/views/user.py index 07af14f4e..424111919 100644 --- a/modoboa/admin/views/user.py +++ b/modoboa/admin/views/user.py @@ -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 _ diff --git a/modoboa/core/__init__.py b/modoboa/core/__init__.py index 0f5234285..c9be3cc2e 100644 --- a/modoboa/core/__init__.py +++ b/modoboa/core/__init__.py @@ -1,3 +1,5 @@ """App config for core.""" +from __future__ import unicode_literals + default_app_config = "modoboa.core.apps.CoreConfig" diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index b0f9b9f43..47742a503 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -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 diff --git a/modoboa/core/apps.py b/modoboa/core/apps.py index a3d6e5dab..d676eb44a 100644 --- a/modoboa/core/apps.py +++ b/modoboa/core/apps.py @@ -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 diff --git a/modoboa/core/commands/__init__.py b/modoboa/core/commands/__init__.py index f767830b3..f21b36fa7 100644 --- a/modoboa/core/commands/__init__.py +++ b/modoboa/core/commands/__init__.py @@ -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) diff --git a/modoboa/core/commands/deploy.py b/modoboa/core/commands/deploy.py index 7cfa145b9..5a21e4fbf 100644 --- a/modoboa/core/commands/deploy.py +++ b/modoboa/core/commands/deploy.py @@ -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' ) diff --git a/modoboa/core/commands/help.py b/modoboa/core/commands/help.py index ab6960d00..88c7efc0b 100644 --- a/modoboa/core/commands/help.py +++ b/modoboa/core/commands/help.py @@ -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 diff --git a/modoboa/core/constants.py b/modoboa/core/constants.py index 6a9d2be6e..a024e5fa3 100644 --- a/modoboa/core/constants.py +++ b/modoboa/core/constants.py @@ -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")) diff --git a/modoboa/core/context_processors.py b/modoboa/core/context_processors.py index 7c081699c..72d4ba435 100644 --- a/modoboa/core/context_processors.py +++ b/modoboa/core/context_processors.py @@ -1,5 +1,9 @@ """Custom context processors.""" +from __future__ import unicode_literals + +from functools import reduce + from . import signals diff --git a/modoboa/core/dev_settings.py b/modoboa/core/dev_settings.py index ed1a3b6cb..cbc4b1b54 100644 --- a/modoboa/core/dev_settings.py +++ b/modoboa/core/dev_settings.py @@ -1,4 +1,7 @@ # Development settings + +from __future__ import unicode_literals + import os BOWER_COMPONENTS_ROOT = os.path.join( diff --git a/modoboa/core/extensions.py b/modoboa/core/extensions.py index 4303ff07d..a6ea04909 100644 --- a/modoboa/core/extensions.py +++ b/modoboa/core/extensions.py @@ -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() diff --git a/modoboa/core/factories.py b/modoboa/core/factories.py index 61f224763..9624ab0d5 100644 --- a/modoboa/core/factories.py +++ b/modoboa/core/factories.py @@ -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" diff --git a/modoboa/core/forms.py b/modoboa/core/forms.py index 09ccd9ee5..c2a021f1f 100644 --- a/modoboa/core/forms.py +++ b/modoboa/core/forms.py @@ -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") diff --git a/modoboa/core/handlers.py b/modoboa/core/handlers.py index 3be6517d2..a953cdc20 100644 --- a/modoboa/core/handlers.py +++ b/modoboa/core/handlers.py @@ -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 } diff --git a/modoboa/core/loggers.py b/modoboa/core/loggers.py index 35efe0397..c6dd24a05 100644 --- a/modoboa/core/loggers.py +++ b/modoboa/core/loggers.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import logging diff --git a/modoboa/core/management/__init__.py b/modoboa/core/management/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/core/management/__init__.py +++ b/modoboa/core/management/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/core/management/commands/__init__.py b/modoboa/core/management/commands/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/core/management/commands/__init__.py +++ b/modoboa/core/management/commands/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/core/management/commands/cleanlogs.py b/modoboa/core/management/commands/cleanlogs.py index 754cfe264..1513819c3 100644 --- a/modoboa/core/management/commands/cleanlogs.py +++ b/modoboa/core/management/commands/cleanlogs.py @@ -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"]: diff --git a/modoboa/core/management/commands/communicate_with_public_api.py b/modoboa/core/management/commands/communicate_with_public_api.py index dcd003a54..39dbdde28 100644 --- a/modoboa/core/management/commands/communicate_with_public_api.py +++ b/modoboa/core/management/commands/communicate_with_public_api.py @@ -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 diff --git a/modoboa/core/management/commands/generate_postfix_maps.py b/modoboa/core/management/commands/generate_postfix_maps.py index 65d85ac49..2e0f7fc05 100644 --- a/modoboa/core/management/commands/generate_postfix_maps.py +++ b/modoboa/core/management/commands/generate_postfix_maps.py @@ -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)) diff --git a/modoboa/core/management/commands/load_initial_data.py b/modoboa/core/management/commands/load_initial_data.py index dff31da7c..5c746481d 100644 --- a/modoboa/core/management/commands/load_initial_data.py +++ b/modoboa/core/management/commands/load_initial_data.py @@ -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( diff --git a/modoboa/core/management/commands/set_default_site.py b/modoboa/core/management/commands/set_default_site.py index 78db35a1f..d80268a10 100644 --- a/modoboa/core/management/commands/set_default_site.py +++ b/modoboa/core/management/commands/set_default_site.py @@ -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 diff --git a/modoboa/core/middleware.py b/modoboa/core/middleware.py index c1463be56..f8f66b403 100644 --- a/modoboa/core/middleware.py +++ b/modoboa/core/middleware.py @@ -1,5 +1,7 @@ """Core middlewares.""" +from __future__ import unicode_literals + from . import models diff --git a/modoboa/core/migrations/__init__.py b/modoboa/core/migrations/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/core/migrations/__init__.py +++ b/modoboa/core/migrations/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/core/mocks.py b/modoboa/core/mocks.py index 7c8f245f7..e593bfc3f 100644 --- a/modoboa/core/mocks.py +++ b/modoboa/core/mocks.py @@ -1,5 +1,7 @@ """Mocks used for testing.""" +from __future__ import unicode_literals + import httmock diff --git a/modoboa/core/models.py b/modoboa/core/models.py index fed83e3f7..baba71d07 100644 --- a/modoboa/core/models.py +++ b/modoboa/core/models.py @@ -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): diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 653b0dd56..6269c666c 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -1,6 +1,9 @@ """ Password hashers for Modoboa. """ + +from __future__ import unicode_literals + from modoboa.core.password_hashers.base import ( PLAINHasher, CRYPTHasher, MD5Hasher, SHA256Hasher ) diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py index c14245e29..ad27ae865 100644 --- a/modoboa/core/password_hashers/advanced.py +++ b/modoboa/core/password_hashers/advanced.py @@ -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 diff --git a/modoboa/core/password_hashers/base.py b/modoboa/core/password_hashers/base.py index b0902c926..a449c8088 100644 --- a/modoboa/core/password_hashers/base.py +++ b/modoboa/core/password_hashers/base.py @@ -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. diff --git a/modoboa/core/signals.py b/modoboa/core/signals.py index 409dc2e0b..56b640ec9 100644 --- a/modoboa/core/signals.py +++ b/modoboa/core/signals.py @@ -1,5 +1,7 @@ """Modoboa core signals.""" +from __future__ import unicode_literals + import django.dispatch account_auto_created = django.dispatch.Signal(providing_args=["user"]) diff --git a/modoboa/core/templatetags/__init__.py b/modoboa/core/templatetags/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/core/templatetags/__init__.py +++ b/modoboa/core/templatetags/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/core/templatetags/core_tags.py b/modoboa/core/templatetags/core_tags.py index 6d3518728..44d0735a4 100644 --- a/modoboa/core/templatetags/core_tags.py +++ b/modoboa/core/templatetags/core_tags.py @@ -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" diff --git a/modoboa/core/tests/__init__.py b/modoboa/core/tests/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/core/tests/__init__.py +++ b/modoboa/core/tests/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/core/tests/stupid_extension_1/__init__.py b/modoboa/core/tests/stupid_extension_1/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/core/tests/stupid_extension_1/__init__.py +++ b/modoboa/core/tests/stupid_extension_1/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/core/tests/stupid_extension_1/modo_extension.py b/modoboa/core/tests/stupid_extension_1/modo_extension.py index 4cc7f4121..4115447d0 100644 --- a/modoboa/core/tests/stupid_extension_1/modo_extension.py +++ b/modoboa/core/tests/stupid_extension_1/modo_extension.py @@ -1,5 +1,7 @@ """A stupid extension used for tests.""" +from __future__ import unicode_literals + from modoboa.core import extensions diff --git a/modoboa/core/tests/stupid_extension_2/__init__.py b/modoboa/core/tests/stupid_extension_2/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/core/tests/stupid_extension_2/__init__.py +++ b/modoboa/core/tests/stupid_extension_2/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/core/tests/stupid_extension_2/modo_extension.py b/modoboa/core/tests/stupid_extension_2/modo_extension.py index 47824e924..e2adace26 100644 --- a/modoboa/core/tests/stupid_extension_2/modo_extension.py +++ b/modoboa/core/tests/stupid_extension_2/modo_extension.py @@ -1,5 +1,7 @@ """A stupid extension used for tests.""" +from __future__ import unicode_literals + from modoboa.core import extensions @@ -11,4 +13,5 @@ class StupidExtension2(extensions.ModoExtension): version = "1.0.0" description = "A stupid extension" + extensions.exts_pool.register_extension(StupidExtension2, show=False) diff --git a/modoboa/core/tests/stupid_extension_2/urls.py b/modoboa/core/tests/stupid_extension_2/urls.py index a7996a92b..43f39f251 100644 --- a/modoboa/core/tests/stupid_extension_2/urls.py +++ b/modoboa/core/tests/stupid_extension_2/urls.py @@ -1,5 +1,7 @@ """Custom urls.""" +from __future__ import unicode_literals + from django.conf.urls import url from . import views diff --git a/modoboa/core/tests/stupid_extension_2/views.py b/modoboa/core/tests/stupid_extension_2/views.py index 4a5f6dd70..25d1b2268 100644 --- a/modoboa/core/tests/stupid_extension_2/views.py +++ b/modoboa/core/tests/stupid_extension_2/views.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django import http diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index 764dadaf0..e3e9a47d0 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -1,5 +1,7 @@ """Tests for core application.""" +from __future__ import unicode_literals + import smtplib from mock import patch diff --git a/modoboa/core/tests/test_core.py b/modoboa/core/tests/test_core.py index 2fae9c971..217e379a3 100644 --- a/modoboa/core/tests/test_core.py +++ b/modoboa/core/tests/test_core.py @@ -1,5 +1,7 @@ """Tests for core application.""" +from __future__ import unicode_literals + import httmock from dateutil.relativedelta import relativedelta diff --git a/modoboa/core/tests/test_extensions.py b/modoboa/core/tests/test_extensions.py index d220f3318..2ceee2307 100644 --- a/modoboa/core/tests/test_extensions.py +++ b/modoboa/core/tests/test_extensions.py @@ -1,12 +1,13 @@ """Extensions management related test cases.""" +from __future__ import unicode_literals + import os import sys from testfixtures import compare from django.core import management -from django.core.urlresolvers import reverse from django.test import TestCase, override_settings from .. import extensions diff --git a/modoboa/core/tests/test_ldap.py b/modoboa/core/tests/test_ldap.py index 182fae6dd..d7b166594 100644 --- a/modoboa/core/tests/test_ldap.py +++ b/modoboa/core/tests/test_ldap.py @@ -1,5 +1,7 @@ """Tests for core application.""" +from __future__ import unicode_literals + from unittest import skipIf from django.core.urlresolvers import reverse diff --git a/modoboa/core/tests/test_views.py b/modoboa/core/tests/test_views.py index 88ca94b41..177ca940d 100644 --- a/modoboa/core/tests/test_views.py +++ b/modoboa/core/tests/test_views.py @@ -1,6 +1,6 @@ """Core views test cases.""" -import json +from __future__ import unicode_literals from testfixtures import compare @@ -29,7 +29,7 @@ class LoginTestCase(ModoTestCase): signals.get_announcements.connect(announcement) response = self.client.get(reverse("core:login")) self.assertEqual(response.status_code, 200) - self.assertIn("This is an annoucement!", response.content) + self.assertIn("This is an annoucement!", str(response.content)) class DashboardTestCase(ModoTestCase): @@ -51,12 +51,12 @@ class DashboardTestCase(ModoTestCase): url = reverse("core:dashboard") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertIn("Latest news", response.content) + self.assertIn("Latest news", response.content.decode('utf-8')) self.client.logout() self.client.login(username=self.dadmin.username, password="toto") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertIn("Latest news", response.content) + self.assertIn("Latest news", response.content.decode('utf-8')) self.client.logout() self.client.login(username=self.user.username, password="toto") response = self.client.get(url) @@ -100,7 +100,7 @@ class DashboardTestCase(ModoTestCase): url = reverse("core:top_notifications_check") response = self.client.get(url) self.assertEqual(response.status_code, 200) - self.assertEqual(len(json.loads(response.content)), 1) + self.assertEqual(len(response.json()), 1) class SettingsTestCase(ModoTestCase): diff --git a/modoboa/core/urls.py b/modoboa/core/urls.py index 1d086effd..d3f34ba00 100644 --- a/modoboa/core/urls.py +++ b/modoboa/core/urls.py @@ -1,5 +1,7 @@ """Core urls.""" +from __future__ import unicode_literals + from django.conf.urls import url from . import views diff --git a/modoboa/core/utils.py b/modoboa/core/utils.py index 371e5b33a..43af52c8d 100644 --- a/modoboa/core/utils.py +++ b/modoboa/core/utils.py @@ -1,5 +1,7 @@ """Utility functions.""" +from __future__ import unicode_literals + from django.utils.translation import ugettext as _ from versionfield.constants import DEFAULT_NUMBER_BITS diff --git a/modoboa/core/views/__init__.py b/modoboa/core/views/__init__.py index 6df7980e4..45177a4e3 100644 --- a/modoboa/core/views/__init__.py +++ b/modoboa/core/views/__init__.py @@ -1,5 +1,7 @@ """Core views.""" +from __future__ import unicode_literals + from .admin import ( check_top_notifications, information, logs, logs_page, parameters, viewsettings) diff --git a/modoboa/core/views/admin.py b/modoboa/core/views/admin.py index d1bdba05b..6771dedb1 100644 --- a/modoboa/core/views/admin.py +++ b/modoboa/core/views/admin.py @@ -1,5 +1,9 @@ """Views available to super administrators only.""" +from __future__ import unicode_literals + +from functools import reduce + from django.contrib.auth.decorators import ( login_required, user_passes_test ) diff --git a/modoboa/core/views/auth.py b/modoboa/core/views/auth.py index 5ca158c1d..2b049b676 100644 --- a/modoboa/core/views/auth.py +++ b/modoboa/core/views/auth.py @@ -1,6 +1,8 @@ # coding: utf-8 """Core authentication views.""" +from __future__ import unicode_literals + import logging from django.core.urlresolvers import reverse diff --git a/modoboa/core/views/base.py b/modoboa/core/views/base.py index 0129de9ac..2470adfa4 100644 --- a/modoboa/core/views/base.py +++ b/modoboa/core/views/base.py @@ -1,5 +1,7 @@ """Base core views.""" +from __future__ import unicode_literals + from django.core.urlresolvers import reverse from django.views import generic diff --git a/modoboa/core/views/dashboard.py b/modoboa/core/views/dashboard.py index a6c9ceb00..9731096d4 100644 --- a/modoboa/core/views/dashboard.py +++ b/modoboa/core/views/dashboard.py @@ -1,5 +1,7 @@ """Core dashboard views.""" +from __future__ import unicode_literals + from dateutil import parser import feedparser import requests diff --git a/modoboa/core/views/user.py b/modoboa/core/views/user.py index feae310d0..91acaff35 100644 --- a/modoboa/core/views/user.py +++ b/modoboa/core/views/user.py @@ -1,9 +1,12 @@ """Simple user views.""" +from __future__ import unicode_literals + from django.shortcuts import render from django.template.loader import render_to_string from django.utils import translation from django.utils.translation import ugettext as _ +from django.utils.encoding import force_text from django.contrib.auth.decorators import login_required, user_passes_test @@ -39,8 +42,9 @@ def profile(request, tplname='core/user_profile.html'): if form.is_valid(): form.save() if update_password and form.cleaned_data["confirmation"] != "": - request.session["password"] = encrypt( - form.cleaned_data["confirmation"]) + request.session["password"] = force_text(encrypt( + form.cleaned_data["confirmation"] + )) translation.activate(request.user.language) request.session[translation.LANGUAGE_SESSION_KEY] = ( request.user.language) diff --git a/modoboa/lib/__init__.py b/modoboa/lib/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/lib/__init__.py +++ b/modoboa/lib/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/lib/api_client.py b/modoboa/lib/api_client.py index ea44bcf59..098e5f8d6 100644 --- a/modoboa/lib/api_client.py +++ b/modoboa/lib/api_client.py @@ -1,5 +1,7 @@ """A client for Modoboa's public API.""" +from __future__ import unicode_literals + import os import pkg_resources diff --git a/modoboa/lib/authbackends.py b/modoboa/lib/authbackends.py index c3c1c3f8b..df9894f1e 100644 --- a/modoboa/lib/authbackends.py +++ b/modoboa/lib/authbackends.py @@ -1,5 +1,7 @@ """Custom authentication backends.""" +from __future__ import unicode_literals + import smtplib from django.conf import settings @@ -14,11 +16,6 @@ class SMTPBackend(object): def authenticate(self, username=None, password=None): """Check the username/password and return a User.""" - if type(username) is unicode: - username = username.encode("utf-8") - if type(password) is unicode: - password = password.encode("utf-8") - host = getattr(settings, "AUTH_SMTP_SERVER_ADDRESS", "localhost") port = getattr(settings, "AUTH_SMTP_SERVER_PORT", 25) secured_mode = getattr(settings, "AUTH_SMTP_SECURED_MODE", None) diff --git a/modoboa/lib/connections.py b/modoboa/lib/connections.py index 3cb4f175a..09287c5e8 100644 --- a/modoboa/lib/connections.py +++ b/modoboa/lib/connections.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from modoboa.lib.cryptutils import decrypt diff --git a/modoboa/lib/cryptutils.py b/modoboa/lib/cryptutils.py index f0401ded7..1cfcd5089 100644 --- a/modoboa/lib/cryptutils.py +++ b/modoboa/lib/cryptutils.py @@ -1,10 +1,14 @@ # coding: utf-8 """Crypto related utilities.""" +from __future__ import unicode_literals + import base64 import random import string +from django.utils.encoding import force_bytes, force_text + from Crypto.Cipher import AES from modoboa.parameters import tools as param_tools @@ -17,7 +21,7 @@ def random_key(l=16): :return: a string """ punctuation = """!#$%&'()*+,-./:;<=>?@[]^_`{|}~""" - population = string.digits + string.letters + punctuation + population = string.digits + string.ascii_letters + punctuation while True: key = "".join(random.sample(population * l, l)) if len(key) == l: @@ -27,13 +31,15 @@ def random_key(l=16): def encrypt(clear): key = param_tools.get_global_parameter("secret_key", app="core") obj = AES.new(key, AES.MODE_ECB) - if type(clear) is unicode: + if type(clear) is str: clear = clear.encode("utf-8") if len(clear) % AES.block_size: - clear += " " * (AES.block_size - len(clear) % AES.block_size) + clear += ( + force_bytes(" ") * (AES.block_size - len(clear) % AES.block_size) + ) ciph = obj.encrypt(clear) ciph = base64.b64encode(ciph) - return ciph + return force_text(ciph) def decrypt(ciph): diff --git a/modoboa/lib/db_utils.py b/modoboa/lib/db_utils.py index 42c8dd55f..8f6999aad 100644 --- a/modoboa/lib/db_utils.py +++ b/modoboa/lib/db_utils.py @@ -1,5 +1,7 @@ # coding: utf-8 +from __future__ import unicode_literals + from django.conf import settings from django.db import connection from django.utils.translation import ugettext as _ diff --git a/modoboa/lib/email_utils.py b/modoboa/lib/email_utils.py index be8a6a411..a0a3aaa74 100644 --- a/modoboa/lib/email_utils.py +++ b/modoboa/lib/email_utils.py @@ -1,14 +1,18 @@ # coding: utf-8 -import re -import smtplib -import time +from __future__ import unicode_literals + from email.header import Header, decode_header from email.mime.text import MIMEText from email.utils import make_msgid, formatdate, parseaddr +import re +import smtplib +import time from django.conf import settings +from django.utils.encoding import smart_text from django.template.loader import render_to_string +from django.utils import six from modoboa.lib import u2u_decode @@ -74,12 +78,12 @@ class Email(object): """ fname = msg.get_filename() if fname is not None: - if type(fname) is unicode: + if type(fname) is six.text_type: fname = fname.encode("utf-8") decoded = decode_header(fname) value = ( decoded[0][0] if decoded[0][1] is None - else unicode(decoded[0][0], decoded[0][1]) + else smart_text(decoded[0][0], decoded[0][1]) ) else: value = "part_%s" % level @@ -321,7 +325,7 @@ def __sendmail(sender, rcpt, msgstring, server='localhost', port=25): s = smtplib.SMTP(server, port) s.sendmail(sender, [rcpt], msgstring) s.quit() - except smtplib.SMTPException, e: + except smtplib.SMTPException as e: return False, "SMTP error: %s" % str(e) return True, None @@ -360,7 +364,7 @@ def sendmail_fromfile(sender, rcpt, fname): """ try: fp = open(fname) - except IOError, e: + except IOError as e: return False, str(e) content = """From: %s diff --git a/modoboa/lib/exceptions.py b/modoboa/lib/exceptions.py index 82c1135f0..4bdd15896 100644 --- a/modoboa/lib/exceptions.py +++ b/modoboa/lib/exceptions.py @@ -4,6 +4,9 @@ ----------------------------------------------- """ + +from __future__ import unicode_literals + from django.utils.translation import ugettext as _ diff --git a/modoboa/lib/fields.py b/modoboa/lib/fields.py index c5e81ff11..4dcaf2998 100644 --- a/modoboa/lib/fields.py +++ b/modoboa/lib/fields.py @@ -1,5 +1,7 @@ """Custom form fields.""" +from __future__ import unicode_literals + from django import forms from django.utils.translation import ugettext_lazy diff --git a/modoboa/lib/form_utils.py b/modoboa/lib/form_utils.py index f04c6f5fc..e2b7144b1 100644 --- a/modoboa/lib/form_utils.py +++ b/modoboa/lib/form_utils.py @@ -2,6 +2,8 @@ """Form management utilities.""" +from __future__ import unicode_literals + import abc from collections import OrderedDict import re @@ -11,7 +13,7 @@ from django.forms.fields import Field from django.forms.widgets import RadioSelect from django.forms.widgets import RadioChoiceInput from django.shortcuts import render -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text, force_str from django.utils.html import conditional_escape from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _, ugettext_lazy @@ -20,6 +22,9 @@ from modoboa.lib.exceptions import BadRequest from modoboa.lib.web_utils import render_to_json_response +ABC = abc.ABCMeta(force_str('ABC'), (object,), {}) + + class WizardStep(object): """A wizard step.""" @@ -64,11 +69,9 @@ class WizardStep(object): self.form = self._cls(*args) -class WizardForm(object): +class WizardForm(ABC): """Custom wizard.""" - __metaclass__ = abc.ABCMeta - template_name = "common/wizard_forms.html" def __init__(self, request, submit_button_label=None): @@ -86,7 +89,7 @@ class WizardForm(object): def errors(self): result = {} for step in self.steps: - for name, value in step.form.errors.items(): + for name, value in list(step.form.errors.items()): if name == '__all__': continue result[name] = value @@ -139,7 +142,7 @@ class WizardForm(object): stepid = self._get_step_id() self.create_forms(self.request.POST) statuses = [] - for cpt in xrange(0, stepid): + for cpt in range(0, stepid): if self.steps[cpt].check_access(self): statuses.append(self.steps[cpt].form.is_valid()) if False in statuses: @@ -201,7 +204,7 @@ class DynamicForm(object): if value is not None: self.fields[name].initial = value if pos: - order = self.fields.keys() + order = list(self.fields.keys()) order.remove(name) order.insert(pos, name) self.fields = OrderedDict((key, self.fields[key]) for key in order) @@ -215,7 +218,7 @@ class DynamicForm(object): """ expr = re.compile(r'%s_\d+' % pattern) values = [] - for k, v in qdict.iteritems(): + for k, v in list(qdict.items()): if k == pattern or expr.match(k): values.append((k, v)) @@ -280,7 +283,7 @@ class TabForms(object): """ result = {} for f in self.forms: - for name, value in f['instance'].errors.items(): + for name, value in list(f['instance'].errors.items()): if name == '__all__': continue result[name] = value @@ -375,7 +378,7 @@ class CustomRadioInput(RadioChoiceInput): label_for = ' for="%s"' % self.attrs["id"] else: label_for = "" - choice_label = conditional_escape(force_unicode(self.choice_label)) + choice_label = conditional_escape(force_text(self.choice_label)) return mark_safe( u"" % (label_for, self.tag(), choice_label) @@ -393,7 +396,7 @@ class InlineRadioRenderer(RadioSelect.renderer): def render(self): return mark_safe( - u"\n".join([u"%s\n" % force_unicode(w) for w in self]) + u"\n".join([u"%s\n" % force_text(w) for w in self]) ) diff --git a/modoboa/lib/imap_utf7.py b/modoboa/lib/imap_utf7.py index 2da57f0e9..f90b13a51 100644 --- a/modoboa/lib/imap_utf7.py +++ b/modoboa/lib/imap_utf7.py @@ -51,6 +51,10 @@ Found here: http://svn.plone.org/svn/collective/mxmImapClient/trunk/imapUTF7.py """ +from __future__ import print_function, unicode_literals + +from django.utils.encoding import force_text + import binascii import codecs @@ -89,7 +93,7 @@ def encoder(s): def modified_unbase64(s): b = binascii.a2b_base64(s.replace(',', '/') + '===') - return unicode(b, 'utf-16be') + return force_text(b, encoding='utf-16be') def decoder(s): @@ -127,6 +131,8 @@ class StreamWriter(codecs.StreamWriter): def imap4_utf_7(name): if name == 'imap4-utf-7': return (encoder, decoder, StreamReader, StreamWriter) + + codecs.register(imap4_utf_7) @@ -172,29 +178,29 @@ if __name__ == '__main__': # print u'båx'.encode('imap4-utf-7') # print 'expected b&AOU-x' - print '#######' - print 'bøx' + print('#######') + print('bøx') e = imapUTF7Encode(u'bøx') - print e - print imapUTF7Decode(e).encode('latin-1') + print(e) + print(imapUTF7Decode(e).encode('latin-1')) - print '#######' - print 'båx' + print('#######') + print('båx') e = imapUTF7Encode(u'båx') - print e - print imapUTF7Decode(e).encode('latin-1') + print(e) + print(imapUTF7Decode(e).encode('latin-1')) - print '#######' - print '~/bågø' + print('#######') + print('~/bågø') e = imapUTF7Encode(u'~/bågø') - print e - print imapUTF7Decode(e).encode('latin-1') + print(e) + print(imapUTF7Decode(e).encode('latin-1')) - print '#######' - print 'Ting & Såger' + print('#######') + print('Ting & Såger') e = imapUTF7Encode(u'Ting & Såger') - print e - print imapUTF7Decode(e).encode('latin-1') + print(e) + print(imapUTF7Decode(e).encode('latin-1')) # e = imapUTF7Decode('b&AOU-x') # print e.encode('latin-1') @@ -202,13 +208,13 @@ if __name__ == '__main__': # e = imapUTF7Decode('b&APg-x') # print e.encode('latin-1') - print '#######' - print '~/Følder/mailbåx & stuff + more' + print('#######') + print('~/Følder/mailbåx & stuff + more') n = u'~/Følder/mailbåx & stuff + more' e = imapUTF7Encode(n) - print e - print imapUTF7Decode(e).encode('latin-1') + print(e) + print(imapUTF7Decode(e).encode('latin-1')) - print '#######' - print '~peter/mail/&ZeVnLIqe-/&U,BTFw-' - print imapUTF7Decode('~peter/mail/&ZeVnLIqe-/&U,BTFw-').encode('utf-8') + print('#######') + print('~peter/mail/&ZeVnLIqe-/&U,BTFw-') + print(imapUTF7Decode('~peter/mail/&ZeVnLIqe-/&U,BTFw-').encode('utf-8')) diff --git a/modoboa/lib/ldap_utils.py b/modoboa/lib/ldap_utils.py index 8fc210eb2..57767b69f 100644 --- a/modoboa/lib/ldap_utils.py +++ b/modoboa/lib/ldap_utils.py @@ -32,10 +32,15 @@ Extracted from `this blog one instance to another. """ + +from __future__ import unicode_literals + import ldap from django.conf import settings from django.utils.translation import ugettext as _ +from django.utils.encoding import force_bytes +from django.utils import six from modoboa.core.password_hashers import get_password_hasher from modoboa.lib.exceptions import InternalError @@ -65,7 +70,10 @@ class LDAPAuthBackend(object): def _get_conn(self, dn, password): """Get a connection from the server.""" - conn = ldap.initialize(self.server_uri) + kwargs = {} + if six.PY3: + kwargs['bytes_mode'] = False + conn = ldap.initialize(self.server_uri, **kwargs) conn.set_option(ldap.OPT_X_TLS_DEMAND, True) conn.set_option(ldap.OPT_DEBUG_LEVEL, 255) conn.simple_bind_s(dn, password) @@ -117,7 +125,7 @@ class LDAPAuthBackend(object): ) ldif = [(ldap.MOD_REPLACE, self.pwd_attr, - self._crypt_password(newpassword))] + force_bytes(self._crypt_password(newpassword)))] try: self.conn.modify_s(user_dn, ldif) except ldap.LDAPError as e: diff --git a/modoboa/lib/listing.py b/modoboa/lib/listing.py index e8f38f84d..c77e7dcf1 100644 --- a/modoboa/lib/listing.py +++ b/modoboa/lib/listing.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from modoboa.parameters import tools as param_tools diff --git a/modoboa/lib/middleware.py b/modoboa/lib/middleware.py index 365d12f68..6fd7c77f2 100644 --- a/modoboa/lib/middleware.py +++ b/modoboa/lib/middleware.py @@ -2,7 +2,10 @@ """Custom middlewares.""" +from __future__ import unicode_literals + from django.http import HttpResponseRedirect +from django.utils.encoding import smart_text from modoboa.lib.exceptions import ModoboaException from modoboa.lib import signals as lib_signals @@ -30,14 +33,14 @@ class CommonExceptionCatcher(object): if request.is_ajax() or "/api/" in request.path: if exception.http_code is None: return ajax_response( - request, status="ko", respmsg=unicode(exception), + request, status="ko", respmsg=smart_text(exception), norefresh=True ) return render_to_json_response( - unicode(exception), status=exception.http_code + smart_text(exception), status=exception.http_code ) return _render_error( - request, user_context=dict(error=unicode(exception)) + request, user_context=dict(error=smart_text(exception)) ) diff --git a/modoboa/lib/migrations/__init__.py b/modoboa/lib/migrations/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/lib/migrations/__init__.py +++ b/modoboa/lib/migrations/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/lib/paginator.py b/modoboa/lib/paginator.py index 79ecf94dd..2c5658a54 100644 --- a/modoboa/lib/paginator.py +++ b/modoboa/lib/paginator.py @@ -2,6 +2,8 @@ Custom pagination tools. """ +from __future__ import unicode_literals + class Page(object): diff --git a/modoboa/lib/permissions.py b/modoboa/lib/permissions.py index 202dc9496..d27bcdf2b 100644 --- a/modoboa/lib/permissions.py +++ b/modoboa/lib/permissions.py @@ -1,6 +1,8 @@ # coding: utf-8 """Object level permissions.""" +from __future__ import unicode_literals + from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType @@ -143,7 +145,7 @@ def get_object_owner(obj): def add_permissions_to_group(group, permissions): """Add the specified permissions to a django group.""" - if isinstance(group, basestring): + if isinstance(group, str): group = Group.objects.get(name=group) for appname, modelname, permname in permissions: diff --git a/modoboa/lib/signals.py b/modoboa/lib/signals.py index 2a114e8c3..f203de7c3 100644 --- a/modoboa/lib/signals.py +++ b/modoboa/lib/signals.py @@ -1,5 +1,7 @@ """Custom signals.""" +from __future__ import unicode_literals + from threading import local _local_store = local() diff --git a/modoboa/lib/singleton.py b/modoboa/lib/singleton.py index 3fd8f0c17..da4210794 100644 --- a/modoboa/lib/singleton.py +++ b/modoboa/lib/singleton.py @@ -1,4 +1,4 @@ - +from __future__ import unicode_literals # works in Python 2 & 3 class _Singleton(type): diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py index 9fc62187b..aff56e97a 100644 --- a/modoboa/lib/sysutils.py +++ b/modoboa/lib/sysutils.py @@ -4,6 +4,9 @@ This module extra functions/shortcuts to communicate with the system (executing commands, etc.) """ + +from __future__ import unicode_literals + import subprocess import inspect import re diff --git a/modoboa/lib/templatetags/__init__.py b/modoboa/lib/templatetags/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/lib/templatetags/__init__.py +++ b/modoboa/lib/templatetags/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/lib/templatetags/form_tags.py b/modoboa/lib/templatetags/form_tags.py index 213dd17ad..11db3bf90 100644 --- a/modoboa/lib/templatetags/form_tags.py +++ b/modoboa/lib/templatetags/form_tags.py @@ -2,8 +2,11 @@ Form rendering tags. """ +from __future__ import unicode_literals + from django import forms from django import template +from django.utils.encoding import smart_text from django.utils.safestring import mark_safe from django.template.loader import render_to_string @@ -43,7 +46,7 @@ def render_field( from modoboa.core.templatetags.core_tags import visirule if type(field.field) is SeparatorField: - return "