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 "%s" % (visirule(field), unicode(field.label)) + return "%s" % (visirule(field), smart_text(field.label)) configure_field_classes(field) context = { "field": field, "help_display_mode": help_display_mode, diff --git a/modoboa/lib/templatetags/lib_tags.py b/modoboa/lib/templatetags/lib_tags.py index 530c272bf..ef7a9da2d 100644 --- a/modoboa/lib/templatetags/lib_tags.py +++ b/modoboa/lib/templatetags/lib_tags.py @@ -1,6 +1,8 @@ # coding: utf-8 """Custom template tags.""" +from __future__ import unicode_literals + from datetime import datetime from django import template @@ -16,7 +18,7 @@ register = template.Library() @register.simple_tag def join(items, sep=','): res = "" - for k, v in items.iteritems(): + for k, v in list(items.items()): if res != "": res += sep res += "%s : '%s'" % (k, v) diff --git a/modoboa/lib/test_utils.py b/modoboa/lib/test_utils.py index e4bb1e4d9..640c199f0 100644 --- a/modoboa/lib/test_utils.py +++ b/modoboa/lib/test_utils.py @@ -1,5 +1,7 @@ """Tools for top level testing (ie. when ran by Travis).""" +from __future__ import unicode_literals + import os import shutil import tempfile diff --git a/modoboa/lib/tests.py b/modoboa/lib/tests.py index c2544717c..e86d6a405 100644 --- a/modoboa/lib/tests.py +++ b/modoboa/lib/tests.py @@ -1,7 +1,7 @@ # coding: utf-8 """Testing utilities.""" -import json +from __future__ import unicode_literals from django.core import management from django.test import TestCase @@ -64,7 +64,7 @@ class ModoTestCase(ParametersMixin, TestCase): response = getattr(self.client, method)( url, params, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, status) - return json.loads(response.content) + return response.json() def ajax_post(self, *args, **kwargs): return self.ajax_request('post', *args, **kwargs) diff --git a/modoboa/lib/u2u_decode.py b/modoboa/lib/u2u_decode.py index 085e714e8..39c498977 100644 --- a/modoboa/lib/u2u_decode.py +++ b/modoboa/lib/u2u_decode.py @@ -2,9 +2,13 @@ """Unstructured rfc2047 header to unicode.""" +from __future__ import unicode_literals + from email.header import decode_header, make_header import re +from django.utils.encoding import smart_text + # check spaces between encoded_words (and strip them) sre = re.compile(r'\?=[ \t]+=\?') # re pat for MIME encoded_word (without trailing spaces) @@ -24,14 +28,14 @@ def decode_mime(m): """Substitute matching encoded_word with unicode equiv.""" h = decode_header(clean_spaces(m)) try: - u = unicode(make_header(h)) + u = smart_text(make_header(h)) except (LookupError, UnicodeDecodeError): return m.group(0) return u def u2u_decode(s): - ur"""utility function for (final) decoding of mime header + """utility function for (final) decoding of mime header note: resulting string is in one line (no \n within) note2: spaces between enc_words are stripped (see RFC2047) diff --git a/modoboa/lib/validators.py b/modoboa/lib/validators.py index 86fbbc85b..c440eb805 100644 --- a/modoboa/lib/validators.py +++ b/modoboa/lib/validators.py @@ -1,5 +1,7 @@ """Field validators.""" +from __future__ import unicode_literals + import re from django.core.exceptions import ValidationError @@ -44,9 +46,9 @@ class UTF8EmailValidator(EmailValidator): ascii_set = u"-!#$%&'*+/=?^_`{}|~0-9A-Z" user_regex_raw = ( # dot-atom - ur"^[" + ascii_set + ul + "]+(\.[" + ascii_set + ul + "]+)*\Z" + r"^[" + ascii_set + ul + "]+(\.[" + ascii_set + ul + "]+)*\Z" # quoted-string - ur'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)' + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)' ) user_regex = re.compile("(" + user_regex_raw, re.IGNORECASE) @@ -58,6 +60,6 @@ class UTF8AndEmptyUserEmailValidator(UTF8EmailValidator): """Same as upper + allows empty local part.""" user_regex = re.compile( - ur"(^$|" + UTF8EmailValidator.user_regex_raw, re.IGNORECASE) + r"(^$|" + UTF8EmailValidator.user_regex_raw, re.IGNORECASE) validate_utf8_and_empty_user_email = UTF8AndEmptyUserEmailValidator() diff --git a/modoboa/lib/web_utils.py b/modoboa/lib/web_utils.py index 1099b6dac..555a1ebf4 100644 --- a/modoboa/lib/web_utils.py +++ b/modoboa/lib/web_utils.py @@ -4,6 +4,8 @@ This module contains extra functions/shortcuts used to render HTML. """ +from __future__ import unicode_literals + import json import re import sys @@ -36,7 +38,7 @@ def getctx(status, level=1, callback=None, **kwargs): else: callername = callback ctx = {"status": status, "callback": callername} - for kw, v in kwargs.iteritems(): + for kw, v in list(kwargs.items()): ctx[kw] = v return ctx @@ -60,7 +62,7 @@ def ajax_response(request, status="ok", respmsg=None, :param kwargs: dict used for template rendering """ ctx = {} - for k, v in kwargs.iteritems(): + for k, v in list(kwargs.items()): ctx[k] = v if template is not None: content = render_to_string(template, ctx, request) diff --git a/modoboa/limits/__init__.py b/modoboa/limits/__init__.py index 1816137f6..990d4c137 100644 --- a/modoboa/limits/__init__.py +++ b/modoboa/limits/__init__.py @@ -1 +1,3 @@ +from __future__ import unicode_literals + default_app_config = "modoboa.limits.apps.LimitsConfig" diff --git a/modoboa/limits/app_settings.py b/modoboa/limits/app_settings.py index cf77296b3..9a724e671 100644 --- a/modoboa/limits/app_settings.py +++ b/modoboa/limits/app_settings.py @@ -1,5 +1,7 @@ """Custom settings.""" +from __future__ import unicode_literals + from django import forms from django.utils.translation import ugettext_lazy as _ diff --git a/modoboa/limits/apps.py b/modoboa/limits/apps.py index f91abd69a..4716d1f19 100644 --- a/modoboa/limits/apps.py +++ b/modoboa/limits/apps.py @@ -1,5 +1,7 @@ """App config for limits.""" +from __future__ import unicode_literals + from django.apps import AppConfig from django.utils.translation import ugettext as _ diff --git a/modoboa/limits/constants.py b/modoboa/limits/constants.py index 069729e24..f12d5818f 100644 --- a/modoboa/limits/constants.py +++ b/modoboa/limits/constants.py @@ -1,5 +1,7 @@ """Modoboa limits constants.""" +from __future__ import unicode_literals + import collections from django.utils.translation import ugettext_lazy as _ diff --git a/modoboa/limits/forms.py b/modoboa/limits/forms.py index 2958b201c..a30d86f88 100644 --- a/modoboa/limits/forms.py +++ b/modoboa/limits/forms.py @@ -2,6 +2,8 @@ """Custom forms.""" +from __future__ import unicode_literals + from django import forms from django.utils.translation import ugettext as _ @@ -36,14 +38,14 @@ class ResourcePoolForm(forms.Form): cleaned_data = super(ResourcePoolForm, self).clean() if self.errors: return cleaned_data - for lname in self.fields.keys(): + for lname in list(self.fields.keys()): if cleaned_data[lname] < -1: self.add_error(lname, _("Invalid limit")) return cleaned_data def load_from_user(self, user): """Load limit values from given user.""" - for fieldname in self.fields.keys(): + for fieldname in list(self.fields.keys()): lname = fieldname.replace("_limit", "") self.fields[fieldname].initial = ( user.userobjectlimit_set.get(name=lname).max_value) @@ -75,7 +77,7 @@ class DomainLimitsForm(forms.Form): ) if not self.domain: return - for fieldname in self.fields.keys(): + for fieldname in list(self.fields.keys()): lname = fieldname.replace("_limit", "") self.fields[fieldname].initial = ( self.domain.domainobjectlimit_set.get(name=lname).max_value) @@ -85,7 +87,7 @@ class DomainLimitsForm(forms.Form): cleaned_data = super(DomainLimitsForm, self).clean() if self.errors: return cleaned_data - for lname in self.fields.keys(): + for lname in list(self.fields.keys()): if cleaned_data[lname] < -1: self.add_error(lname, _("Invalid limit")) return cleaned_data diff --git a/modoboa/limits/handlers.py b/modoboa/limits/handlers.py index 29bb1b776..8602f2bc6 100644 --- a/modoboa/limits/handlers.py +++ b/modoboa/limits/handlers.py @@ -1,5 +1,7 @@ """Django signal handlers for limits.""" +from __future__ import unicode_literals + from django.db.models import signals from django.dispatch import receiver from django.template.loader import render_to_string diff --git a/modoboa/limits/lib.py b/modoboa/limits/lib.py index 35af3b584..500cdf454 100644 --- a/modoboa/limits/lib.py +++ b/modoboa/limits/lib.py @@ -4,6 +4,9 @@ ------------------------------- """ + +from __future__ import unicode_literals + from django.utils.translation import ugettext as _ from modoboa.lib.exceptions import ModoboaException diff --git a/modoboa/limits/migrations/__init__.py b/modoboa/limits/migrations/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/limits/migrations/__init__.py +++ b/modoboa/limits/migrations/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/limits/models.py b/modoboa/limits/models.py index 58e3e8c0e..0797a7557 100644 --- a/modoboa/limits/models.py +++ b/modoboa/limits/models.py @@ -2,6 +2,8 @@ """Models for the limits extensions.""" +from __future__ import unicode_literals + from django.conf import settings from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -72,7 +74,7 @@ class UserObjectLimit(ObjectLimitMixin, models.Model): content_type = models.ForeignKey("contenttypes.ContentType") max_value = models.IntegerField(default=0) - class Meta: + class Meta(object): unique_together = (("user", "name"), ) @property @@ -134,7 +136,7 @@ class DomainObjectLimit(ObjectLimitMixin, models.Model): name = models.CharField(max_length=254) max_value = models.IntegerField(default=0) - class Meta: + class Meta(object): unique_together = (("domain", "name"), ) @property diff --git a/modoboa/limits/serializers.py b/modoboa/limits/serializers.py index c11205a92..8b447ad71 100644 --- a/modoboa/limits/serializers.py +++ b/modoboa/limits/serializers.py @@ -1,5 +1,7 @@ """Limits serializers.""" +from __future__ import unicode_literals + from rest_framework import serializers from . import lib @@ -24,7 +26,7 @@ class ResourcesSerializer(serializers.Serializer): def update(self, instance, validated_data): """Update limits.""" user = self.context["request"].user - for name, max_value in validated_data.items(): + for name, max_value in list(validated_data.items()): limit = instance.userobjectlimit_set.get(name=name) if not user.is_superuser: lib.allocate_resources_from_user(limit, user, max_value) diff --git a/modoboa/limits/tests/__init__.py b/modoboa/limits/tests/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/limits/tests/__init__.py +++ b/modoboa/limits/tests/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/limits/tests/test_api.py b/modoboa/limits/tests/test_api.py index 5917c777f..ecfbe9d8f 100644 --- a/modoboa/limits/tests/test_api.py +++ b/modoboa/limits/tests/test_api.py @@ -1,6 +1,8 @@ # coding: utf-8 """Test cases for the limits extension.""" +from __future__ import unicode_literals + from testfixtures import compare from django.core.urlresolvers import reverse @@ -101,7 +103,9 @@ class APIAdminLimitsTestCase(lib_tests.ModoAPITestCase): data["name"] = "test5.com" response = self.client.post(url, data, format="json") self.assertEqual(response.status_code, 403) - self.assertEqual(response.content, '"Domains: limit reached"') + self.assertEqual( + response.content.decode('utf-8'), '"Domains: limit reached"' + ) self.client.delete( reverse("api:domain-detail", @@ -110,7 +114,7 @@ class APIAdminLimitsTestCase(lib_tests.ModoAPITestCase): response = self.client.post(url, data, format="json") self.assertEqual(response.status_code, 400) self.assertEqual( - response.content, + response.content.decode(), '"You\'re not allowed to define unlimited values"') def test_domain_aliases_limit(self): diff --git a/modoboa/limits/tests/test_domain_limits.py b/modoboa/limits/tests/test_domain_limits.py index dede7d468..0122c418f 100644 --- a/modoboa/limits/tests/test_domain_limits.py +++ b/modoboa/limits/tests/test_domain_limits.py @@ -1,6 +1,8 @@ # coding: utf-8 """Test cases for the limits extension.""" +from __future__ import unicode_literals + from django.core.urlresolvers import reverse from modoboa.admin import factories as admin_factories diff --git a/modoboa/limits/tests/test_import.py b/modoboa/limits/tests/test_import.py index 0cc11e39a..dc743b438 100644 --- a/modoboa/limits/tests/test_import.py +++ b/modoboa/limits/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 @@ -28,7 +31,7 @@ class LimitImportTestCase(ModoTestCase): """Check domain aliases limit.""" self.client.force_login(self.reseller) self.assertFalse(limit.is_exceeded()) - f = ContentFile(b"""domainalias; domalias1.com; {domain}; True + f = ContentFile("""domainalias; domalias1.com; {domain}; True domainalias; domalias2.com; {domain}; True """.format(domain=self.domain), name="domains.csv") self.client.post( @@ -53,7 +56,7 @@ domainalias; domalias2.com; {domain}; True self.client.force_login(self.reseller) self.assertFalse(limit.is_exceeded()) content = "" - for cpt in xrange(initial_count, 2): + for cpt in range(initial_count, 2): content += ( "account; admin{cpt}@{domain}; toto; User; One; True; " "DomainAdmins; user{cpt}@{domain}; 5; {domain}\n" @@ -67,7 +70,7 @@ domainalias; domalias2.com; {domain}; True ) self.assertTrue(limit.is_exceeded()) - f = ContentFile(b"""account; admin3@{domain}; toto; User; One; True; DomainAdmins; admin3@{domain}; 5; {domain} + f = ContentFile("""account; admin3@{domain}; toto; User; One; True; DomainAdmins; admin3@{domain}; 5; {domain} """.format(domain=self.domain), name="domain_admins.csv") response = self.client.post( reverse("admin:identity_import"), { @@ -108,7 +111,7 @@ account; user3@{domain}; toto; User; One; True; SimpleUsers; user3@{domain}; 5 """Check mailbox aliases limit.""" self.client.force_login(self.dadmin) self.assertFalse(limit.is_exceeded()) - f = ContentFile(b"""alias; alias1@{domain}; True; user@{domain} + f = ContentFile("""alias; alias1@{domain}; True; user@{domain} alias; alias2@{domain}; True; user@{domain} """.format(domain=self.domain), name="aliases.csv") response = self.client.post( diff --git a/modoboa/limits/tests/test_user_limits.py b/modoboa/limits/tests/test_user_limits.py index c871e0b58..e57618755 100644 --- a/modoboa/limits/tests/test_user_limits.py +++ b/modoboa/limits/tests/test_user_limits.py @@ -1,6 +1,8 @@ # coding: utf-8 """Test cases for the limits extension.""" +from __future__ import unicode_literals + from django.core.urlresolvers import reverse from modoboa.admin.factories import populate_database diff --git a/modoboa/limits/urls_api.py b/modoboa/limits/urls_api.py index de97d5718..dad167523 100644 --- a/modoboa/limits/urls_api.py +++ b/modoboa/limits/urls_api.py @@ -1,5 +1,7 @@ """Limits API urls.""" +from __future__ import unicode_literals + from rest_framework import routers from . import viewsets diff --git a/modoboa/limits/utils.py b/modoboa/limits/utils.py index 64b3358ba..265a579ca 100644 --- a/modoboa/limits/utils.py +++ b/modoboa/limits/utils.py @@ -1,16 +1,18 @@ """Modoboa limits utilities.""" +from __future__ import unicode_literals + from . import constants def get_user_limit_templates(): """Return defined templates.""" - return constants.DEFAULT_USER_LIMITS.items() + return list(constants.DEFAULT_USER_LIMITS.items()) def get_domain_limit_templates(): """Return defined templates.""" - return constants.DEFAULT_DOMAIN_LIMITS.items() + return list(constants.DEFAULT_DOMAIN_LIMITS.items()) def move_pool_resource(owner, user): diff --git a/modoboa/limits/views.py b/modoboa/limits/views.py index 57d631c3f..e17c22b7f 100644 --- a/modoboa/limits/views.py +++ b/modoboa/limits/views.py @@ -1 +1,3 @@ # coding: utf-8 + +from __future__ import unicode_literals diff --git a/modoboa/limits/viewsets.py b/modoboa/limits/viewsets.py index 1b7fff120..2d792ca75 100644 --- a/modoboa/limits/viewsets.py +++ b/modoboa/limits/viewsets.py @@ -1,5 +1,7 @@ """Limits API.""" +from __future__ import unicode_literals + from django.contrib.contenttypes.models import ContentType from rest_framework import mixins, viewsets diff --git a/modoboa/parameters/__init__.py b/modoboa/parameters/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/parameters/__init__.py +++ b/modoboa/parameters/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/parameters/forms.py b/modoboa/parameters/forms.py index 2996e96e5..d26996e13 100644 --- a/modoboa/parameters/forms.py +++ b/modoboa/parameters/forms.py @@ -1,5 +1,7 @@ """Parameters forms.""" +from __future__ import unicode_literals + from django import forms from modoboa.lib import db_utils @@ -25,7 +27,7 @@ class GenericParametersForm(forms.Form): self.visirules = {} if self.visibility_rules is not None: - for key, rule in self.visibility_rules.items(): + for key, rule in list(self.visibility_rules.items()): field, value = rule.split("=") visibility = { "field": "id_%s-%s" % (self.app, field), "value": value @@ -68,7 +70,7 @@ class AdminParametersForm(GenericParametersForm): def save(self): """Save parameters to database.""" parameters = {} - for name, value in self.cleaned_data.items(): + for name, value in list(self.cleaned_data.items()): if type(self.fields[name]) is form_utils.SeparatorField: continue parameters[name] = value @@ -99,7 +101,7 @@ class UserParametersForm(GenericParametersForm): def save(self): """Save new values.""" parameters = {} - for name, value in self.cleaned_data.items(): + for name, value in list(self.cleaned_data.items()): if type(self.fields[name]) is form_utils.SeparatorField: continue parameters[name] = value diff --git a/modoboa/parameters/tests.py b/modoboa/parameters/tests.py index 7ce503c2d..36fcdafd1 100644 --- a/modoboa/parameters/tests.py +++ b/modoboa/parameters/tests.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.test import TestCase # Create your tests here. diff --git a/modoboa/parameters/tools.py b/modoboa/parameters/tools.py index 1be26d468..383b17442 100644 --- a/modoboa/parameters/tools.py +++ b/modoboa/parameters/tools.py @@ -1,5 +1,7 @@ """Parameters management.""" +from __future__ import unicode_literals + import copy from modoboa.lib import exceptions @@ -42,9 +44,9 @@ class Registry(object): def _load_default_values(self, level): """Load default values.""" - for app, data in self._registry[level].items(): + for app, data in list(self._registry[level].items()): form = data["formclass"](load_values_from_db=False) - for name, field in form.fields.items(): + for name, field in list(form.fields.items()): if type(field) is form_utils.SeparatorField: continue data["defaults"][name] = field.initial @@ -56,7 +58,8 @@ class Registry(object): if first_app in self._registry[level]: sorted_apps.append(first_app) sorted_apps += sorted( - [a for a in self._registry[level].keys() if a != first_app], + [a for a in list(self._registry[level].keys()) + if a != first_app], key=lambda a: self._registry[level][a]["label"] ) result = [] @@ -126,7 +129,7 @@ class Manager(object): if app is None: app = guess_extension_name() values = registry.get_defaults(self._level, app) - for parameter, value in values.iteritems(): + for parameter, value in list(values.items()): if app in self._parameters: value = self._parameters[app].get(parameter, value) yield (parameter, value) diff --git a/modoboa/relaydomains/__init__.py b/modoboa/relaydomains/__init__.py index 0b2987e99..c9afa7d7b 100644 --- a/modoboa/relaydomains/__init__.py +++ b/modoboa/relaydomains/__init__.py @@ -1 +1,3 @@ +from __future__ import unicode_literals + default_app_config = "modoboa.relaydomains.apps.RelayDomainsConfig" diff --git a/modoboa/relaydomains/app_settings.py b/modoboa/relaydomains/app_settings.py index a6cf7fb4d..ac892234e 100644 --- a/modoboa/relaydomains/app_settings.py +++ b/modoboa/relaydomains/app_settings.py @@ -1,5 +1,7 @@ """Relay domains settings definition.""" +from __future__ import unicode_literals + from django import forms from django.utils.translation import ugettext_lazy diff --git a/modoboa/relaydomains/apps.py b/modoboa/relaydomains/apps.py index 34b229a03..937456f2b 100644 --- a/modoboa/relaydomains/apps.py +++ b/modoboa/relaydomains/apps.py @@ -1,5 +1,7 @@ """AppConfig for relaydomains.""" +from __future__ import unicode_literals + from django.apps import AppConfig from django.utils.translation import ugettext as _ diff --git a/modoboa/relaydomains/constants.py b/modoboa/relaydomains/constants.py index 951bfa1af..1e4b24230 100644 --- a/modoboa/relaydomains/constants.py +++ b/modoboa/relaydomains/constants.py @@ -1,5 +1,7 @@ """Relaydomains app constants.""" +from __future__ import unicode_literals + PERMISSIONS = { "Resellers": [ ("relaydomains", "relaydomain", "add_relaydomain"), diff --git a/modoboa/relaydomains/forms.py b/modoboa/relaydomains/forms.py index abd3652b1..de872f917 100644 --- a/modoboa/relaydomains/forms.py +++ b/modoboa/relaydomains/forms.py @@ -1,5 +1,7 @@ """Postfix relay domains extension forms.""" +from __future__ import unicode_literals + from django import forms from django.core import validators from django.utils.translation import ugettext as _ @@ -22,7 +24,7 @@ class RelayDomainWizardStep(WizardStep): class RelayDomainFormGeneral(forms.ModelForm): """RelayDomain form.""" - class Meta: + class Meta(object): model = RelayDomain exclude = ["domain", "creation", "last_modification"] widgets = { diff --git a/modoboa/relaydomains/handlers.py b/modoboa/relaydomains/handlers.py index 87b8e36e3..2bdd168a9 100644 --- a/modoboa/relaydomains/handlers.py +++ b/modoboa/relaydomains/handlers.py @@ -1,5 +1,7 @@ """Django signal handlers for relaydomains.""" +from __future__ import unicode_literals + from django.conf import settings from django.db.models import signals from django.dispatch import receiver diff --git a/modoboa/relaydomains/lib.py b/modoboa/relaydomains/lib.py index eaa2295de..b32a710ca 100644 --- a/modoboa/relaydomains/lib.py +++ b/modoboa/relaydomains/lib.py @@ -1,5 +1,7 @@ """Internal library.""" +from __future__ import unicode_literals + from .models import RelayDomain diff --git a/modoboa/relaydomains/migrations/__init__.py b/modoboa/relaydomains/migrations/__init__.py index e69de29bb..baffc4882 100644 --- a/modoboa/relaydomains/migrations/__init__.py +++ b/modoboa/relaydomains/migrations/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/modoboa/relaydomains/models.py b/modoboa/relaydomains/models.py index 628e7cd54..4347a2a13 100644 --- a/modoboa/relaydomains/models.py +++ b/modoboa/relaydomains/models.py @@ -1,5 +1,7 @@ """Relay domain related models.""" +from __future__ import unicode_literals + from django.db import models from django.db.models.manager import Manager from django.utils.translation import ugettext as _, ugettext_lazy @@ -50,7 +52,7 @@ class Service(models.Model): objects = ServiceManager() - class Meta: + class Meta(object): db_table = "postfix_relay_domains_service" def __str__(self): @@ -84,7 +86,7 @@ class RelayDomain(admin_models.AdminObject): default=False ) - class Meta: + class Meta(object): ordering = ["domain__name"] db_table = "postfix_relay_domains_relaydomain" diff --git a/modoboa/relaydomains/postfix_maps.py b/modoboa/relaydomains/postfix_maps.py index e91444e7f..fdb084a61 100644 --- a/modoboa/relaydomains/postfix_maps.py +++ b/modoboa/relaydomains/postfix_maps.py @@ -1,5 +1,7 @@ """Map file definitions for postfix.""" +from __future__ import unicode_literals + class RelayDomainsMap(object): diff --git a/modoboa/relaydomains/tests.py b/modoboa/relaydomains/tests.py index 1ca7e151f..fe773212f 100644 --- a/modoboa/relaydomains/tests.py +++ b/modoboa/relaydomains/tests.py @@ -1,6 +1,6 @@ -"""modoboa-admin-relaydomains unit tests.""" +"""relaydomains unit tests.""" -import json +from __future__ import unicode_literals from django.core.files.base import ContentFile from django.core.urlresolvers import reverse @@ -86,7 +86,7 @@ class RelayDomainsTestCase(ModoTestCase, Operations): url = reverse("admin:_domain_list") response = self.client.get(url) self.assertEqual(response.status_code, 200) - content = json.loads(response.content) + content = response.json() self.assertIn("relaydomain.tld", content["rows"]) self.assertIn("Relay Domain", content["rows"]) diff --git a/modoboa/relaydomains/urls.py b/modoboa/relaydomains/urls.py index e8dcd2cbd..e71b7b830 100644 --- a/modoboa/relaydomains/urls.py +++ b/modoboa/relaydomains/urls.py @@ -1,5 +1,7 @@ """Relay domains urls.""" +from __future__ import unicode_literals + from django.conf.urls import url from . import views diff --git a/modoboa/relaydomains/views.py b/modoboa/relaydomains/views.py index 6e30d2ce9..d115dc90c 100644 --- a/modoboa/relaydomains/views.py +++ b/modoboa/relaydomains/views.py @@ -1,5 +1,7 @@ """Custom views.""" +from __future__ import unicode_literals + from django.contrib.auth.decorators import ( login_required, permission_required ) diff --git a/modoboa/test_settings.py b/modoboa/test_settings.py index 06c111971..ba67caaf9 100644 --- a/modoboa/test_settings.py +++ b/modoboa/test_settings.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- + +from __future__ import unicode_literals + import os # Database diff --git a/modoboa/urls.py b/modoboa/urls.py index 5d0d350f8..34635fea7 100644 --- a/modoboa/urls.py +++ b/modoboa/urls.py @@ -1,3 +1,7 @@ +from __future__ import unicode_literals + +from functools import reduce + from django.conf import settings from django.conf.urls import include, url from django.views.i18n import JavaScriptCatalog diff --git a/modoboa/urls_api.py b/modoboa/urls_api.py index 64d6aaa2f..ef93a9914 100644 --- a/modoboa/urls_api.py +++ b/modoboa/urls_api.py @@ -1,5 +1,7 @@ """External API urls.""" +from __future__ import unicode_literals + from django.conf.urls import include, url from modoboa.core.extensions import exts_pool diff --git a/requirements.txt b/requirements.txt index ea5733c8a..08dcfb547 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,13 +15,13 @@ djangorestframework==3.6.2 argparse # bcrypt requires libffi-dev bcrypt==2.0.0 -dnspython==1.12.0 +dnspython==1.15.0 feedparser==5.2.1 gevent==1.2.1 ipaddress jsonfield==1.0.3 passlib==1.6.5 -progressbar==2.3 +progressbar33==2.4 py-dateutil==2.2 pycrypto pytz diff --git a/test_project/manage.py b/test_project/manage.py index 0fc36a34c..7fae8c557 100755 --- a/test_project/manage.py +++ b/test_project/manage.py @@ -1,4 +1,7 @@ #!/usr/bin/env python + +from __future__ import unicode_literals + import os import sys diff --git a/test_project/test_project/__init__.py b/test_project/test_project/__init__.py index e69de29bb..baffc4882 100644 --- a/test_project/test_project/__init__.py +++ b/test_project/test_project/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index d19515f9a..7493ab588 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -8,6 +8,8 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.6/ref/settings/ """ +from __future__ import unicode_literals + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os from logging.handlers import SysLogHandler diff --git a/test_project/test_project/urls.py b/test_project/test_project/urls.py index 1a4d5cea7..1317f7c7b 100644 --- a/test_project/test_project/urls.py +++ b/test_project/test_project/urls.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from django.conf.urls import include, url urlpatterns = [ diff --git a/test_project/test_project/wsgi.py b/test_project/test_project/wsgi.py index 5ce7da76a..e63b59d9e 100644 --- a/test_project/test_project/wsgi.py +++ b/test_project/test_project/wsgi.py @@ -7,6 +7,8 @@ For more information on this file, see https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ """ +from __future__ import unicode_literals + import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") diff --git a/tox.ini b/tox.ini index ed4170764..1f949ddfd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] -envlist = py27 +envlist = py27,py34 [testenv] changedir=test_project commands = python ../tests.py - coverage run --source modoboa manage.py test modoboa.core modoboa.lib modoboa.admin.tests modoboa.limits modoboa.relaydomains + coverage run --source modoboa manage.py test modoboa.core modoboa.lib modoboa.admin modoboa.limits modoboa.relaydomains + coverage report --show-missing deps = -r{toxinidir}/test-requirements.txt