Merge pull request #1124 from modoboa/refacto/python3

Refacto/python3
This commit is contained in:
Antoine Nguyen
2017-05-16 17:34:23 +02:00
committed by GitHub
190 changed files with 683 additions and 245 deletions
+2
View File
@@ -10,3 +10,5 @@ DEV
.idea
.ropeproject
modoboa.iml
.tox
modoboa_test
+2 -1
View File
@@ -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
+2
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env python
# coding: utf-8
from __future__ import unicode_literals
from modoboa.core.commands import handle_command_line
if __name__ == "__main__":
-1
View File
@@ -1,2 +1 @@
python-ldap
django-auth-ldap
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
+2
View File
@@ -1 +1,3 @@
from __future__ import unicode_literals
default_app_config = "modoboa.admin.apps.AdminConfig"
+2
View File
@@ -1,5 +1,7 @@
"""Admin API."""
from __future__ import unicode_literals
from django import http
from django.contrib.contenttypes.models import ContentType
+4
View File
@@ -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:
+2
View File
@@ -1,5 +1,7 @@
"""AppConfig for admin."""
from __future__ import unicode_literals
from django.apps import AppConfig
from .app_settings import load_admin_settings
+2
View File
@@ -1,5 +1,7 @@
"""Admin constants."""
from __future__ import unicode_literals
DNSBL_PROVIDERS = [
"aspews.ext.sorbs.net",
"b.barracudacentral.org",
+3
View File
@@ -1,6 +1,9 @@
"""
Fixtures factories.
"""
from __future__ import unicode_literals
import factory
from modoboa.core.factories import PermissionFactory, UserFactory
+2
View File
@@ -1,3 +1,5 @@
from __future__ import unicode_literals
from .domain import (
DomainFormGeneral, DomainFormOptions, DomainForm, DomainWizard
)
+3
View File
@@ -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
+2
View File
@@ -1,5 +1,7 @@
"""Forms related to aliases management."""
from __future__ import unicode_literals
from collections import OrderedDict
from django import forms
+9 -3
View File
@@ -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])
+4 -2
View File
@@ -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):
+2
View File
@@ -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
+3 -1
View File
@@ -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):
+3 -1
View File
@@ -1,5 +1,7 @@
"""Django signal handlers for admin."""
from __future__ import unicode_literals
from django.core.urlresolvers import reverse
from django.db.models import signals
from django.dispatch import receiver
@@ -174,7 +176,7 @@ def account_auto_created(sender, user, **kwargs):
@receiver(core_signals.account_exported)
def export_admin_domains(sender, user, **kwargs):
"""Export administered domains too."""
result = [user.mailbox.quota] if hasattr(user, "mailbox") else [""]
result = [user.mailbox.quota] if hasattr(user, "mailbox") else [u""]
if user.role != "DomainAdmins":
return result
return result + [
+2
View File
@@ -2,6 +2,8 @@
"""Internal library for admin."""
from __future__ import unicode_literals
from functools import wraps
from itertools import chain
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
@@ -0,0 +1 @@
from __future__ import unicode_literals
@@ -1,5 +1,7 @@
"""A management command to apply mailbox operations."""
from __future__ import unicode_literals, print_function
import logging
import os
import shutil
@@ -79,7 +81,7 @@ class Command(BaseCommand):
if not code:
return False
with open(path, 'w') as fp:
print >> fp, os.getpid()
print(os.getpid(), file=fp)
return True
def handle(self, *args, **options):
@@ -1,5 +1,7 @@
"""Modoboa main management command."""
from __future__ import unicode_literals
from subcommand.base import SubcommandCommand
from .subcommands._mx import CheckMXRecords
@@ -0,0 +1 @@
from __future__ import unicode_literals
@@ -1,5 +1,7 @@
"""Django management command to export admin objects."""
from __future__ import unicode_literals
import csv
import sys
@@ -1,5 +1,7 @@
"""Django management command to import admin objects."""
from __future__ import unicode_literals
import csv
import os
@@ -1,6 +1,7 @@
"""Management command to check defined domains."""
from datetime import datetime
from __future__ import print_function, unicode_literals
from datetime import timedelta
import ipaddress
@@ -1,7 +1,10 @@
"""Management command to check and fix known problems."""
from __future__ import print_function, unicode_literals
from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils.encoding import smart_str
from modoboa.lib.permissions import grant_access_to_object
from modoboa.lib.permissions import get_object_owner
@@ -110,7 +113,7 @@ class Repair(BaseCommand):
# Load known problems from extensions.
for ext in settings.MODOBOA_APPS:
try:
__import__(ext, locals(), globals(), ["known_problems"])
__import__(ext, locals(), globals(), [smart_str("known_problems")])
except ImportError:
pass
for func in known_problems:
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
+2
View File
@@ -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
+9 -5
View File
@@ -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)
+2
View File
@@ -1,5 +1,7 @@
"""Base admin models."""
from __future__ import unicode_literals
from django.db import models
from django.utils import timezone
+11 -2
View File
@@ -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)
+2
View File
@@ -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
+2
View File
@@ -1,5 +1,7 @@
"""Models related to mailboxes management."""
from __future__ import unicode_literals
import os
import pwd
+2
View File
@@ -1,5 +1,7 @@
"""Map file definitions for postfix."""
from __future__ import unicode_literals
class DomainsMap(object):
+6 -3
View File
@@ -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)
+2
View File
@@ -1,5 +1,7 @@
"""Modoboa admin signals."""
from __future__ import unicode_literals
import django.dispatch
check_extra_account_form = django.dispatch.Signal(
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
+4
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
+6 -4
View File
@@ -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")
+4 -2
View File
@@ -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."""
+19 -17
View File
@@ -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):
+11 -10
View File
@@ -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)
+2
View File
@@ -1,5 +1,7 @@
# coding: utf-8
from __future__ import unicode_literals
from django.core.urlresolvers import reverse
from modoboa.lib.tests import ModoTestCase
+7 -4
View File
@@ -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"
)
+12 -9
View File
@@ -1,4 +1,7 @@
# coding: utf-8
from __future__ import unicode_literals
from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse
@@ -23,7 +26,7 @@ class ImportTestCase(ModoTestCase):
def test_domains_import(self):
response = self.client.get(reverse("admin:domain_import"))
self.assertEqual(response.status_code, 200)
self.assertIn("Provide a CSV", response.content)
self.assertIn("Provide a CSV", response.content.decode())
f = ContentFile(b"""domain; domain1.com; 1000; 100; True
domain; domain2.com; 1000; 200; False
@@ -80,14 +83,14 @@ domainalias;test.alias;test.com;True
"sourcefile": f
}
)
self.assertIn('Object already exists: domainalias', resp.content)
self.assertIn('Object already exists: domainalias', resp.content.decode())
def test_identities_import(self):
response = self.client.get(reverse("admin:identity_import"))
self.assertEqual(response.status_code, 200)
self.assertIn("Provide a CSV", response.content)
self.assertIn("Provide a CSV", response.content.decode())
f = ContentFile(b"""
f = ContentFile("""
account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 0
account; Truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 5; test.com
alias; alias1@test.com; True; user1@test.com
@@ -176,7 +179,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; ; t
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
self.assertIn('wrong quota value', resp.content)
self.assertIn('wrong quota value', resp.content.decode())
def test_import_domain_by_domainadmin(self):
"""Check if a domain admin is not allowed to import a domain."""
@@ -189,7 +192,7 @@ domain; domain2.com; 1000; 200; False
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
self.assertIn("You are not allowed to import domains", resp.content)
self.assertIn("You are not allowed to import domains", resp.content.decode())
f = ContentFile(b"""
domainalias; domalias1.com; test.com; True
""", name="identities.csv")
@@ -198,7 +201,7 @@ domainalias; domalias1.com; test.com; True
{"sourcefile": f, "crypt_password": True}
)
self.assertIn(
"You are not allowed to import domain aliases", resp.content)
"You are not allowed to import domain aliases", resp.content.decode())
def test_import_quota_too_big(self):
self.client.logout()
@@ -210,7 +213,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com; 40
reverse("admin:identity_import"),
{"sourcefile": f, "crypt_password": True}
)
self.assertIn("Domain quota exceeded", resp.content)
self.assertIn("Domain quota exceeded", resp.content.decode())
def test_import_missing_quota(self):
f = ContentFile(b"""
@@ -227,7 +230,7 @@ account; user1@test.com; toto; User; One; True; SimpleUsers; user1@test.com
)
def test_import_duplicate(self):
f = ContentFile(b"""
f = ContentFile("""
account; admin@test.com; toto; Admin; ; True; DomainAdmins; admin@test.com; 0; test.com
account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0; test.com
""", name="identities.csv")
@@ -1,5 +1,7 @@
"""Management command tests."""
from __future__ import unicode_literals
import os
import shutil
import tempfile
+2
View File
@@ -1,5 +1,7 @@
"""Test map files generation."""
from __future__ import unicode_literals
import os
from django.conf import settings
+2
View File
@@ -1,5 +1,7 @@
"""DNSBL related tests."""
from __future__ import unicode_literals
from django.core import management
from django.core.urlresolvers import reverse
from django.test import override_settings
@@ -1,3 +1,5 @@
from __future__ import unicode_literals
from django.core.urlresolvers import reverse
from modoboa.core.models import User
+3
View File
@@ -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
+3 -3
View File
@@ -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"])
+2
View File
@@ -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
+2
View File
@@ -1,5 +1,7 @@
"""Admin API urls."""
from __future__ import unicode_literals
from rest_framework import routers
from . import api
+2
View File
@@ -1,5 +1,7 @@
"""Shortcuts."""
from __future__ import unicode_literals
from .alias import AliasDetailView
from .identity import AccountDetailView
from .dns import DNSBLDomainDetailView, MXDomainDetailView
+2
View File
@@ -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
+2
View File
@@ -1,5 +1,7 @@
"""DNSBL related views."""
from __future__ import unicode_literals
from django.views import generic
from django.utils.translation import ugettext as _
+4
View File
@@ -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
+12 -5
View File
@@ -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)
+2
View File
@@ -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
+16 -2
View File
@@ -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)
+2
View File
@@ -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 _
+2
View File
@@ -1,3 +1,5 @@
"""App config for core."""
from __future__ import unicode_literals
default_app_config = "modoboa.core.apps.CoreConfig"
+3
View File
@@ -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
+2
View File
@@ -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
+5 -2
View File
@@ -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)
+26 -16
View File
@@ -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'
)
+3 -1
View File
@@ -1,5 +1,7 @@
# coding: utf-8
from __future__ import unicode_literals, print_function
import sys
from . import Command
@@ -16,7 +18,7 @@ class HelpCommand(Command):
def handle(self, parsed_args):
if parsed_args.name not in self._commands:
print >>sys.stderr, "Unknown command: %s" % parsed_args.name
print("Unknown command: %s" % parsed_args.name, file=sys.stderr)
sys.exit(1)
cmd = self._commands[parsed_args.name](self._commands)
cmd._parser.prog = "modoboa-admin.py %s" % parsed_args.name
+2
View File
@@ -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"))
+4
View File
@@ -1,5 +1,9 @@
"""Custom context processors."""
from __future__ import unicode_literals
from functools import reduce
from . import signals
+3
View File
@@ -1,4 +1,7 @@
# Development settings
from __future__ import unicode_literals
import os
BOWER_COMPONENTS_ROOT = os.path.join(
+7 -3
View File
@@ -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()
+6 -4
View File
@@ -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"
+3 -1
View File
@@ -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")
+5 -2
View File
@@ -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
}
+2
View File
@@ -1,3 +1,5 @@
from __future__ import unicode_literals
import logging
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
@@ -0,0 +1 @@
from __future__ import unicode_literals
@@ -1,5 +1,7 @@
# coding: utf-8
from __future__ import print_function, unicode_literals
import datetime
import logging
@@ -27,7 +29,7 @@ class Command(BaseCommand):
def __vprint(self, msg):
if not self.verbose:
return
print msg
print(msg)
def handle(self, *args, **options):
if options["debug"]:
@@ -1,5 +1,7 @@
"""Management command to communicate with public API."""
from __future__ import unicode_literals
from django.core.management.base import BaseCommand, CommandError
from modoboa.admin import models as admin_models
@@ -1,4 +1,5 @@
"""Management command to generate/update postfix map files."""
from __future__ import print_function, unicode_literals
import copy
import hashlib
@@ -9,6 +10,7 @@ from django.conf import settings
from django.core.management.base import BaseCommand
from django.template import Context, Template
from django.utils import timezone
from django.utils.encoding import force_bytes
import dj_database_url
@@ -67,7 +69,7 @@ class Command(BaseCommand):
fname not in self.__checksums)
if condition:
return True
with open(path) as fp:
with open(path, mode="rb") as fp:
checksum = hashlib.md5(fp.read()).hexdigest()
return checksum == self.__checksums[fname]["checksum"]
@@ -134,14 +136,14 @@ query = {{ query|safe }}
context["dbhost"] = mapcontent["hosts"]
content = self.get_template(context["dbtype"]).render(
Context(
dict(context.items(),
dict(list(context.items()),
query=getattr(mapobject, context["dbtype"]))
)
)
fullpath = os.path.join(destdir, mapobject.filename)
with open(fullpath, "w") as fp:
fp.write(content)
return hashlib.md5(content).hexdigest()
return hashlib.md5(force_bytes(content)).hexdigest()
def handle(self, *args, **options):
"""Command entry point."""
@@ -160,6 +162,6 @@ query = {{ query|safe }}
force_overwrite=options["force_overwrite"])
checksums[mapobject.filename] = checksum
with open(self.__checksums_file, "w") as fp:
for fname, checksum in checksums.items():
for fname, checksum in list(checksums.items()):
fp.write("{}:{}:{}\n".format(
fname, context["dbtype"], checksum))
@@ -5,6 +5,10 @@
"""
from __future__ import unicode_literals
from functools import reduce
from django.contrib.auth.models import Group
from django.core.management.base import BaseCommand
@@ -57,7 +61,7 @@ class Command(BaseCommand):
extensions.exts_pool.load_all()
groups = constants.PERMISSIONS.keys()
groups = list(constants.PERMISSIONS.keys())
for groupname in groups:
group, created = Group.objects.get_or_create(name=groupname)
results = signals.extra_role_permissions.send(
@@ -70,7 +74,7 @@ class Command(BaseCommand):
continue
add_permissions_to_group(group, permissions)
for extname in extensions.exts_pool.extensions.keys():
for extname in list(extensions.exts_pool.extensions.keys()):
extension = extensions.exts_pool.get_extension(extname)
extension.load_initial_data()
signals.initial_data_loaded.send(
@@ -5,6 +5,8 @@ A simple managemenent command to update the default site.
See `https://docs.djangoproject.com/en/dev/ref/contrib/sites/`_.
"""
from __future__ import unicode_literals
from django.core.management.base import BaseCommand, CommandError
from django.contrib.sites.models import Site
+2
View File
@@ -1,5 +1,7 @@
"""Core middlewares."""
from __future__ import unicode_literals
from . import models
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
+2
View File
@@ -1,5 +1,7 @@
"""Mocks used for testing."""
from __future__ import unicode_literals
import httmock
+17 -14
View File
@@ -1,12 +1,16 @@
"""Core models."""
from __future__ import unicode_literals
from email.header import Header
import re
from django.core.urlresolvers import reverse
from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.encoding import (
python_2_unicode_compatible, smart_bytes, smart_text, force_str, force_text
)
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _, ugettext_lazy
@@ -91,7 +95,7 @@ class User(PermissionsMixin):
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
class Meta(object):
ordering = ["username"]
index_together = [
['email', 'is_active']
@@ -119,8 +123,7 @@ class User(PermissionsMixin):
load_core_settings()
scheme = param_tools.get_global_parameter(
"password_scheme", raise_exception=False)
if isinstance(raw_value, unicode):
raw_value = raw_value.encode("utf-8")
raw_value = smart_bytes(raw_value)
return get_password_hasher(scheme.upper())().encrypt(raw_value)
def set_password(self, raw_value, curvalue=None):
@@ -152,8 +155,7 @@ class User(PermissionsMixin):
match = self.password_expr.match(self.password)
if match is None:
return False
if isinstance(raw_value, unicode):
raw_value = raw_value.encode("utf-8")
raw_value = force_str(raw_value)
scheme = match.group(1)
val2 = match.group(2)
hasher = get_password_hasher(scheme)
@@ -410,13 +412,13 @@ class User(PermissionsMixin):
"""
row = [
"account",
self.username.encode("utf-8"),
self.password.encode("utf-8"),
self.first_name.encode("utf-8"),
self.last_name.encode("utf-8"),
self.is_active,
self.role,
self.email.encode("utf-8")
force_text(self.username),
force_text(self.password),
force_text(self.first_name),
force_text(self.last_name),
force_text(self.is_active),
force_text(self.role),
force_text(self.email)
]
results = signals.account_exported.send(
sender=self.__class__, user=self)
@@ -424,6 +426,7 @@ class User(PermissionsMixin):
row += result[1]
csvwriter.writerow(row)
reversion.register(User)
@@ -455,7 +458,7 @@ class ObjectAccess(models.Model):
content_object = GenericForeignKey('content_type', 'object_id')
is_owner = models.BooleanField(default=False)
class Meta:
class Meta(object):
unique_together = (("user", "content_type", "object_id"),)
def __unicode__(self):
@@ -1,6 +1,9 @@
"""
Password hashers for Modoboa.
"""
from __future__ import unicode_literals
from modoboa.core.password_hashers.base import (
PLAINHasher, CRYPTHasher, MD5Hasher, SHA256Hasher
)
@@ -4,6 +4,8 @@ Advanced (ie. stronger) password hashers.
This module relies on `passlib` to provide more secure hashers.
"""
from __future__ import unicode_literals
from passlib.hash import bcrypt, md5_crypt, sha256_crypt, sha512_crypt
from modoboa.parameters import tools as param_tools
+12 -7
View File
@@ -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.
+2
View File
@@ -1,5 +1,7 @@
"""Modoboa core signals."""
from __future__ import unicode_literals
import django.dispatch
account_auto_created = django.dispatch.Signal(providing_args=["user"])
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
+5 -1
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
from __future__ import unicode_literals
@@ -0,0 +1 @@
from __future__ import unicode_literals
@@ -1,5 +1,7 @@
"""A stupid extension used for tests."""
from __future__ import unicode_literals
from modoboa.core import extensions
@@ -0,0 +1 @@
from __future__ import unicode_literals

Some files were not shown because too many files have changed in this diff Show More