Merge pull request #672 from tonioo/code_style

Code style
This commit is contained in:
Antoine Nguyen
2015-01-16 11:51:40 +01:00
127 changed files with 1487 additions and 658 deletions
+15 -7
View File
@@ -2,9 +2,10 @@
from django import forms
from django.conf import settings
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib.formutils import YesNoField, SeparatorField, InlineRadioSelect
from modoboa.lib.cryptutils import random_key
from modoboa.lib import parameters
from modoboa.lib.cryptutils import random_key
from modoboa.lib.form_utils import YesNoField, SeparatorField, InlineRadioSelect
def enabled_applications():
@@ -18,7 +19,7 @@ def enabled_applications():
"""
from modoboa.core.models import Extension
from modoboa.core.extensions import exts_pool
from modoboa.lib.dbutils import db_table_exists
from modoboa.lib.db_utils import db_table_exists
result = [("user", "user")]
if db_table_exists("core_extension"):
@@ -304,8 +305,9 @@ class GeneralParametersForm(parameters.AdminParametersForm):
required_fields = ["ldap_user_dn_template"]
for f in required_fields:
if not f in cleaned_data or cleaned_data[f] == u'':
self._errors[f] = self.error_class([_("This field is required")])
if f not in cleaned_data or cleaned_data[f] == u'':
self._errors[f] = self.error_class(
[_("This field is required")])
return cleaned_data
@@ -341,14 +343,20 @@ class GeneralParametersForm(parameters.AdminParametersForm):
))
if values["ldap_auth_method"] == "searchbind":
setattr(settings, "AUTH_LDAP_BIND_DN", values["ldap_bind_dn"])
setattr(settings, "AUTH_LDAP_BIND_PASSWORD", values["ldap_bind_password"])
setattr(
settings, "AUTH_LDAP_BIND_PASSWORD",
values["ldap_bind_password"]
)
search = LDAPSearch(
values["ldap_search_base"], ldap.SCOPE_SUBTREE,
values["ldap_search_filter"]
)
setattr(settings, "AUTH_LDAP_USER_SEARCH", search)
else:
setattr(settings, "AUTH_LDAP_USER_DN_TEMPLATE", values["ldap_user_dn_template"])
setattr(
settings, "AUTH_LDAP_USER_DN_TEMPLATE",
values["ldap_user_dn_template"]
)
if values["ldap_is_active_directory"] == "yes":
if not hasattr(settings, "AUTH_LDAP_GLOBAL_OPTIONS"):
setattr(settings, "AUTH_LDAP_GLOBAL_OPTIONS", {
+6 -2
View File
@@ -1,12 +1,15 @@
# coding: utf-8
import argparse
import os
import sys
import argparse
from django.conf import settings
from django.template import Context, Template
class Command(object):
"""Base command class
A valid administrative command must inherit from this class.
@@ -71,6 +74,7 @@ def scan_for_commands(dirname=""):
def handle_command_line():
"""Parse the command line."""
commands = scan_for_commands()
parser = argparse.ArgumentParser(
description="A set of utilities to ease the installation of Modoboa.",
@@ -83,7 +87,7 @@ def handle_command_line():
help='A valid command name')
(args, remaining) = parser.parse_known_args()
if not args.command in commands:
if args.command not in commands:
print >> sys.stderr, "Unknown command '%s'" % args.command
sys.exit(1)
+4 -1
View File
@@ -1,9 +1,12 @@
# coding: utf-8
import sys
from . import Command
class HelpCommand(Command):
help = "Display the help message associated to a specific command"
def __init__(self, *args, **kwargs):
@@ -12,7 +15,7 @@ class HelpCommand(Command):
help='A command name')
def handle(self, parsed_args):
if not parsed_args.name in self._commands:
if parsed_args.name not in self._commands:
print >>sys.stderr, "Unknown command: %s" % parsed_args.name
sys.exit(1)
cmd = self._commands[parsed_args.name](self._commands)
+8 -4
View File
@@ -1,18 +1,22 @@
# coding: utf-8
"""postfix_maps command."""
import datetime
import getpass
import inspect
import os
import sys
import datetime
from django.template import Context, Template
from . import Command
class MapFilesGenerator(object):
def __init__(self, dbinfo=None):
"""Constructor.
"""
"""Constructor."""
self.template = Template("""user = {{ dbuser }}
password = {{ dbpass }}
dbname = {{ dbname }}
@@ -69,7 +73,7 @@ query = {{ query|safe }}
continue
if v is MapFile:
continue
if not v.category in args.categories:
if v.category not in args.categories:
continue
self.__render_map(args, v)
+7 -3
View File
@@ -1,8 +1,11 @@
"""Extension management."""
from django.conf import settings
from django.conf.urls import include
class ModoExtension(object):
"""Base extension class.
Each Modoboa extension must inherit from this class to be
@@ -35,6 +38,7 @@ class ModoExtension(object):
class ExtensionsPool(object):
"""The extensions manager"""
def __init__(self):
@@ -44,9 +48,9 @@ class ExtensionsPool(object):
self.extensions[ext.name] = dict(cls=ext, show=show)
def get_extension(self, name):
if not name in self.extensions:
if name not in self.extensions:
return None
if not "instance" in self.extensions[name]:
if "instance" not in self.extensions[name]:
self.extensions[name]["instance"] = self.extensions[name]["cls"]()
return self.extensions[name]["instance"]
@@ -54,7 +58,7 @@ class ExtensionsPool(object):
"""Check if an extension is enabled or not."""
from modoboa.core.models import Extension
if not name in self.extensions:
if name not in self.extensions:
return False
try:
state = Extension.objects.get(name=name).enabled
+15 -7
View File
@@ -1,6 +1,10 @@
# coding: utf-8
"""Core forms."""
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.models import User
from modoboa.lib import parameters
@@ -38,9 +42,9 @@ class ProfileForm(forms.ModelForm):
model = User
fields = ("first_name", "last_name")
widgets = {
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
'last_name': forms.TextInput(attrs={'class': 'form-control'})
}
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
'last_name': forms.TextInput(attrs={'class': 'form-control'})
}
def __init__(self, update_password, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
@@ -61,15 +65,19 @@ class ProfileForm(forms.ModelForm):
return self.cleaned_data["oldpassword"]
def clean_confirmation(self):
if self.cleaned_data["newpassword"] != self.cleaned_data["confirmation"]:
newpassword = self.cleaned_data["newpassword"]
confirmation = self.cleaned_data["confirmation"]
if newpassword != confirmation:
raise forms.ValidationError(_("Passwords mismatch"))
return self.cleaned_data["confirmation"]
def save(self, commit=True):
user = super(ProfileForm, self).save(commit=False)
if commit:
if self.cleaned_data.has_key("confirmation") and \
self.cleaned_data["confirmation"] != "":
user.set_password(self.cleaned_data["confirmation"], self.cleaned_data["oldpassword"])
if self.cleaned_data.get("confirmation", "") != "":
user.set_password(
self.cleaned_data["confirmation"],
self.cleaned_data["oldpassword"]
)
user.save()
return user
@@ -21,6 +21,6 @@ class Command(BaseCommand):
"""Entry point."""
for extension in Extension.objects.all():
fullname = "modoboa.extensions.{0}".format(extension.name)
if not fullname in settings.MODOBOA_APPS:
if fullname not in settings.MODOBOA_APPS:
extension.delete()
connection.close()
@@ -53,6 +53,6 @@ class Command(BaseCommand, CloseConnectionMixin):
for extname in args:
fullname = "modoboa.extensions.{0}".format(extname)
if not fullname in settings.MODOBOA_APPS:
if fullname not in settings.MODOBOA_APPS:
continue
self._change_extension_state(extname, options)
+48 -27
View File
@@ -1,36 +1,43 @@
import sys
"""Core models."""
import logging
import os
import re
import logging
import reversion
from django.db import models
from django.db.models.signals import post_delete
import sys
from django.conf import settings
from django.dispatch import receiver
from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.contrib.auth.hashers import make_password, is_password_usable
from django.contrib.auth.models import (
UserManager, Group, PermissionsMixin
)
from django.contrib.auth.hashers import make_password, is_password_usable
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
from modoboa.core.extensions import exts_pool
from modoboa.core.password_hashers import get_password_hasher
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import (
PermDeniedException, InternalError, BadRequest, Conflict
)
from modoboa.lib.sysutils import exec_cmd
from modoboa.core.extensions import exts_pool
from modoboa.core.password_hashers import get_password_hasher
try:
from modoboa.lib.ldaputils import LDAPAuthBackend
from modoboa.lib.ldap_utils import LDAPAuthBackend
ldap_available = True
except ImportError:
ldap_available = False
class User(PermissionsMixin):
"""Custom User model.
It overloads the way passwords are stored into the database. The
@@ -108,7 +115,7 @@ class User(PermissionsMixin):
load_core_settings()
scheme = parameters.get_admin("PASSWORD_SCHEME")
if type(raw_value) is unicode:
if isinstance(raw_value, unicode):
raw_value = raw_value.encode("utf-8")
return get_password_hasher(scheme.upper())().encrypt(raw_value)
@@ -140,7 +147,7 @@ class User(PermissionsMixin):
match = self.password_expr.match(self.password)
if match is None:
return False
if type(raw_value) is unicode:
if isinstance(raw_value, unicode):
raw_value = raw_value.encode("utf-8")
scheme = match.group(1)
val2 = match.group(2)
@@ -242,7 +249,8 @@ class User(PermissionsMixin):
"""
ct = ContentType.objects.get_for_model(obj)
try:
ooentry = self.objectaccess_set.get(content_type=ct, object_id=obj.id)
ooentry = self.objectaccess_set.get(
content_type=ct, object_id=obj.id)
except ObjectAccess.DoesNotExist:
return False
return ooentry.is_owner
@@ -263,7 +271,8 @@ class User(PermissionsMixin):
ct = ContentType.objects.get_for_model(obj)
try:
ooentry = self.objectaccess_set.get(content_type=ct, object_id=obj.id)
ooentry = self.objectaccess_set.get(
content_type=ct, object_id=obj.id)
except ObjectAccess.DoesNotExist:
pass
else:
@@ -333,7 +342,8 @@ class User(PermissionsMixin):
if len(row) < 7:
raise BadRequest(_("Invalid line"))
role = row[6].strip()
if not user.is_superuser and not role in ["SimpleUsers", "DomainAdmins"]:
allowed_roles = ["SimpleUsers", "DomainAdmins"]
if not user.is_superuser and role not in allowed_roles:
raise PermDeniedException(
_("You can't import an account with a role greater than yours")
)
@@ -345,13 +355,15 @@ class User(PermissionsMixin):
else:
raise Conflict
if role == "SimpleUsers":
if (len(row) < 8 or not row[7].strip()):
if len(row) < 8 or not row[7].strip():
raise BadRequest(
_("The simple user '%s' must have a valid email address" % self.username)
_("The simple user '%s' must have a valid email address"
% self.username)
)
if self.username != row[7].strip():
raise BadRequest(
_("username and email fields must not differ for '%s'" % self.username)
_("username and email fields must not differ for '%s'"
% self.username)
)
if crypt_password:
@@ -374,9 +386,16 @@ class User(PermissionsMixin):
:param csvwriter: csv object
"""
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.group, self.email.encode("utf-8")]
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.group,
self.email.encode("utf-8")
]
row += events.raiseQueryEvent("AccountExported", self)
csvwriter.writerow(row)
@@ -414,7 +433,9 @@ class ObjectAccess(models.Model):
unique_together = (("user", "content_type", "object_id"),)
def __unicode__(self):
return "%s => %s (%s)" % (self.user, self.content_object, self.content_type)
return "%s => %s (%s)" % (
self.user, self.content_object, self.content_type
)
class Extension(models.Model):
@@ -501,7 +522,7 @@ def post_revision_commit(sender, **kwargs):
"""
from modoboa.lib.signals import get_request
current_user = get_request().user.username
current_user = get_request().user.username
logger = logging.getLogger("modoboa.admin")
for version in kwargs["versions"]:
if version.object is None:
@@ -9,6 +9,7 @@ from modoboa.lib import parameters
class MD5CRYPTHasher(PasswordHasher):
"""
MD5-CRYPT password hasher.
@@ -29,6 +30,7 @@ class MD5CRYPTHasher(PasswordHasher):
class SHA256CRYPTHasher(PasswordHasher):
"""
SHA256-CRYPT password hasher.
@@ -51,6 +53,7 @@ class SHA256CRYPTHasher(PasswordHasher):
class SHA512CRYPTHasher(PasswordHasher):
"""
SHA512-CRYPT password hasher.
+6
View File
@@ -12,9 +12,11 @@ from django.utils.crypto import constant_time_compare
class PasswordHasher(object):
"""
Base class of all hashers.
"""
def __init__(self, target='local'):
self._target = target
@@ -61,6 +63,7 @@ class PasswordHasher(object):
class PLAINHasher(PasswordHasher):
"""
Plain (ie. clear) password hasher.
"""
@@ -73,6 +76,7 @@ class PLAINHasher(PasswordHasher):
class CRYPTHasher(PasswordHasher):
"""
crypt password hasher.
@@ -89,6 +93,7 @@ class CRYPTHasher(PasswordHasher):
class MD5Hasher(PasswordHasher):
"""
MD5 password hasher.
@@ -104,6 +109,7 @@ class MD5Hasher(PasswordHasher):
class SHA256Hasher(PasswordHasher):
"""
SHA256 password hasher.
+9 -4
View File
@@ -1,10 +1,14 @@
"""Custom tags for Core application."""
import re
from django import template
from django.contrib.sessions.models import Session
from django.core.urlresolvers import reverse
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.contrib.sessions.models import Session
from modoboa.lib import events
@@ -125,7 +129,7 @@ def colorize_level(level):
"WARNING": "text-warning",
"CRITICAL": "text-danger"
}
if not level in classes:
if level not in classes:
return level
return "<p class='%s'>%s</p>" % (classes[level], level)
@@ -140,7 +144,7 @@ def tohtml(message):
def visirule(field):
if not hasattr(field, 'form') or \
not hasattr(field.form, "visirules") or \
not field.html_name in field.form.visirules:
field.html_name not in field.form.visirules:
return ""
rule = field.form.visirules[field.html_name]
return " data-visibility-field='%s' data-visibility-value='%s' " \
@@ -154,6 +158,7 @@ def get_version():
class ConnectedUsers(template.Node):
def __init__(self, varname):
self.varname = varname
+2 -1
View File
@@ -74,7 +74,8 @@ class ModoboaAdminCommandTestCase(TestCase):
categories = ["std", "autoreply", "relaydomains"]
dburl = "{0}://user:password@localhost/testdb".format(engine)
code, output = exec_cmd(
"modoboa-admin.py postfix_maps --categories {0} --dburl {1} {2}".format(
"modoboa-admin.py postfix_maps --categories {0}"
" --dburl {1} {2}".format(
" ".join(categories), dburl, self.workdir
)
)
+6 -9
View File
@@ -1,21 +1,18 @@
"""
Views available to super administrators only.
"""
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.contrib.auth.decorators import (
login_required, user_passes_test
)
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from modoboa.core.models import Extension, Log
from modoboa.core.utils import new_version_available
from modoboa.lib import events, parameters
from modoboa.lib.listing import get_sort_order, get_listing_page
from modoboa.lib.webutils import (
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
)
@@ -88,12 +85,12 @@ def saveextensions(request):
if k.startswith("select_"):
parts = k.split("_", 1)
dbext = Extension.objects.get(name=parts[1])
if not dbext in actived_exts:
if dbext not in actived_exts:
dbext.on()
else:
found += [dbext]
for ext in actived_exts:
if not ext in found:
if ext not in found:
ext.off()
return render_to_json_response(_("Modifications applied."))
+8 -5
View File
@@ -1,16 +1,19 @@
# coding: utf-8
import logging
from django.views.decorators.cache import never_cache
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.utils.translation import ugettext as _
from modoboa.lib import events, parameters
from modoboa.lib.webutils import _render_to_string
from django.views.decorators.cache import never_cache
from modoboa.core.forms import LoginForm
from modoboa.lib import events, parameters
from modoboa.lib.web_utils import _render_to_string
def dologin(request):
"""Try to authenticate."""
error = None
if request.method == "POST":
form = LoginForm(request.POST)
@@ -43,7 +46,7 @@ def dologin(request):
error = _(
"Your username and password didn't match. Please try again.")
logger.warning(
"Failed connection attempt from '%(addr)s' as user '%(user)s'" \
"Failed connection attempt from '%(addr)s' as user '%(user)s'"
% {"addr": request.META["REMOTE_ADDR"],
"user": form.cleaned_data["username"]}
)
+9 -5
View File
@@ -1,12 +1,15 @@
"""Simple user views."""
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.utils.translation import ugettext as _
from django.contrib.auth.decorators import login_required
from modoboa.core.forms import ProfileForm
from modoboa.lib import events, parameters
from modoboa.lib.webutils import (
from modoboa.lib.cryptutils import encrypt
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
)
from modoboa.lib.cryptutils import encrypt
from modoboa.core.forms import ProfileForm
@login_required
@@ -31,7 +34,8 @@ 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"] = encrypt(
form.cleaned_data["confirmation"])
return render_to_json_response(_("Profile updated"))
return render_to_json_response({'form_errors': form.errors}, status=400)
+1 -1
View File
@@ -3,7 +3,7 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
from modoboa.lib import events
from modoboa.lib.webutils import static_url
from modoboa.lib.web_utils import static_url
@events.observe("UserMenuDisplay")
+2 -2
View File
@@ -3,8 +3,8 @@ import os
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from modoboa.lib.webutils import render_to_json_response
from modoboa.lib.emailutils import sendmail_simple, sendmail_fromfile
from modoboa.lib.web_utils import render_to_json_response
from modoboa.lib.email_utils import sendmail_simple, sendmail_fromfile
from modoboa.extensions.admin.lib import needs_mailbox
+19 -14
View File
@@ -1,13 +1,15 @@
# coding: utf-8
from django.utils.translation import ugettext as _, ugettext_lazy
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.extensions import ModoExtension, exts_pool
from modoboa.lib import parameters, events
from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict
from modoboa.lib.emailutils import split_mailbox
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias
)
from modoboa.lib import parameters, events
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict
admin_events = [
"DomainCreated",
@@ -54,7 +56,8 @@ class AdminConsole(ModoExtension):
def load(self):
from modoboa.extensions.admin.app_settings import AdminParametersForm
parameters.register(AdminParametersForm, ugettext_lazy("Administration"))
parameters.register(
AdminParametersForm, ugettext_lazy("Administration"))
events.declare(admin_events)
def destroy(self):
@@ -80,15 +83,15 @@ def admin_menu(target, user):
entries = []
if user.has_perm("admin.view_domains"):
entries += [
{"name" : "domains",
"url" : reverse("admin:domain_list"),
"label" : _("Domains")}
{"name": "domains",
"url": reverse("admin:domain_list"),
"label": _("Domains")}
]
if user.has_perm("core.add_user") or user.has_perm("admin.add_alias"):
entries += [
{"name" : "identities",
"url" : reverse("admin:identity_list"),
"label" : _("Identities")},
{"name": "identities",
"url": reverse("admin:identity_list"),
"label": _("Identities")},
]
return entries
@@ -166,7 +169,8 @@ def import_account_mailbox(user, account, row):
domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
raise BadRequest(
_("Account import failed (%s): domain does not exist" % account.username)
_("Account import failed (%s): domain does not exist"
% account.username)
)
if not user.can_access(domain):
raise PermDeniedException
@@ -175,7 +179,7 @@ def import_account_mailbox(user, account, row):
except Mailbox.DoesNotExist:
pass
else:
raise Conflict(_("Mailbox %s already exists" % account.email))
raise Conflict(_("Mailbox %s already exists" % account.email))
if len(row) == 1:
quota = None
else:
@@ -183,7 +187,8 @@ def import_account_mailbox(user, account, row):
quota = int(row[1].strip())
except ValueError:
raise BadRequest(
_("Account import failed (%s): wrong quota value" % account.username)
_("Account import failed (%s): wrong quota value"
% account.username)
)
use_domain_quota = True if not quota else False
mb = Mailbox(address=mailbox, domain=domain,
+1 -1
View File
@@ -1,7 +1,7 @@
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy
from modoboa.lib.formutils import YesNoField, SeparatorField
from modoboa.lib.form_utils import YesNoField, SeparatorField
from modoboa.lib.sysutils import exec_cmd
from modoboa.lib import parameters
import os
+4 -2
View File
@@ -37,7 +37,8 @@ def populate_database():
"""
dom = DomainFactory.create(name="test.com")
admin = UserFactory(
username="admin@test.com", groups=('DomainAdmins', ), password='{PLAIN}toto'
username="admin@test.com", groups=('DomainAdmins', ),
password='{PLAIN}toto'
)
MailboxFactory(address='admin', domain=dom, user=admin)
account = UserFactory.create(
@@ -59,7 +60,8 @@ def populate_database():
dom2 = DomainFactory.create(name='test2.com', quota=0)
admin = UserFactory.create(
username='admin@test2.com', groups=('DomainAdmins',), password='{PLAIN}toto'
username='admin@test2.com', groups=('DomainAdmins',),
password='{PLAIN}toto'
)
MailboxFactory.create(address='admin', domain=dom2, user=admin)
u = UserFactory.create(
+43 -29
View File
@@ -1,19 +1,22 @@
"""Forms related to accounts management."""
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from django.http import QueryDict
from django.core.urlresolvers import reverse
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import PermDeniedException, Conflict, NotFound
from modoboa.lib.permissions import get_account_roles
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.formutils import (
DomainNameField, DynamicForm, TabForms, WizardForm
)
from modoboa.lib.webutils import render_to_json_response
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.models import User
from modoboa.extensions.admin.models import (
Domain, Mailbox, Alias
)
from modoboa.lib import events, parameters
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import PermDeniedException, Conflict, NotFound
from modoboa.lib.form_utils import (
DomainNameField, DynamicForm, TabForms, WizardForm
)
from modoboa.lib.permissions import get_account_roles
from modoboa.lib.web_utils import render_to_json_response
class AccountFormGeneral(forms.ModelForm):
@@ -77,7 +80,8 @@ class AccountFormGeneral(forms.ModelForm):
del self.fields["is_active"]
self.fields["role"].initial = account.group
if not account.is_local \
and parameters.get_admin("LDAP_AUTH_METHOD", app="core") == "directbind":
and parameters.get_admin(
"LDAP_AUTH_METHOD", app="core") == "directbind":
del self.fields["password1"]
del self.fields["password2"]
@@ -90,7 +94,7 @@ class AccountFormGeneral(forms.ModelForm):
"""
if not self.instance.mailbox_set.count():
return False
return self.instance.mailbox_set.all()[0].domain.enabled == False
return not self.instance.mailbox_set.all()[0].domain.enabled
def clean_role(self):
if self.user.group == "DomainAdmins":
@@ -101,7 +105,7 @@ class AccountFormGeneral(forms.ModelForm):
def clean_username(self):
from django.core.validators import validate_email
if not "role" in self.cleaned_data:
if "role" not in self.cleaned_data:
return self.cleaned_data["username"]
if self.cleaned_data["role"] != "SimpleUsers":
return self.cleaned_data["username"]
@@ -113,7 +117,8 @@ class AccountFormGeneral(forms.ModelForm):
password1 = self.cleaned_data.get("password1", "")
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError(_("The two password fields didn't match."))
raise forms.ValidationError(
_("The two password fields didn't match."))
return password2
def save(self, commit=True):
@@ -165,7 +170,8 @@ class AccountFormMail(forms.Form, DynamicForm):
"quota": 3
}
self.extra_fields = []
for fname, field in events.raiseQueryEvent('ExtraFormFields', 'mailform', self.mb):
result = events.raiseQueryEvent('ExtraFormFields', 'mailform', self.mb)
for fname, field in result:
self.fields[fname] = field
self.extra_fields.append(fname)
if self.mb is not None:
@@ -227,7 +233,7 @@ class AccountFormMail(forms.Form, DynamicForm):
events.raiseEvent("CanCreate", user, "mailboxes")
self.mb = Mailbox(address=locpart, domain=domain, user=account,
use_domain_quota=self.cleaned_data["quota_act"])
self.mb.set_quota(self.cleaned_data["quota"],
self.mb.set_quota(self.cleaned_data["quota"],
user.has_perm("admin.add_domain"))
self.mb.save(creator=user)
@@ -235,7 +241,8 @@ class AccountFormMail(forms.Form, DynamicForm):
newaddress = None
if self.cleaned_data["email"] != self.mb.full_address:
newaddress = self.cleaned_data["email"]
elif account.group == "SimpleUsers" and account.username != self.mb.full_address:
elif (account.group == "SimpleUsers" and
account.username != self.mb.full_address):
newaddress = account.username
if newaddress is not None:
self.mb.old_full_address = self.mb.full_address
@@ -267,7 +274,7 @@ class AccountFormMail(forms.Form, DynamicForm):
aliases.append(value)
for alias in self.mb.alias_set.all():
if not alias.full_address in aliases:
if alias.full_address not in aliases:
if len(alias.get_recipients()) >= 2:
continue
alias.delete()
@@ -331,9 +338,9 @@ class AccountPermissionsForm(forms.Form, DynamicForm):
if not hasattr(self, "account") or self.account is None:
return
for pos, domain in enumerate(Domain.objects.get_for_admin(self.account)):
for pos, dom in enumerate(Domain.objects.get_for_admin(self.account)):
name = "domains_%d" % (pos + 1)
self._create_field(DomainNameField, name, domain.name)
self._create_field(DomainNameField, name, dom.name)
if len(args) and isinstance(args[0], QueryDict):
self._load_from_qdict(args[0], "domains", DomainNameField)
@@ -346,20 +353,20 @@ class AccountPermissionsForm(forms.Form, DynamicForm):
continue
if value in ["", None]:
continue
if not value in current_domains:
if value not in current_domains:
domain = Domain.objects.get(name=value)
domain.add_admin(self.account)
for domain in Domain.objects.get_for_admin(self.account):
if not len(filter(lambda name: self.cleaned_data[name] == domain.name,
self.cleaned_data.keys())):
if not filter(lambda name: self.cleaned_data[name] == domain.name,
self.cleaned_data.keys()):
domain.remove_admin(self.account)
class AccountForm(TabForms):
"""Account edition form.
"""
"""Account edition form."""
def __init__(self, request, *args, **kwargs):
self.user = request.user
self.forms = [
@@ -369,8 +376,11 @@ class AccountForm(TabForms):
new_args=[self.user], mandatory=True),
dict(id="mail", title=_("Mail"), formtpl="admin/mailform.html",
cls=AccountFormMail),
dict(id="perms", title=_("Permissions"), formtpl="admin/permsform.html",
cls=AccountPermissionsForm)
dict(
id="perms", title=_("Permissions"),
formtpl="admin/permsform.html",
cls=AccountPermissionsForm
)
]
cbargs = [self.user]
if "instances" in kwargs:
@@ -402,7 +412,9 @@ class AccountForm(TabForms):
return False
return True
if False in events.raiseQueryEvent("CheckExtraAccountForm", self.account, form):
extra_forms = events.raiseQueryEvent(
"CheckExtraAccountForm", self.account, form)
if False in extra_forms:
return False
return True
@@ -435,8 +447,10 @@ class AccountForm(TabForms):
class AccountWizard(WizardForm):
"""Account creation wizard.
"""
def __init__(self, request):
super(AccountWizard, self).__init__(request)
self.add_step(
@@ -454,7 +468,7 @@ class AccountWizard(WizardForm):
})
def done(self):
from modoboa.lib.webutils import render_to_json_response
from modoboa.lib.web_utils import render_to_json_response
account = self.first_step.form.save()
account.post_create(self.request.user)
+11 -7
View File
@@ -1,14 +1,17 @@
"""Forms related to aliases management."""
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from django.http import QueryDict
from modoboa.lib.exceptions import BadRequest, NotFound, Conflict
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.formutils import (
DynamicForm
)
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.extensions.admin.models import (
Domain, Mailbox, Alias
)
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import BadRequest, NotFound, Conflict
from modoboa.lib.form_utils import (
DynamicForm
)
class AliasForm(forms.ModelForm, DynamicForm):
@@ -109,7 +112,8 @@ class AliasForm(forms.ModelForm, DynamicForm):
rcpt = None
if rcpt is None:
try:
rcpt = Mailbox.objects.get(domain=domain, address=local_part)
rcpt = Mailbox.objects.get(
domain=domain, address=local_part)
except Mailbox.DoesNotExist:
raise NotFound(
_("Local recipient %s not found" % v)
+16 -10
View File
@@ -1,19 +1,21 @@
"""Forms related to domains management."""
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.core.urlresolvers import reverse
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import ModoboaException, Conflict
from modoboa.lib.formutils import (
DomainNameField, YesNoField, WizardForm, DynamicForm, TabForms
)
from modoboa.lib.webutils import render_to_json_response
from modoboa.core.models import User
from modoboa.extensions.admin.lib import check_if_domain_exists
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias, Quota
)
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import ModoboaException, Conflict
from modoboa.lib.form_utils import (
DomainNameField, YesNoField, WizardForm, DynamicForm, TabForms
)
from modoboa.lib.web_utils import render_to_json_response
class DomainFormGeneral(forms.ModelForm, DynamicForm):
@@ -153,12 +155,13 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
continue
aliases.append(v)
for dalias in d.domainalias_set.all():
if not dalias.name in aliases:
if dalias.name not in aliases:
dalias.delete()
else:
aliases.remove(dalias.name)
if aliases:
events.raiseEvent("CanCreate", user, "domain_aliases", len(aliases))
events.raiseEvent(
"CanCreate", user, "domain_aliases", len(aliases))
for alias in aliases:
try:
d.domainalias_set.get(name=alias)
@@ -207,7 +210,8 @@ class DomainFormOptions(forms.Form):
def __init__(self, user, *args, **kwargs):
super(DomainFormOptions, self).__init__(*args, **kwargs)
if False in events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins'):
result = events.raiseQueryEvent('UserCanSetRole', user, 'DomainAdmins')
if False in result:
self.fields = {}
return
if args:
@@ -324,8 +328,10 @@ class DomainForm(TabForms):
class DomainWizard(WizardForm):
"""Domain creation wizard.
"""
def __init__(self, request):
super(DomainWizard, self).__init__(request)
self.add_step(
+2
View File
@@ -32,12 +32,14 @@ class ExportDataForm(forms.Form):
class ExportDomainsForm(ExportDataForm):
def __init__(self, *args, **kwargs):
super(ExportDomainsForm, self).__init__(*args, **kwargs)
self.fields["filename"].initial = "modoboa-domains.csv"
class ExportIdentitiesForm(ExportDataForm):
def __init__(self, *args, **kwargs):
super(ExportIdentitiesForm, self).__init__(*args, **kwargs)
self.fields["filename"].initial = "modoboa-identities.csv"
+9 -4
View File
@@ -1,10 +1,13 @@
"""Forms related to forwards management."""
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib.exceptions import BadRequest, PermDeniedException
from modoboa.lib.emailutils import split_mailbox
from modoboa.extensions.admin.models import (
Domain
)
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import BadRequest, PermDeniedException
class ForwardForm(forms.Form):
@@ -12,12 +15,14 @@ class ForwardForm(forms.Form):
label=ugettext_lazy("Recipient(s)"),
widget=forms.Textarea(attrs={"class": "form-control"}),
required=False,
help_text=ugettext_lazy("Indicate one or more recipients separated by a ','")
help_text=ugettext_lazy(
"Indicate one or more recipients separated by a ','")
)
keepcopies = forms.BooleanField(
label=ugettext_lazy("Keep local copies"),
required=False,
help_text=ugettext_lazy("Forward messages and store copies into your local mailbox")
help_text=ugettext_lazy(
"Forward messages and store copies into your local mailbox")
)
def parse_dest(self):
+12 -1
View File
@@ -1,8 +1,13 @@
"""Forms related to import operations."""
from django import forms
from django.utils.translation import ugettext_lazy
class ImportDataForm(forms.Form):
"""Base form to import objects."""
sourcefile = forms.FileField(label=ugettext_lazy("Select a file"))
sepchar = forms.CharField(
label=ugettext_lazy("Separator"),
@@ -26,7 +31,13 @@ class ImportDataForm(forms.Form):
class ImportIdentitiesForm(ImportDataForm):
"""A form to import identities."""
crypt_password = forms.BooleanField(
label=ugettext_lazy("Crypt passwords"), required=False,
help_text=ugettext_lazy("Check this option if passwords contained in your file are not crypted")
help_text=ugettext_lazy(
"Check this option if passwords contained in your file "
"are not crypted"
)
)
+1 -1
View File
@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from modoboa.core.models import User
from modoboa.lib import events
from modoboa.lib.exceptions import PermDeniedException
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.email_utils import split_mailbox
from modoboa.extensions.admin.models import (
Domain, Alias
)
@@ -4,7 +4,7 @@ from south.db import db
from south.v2 import DataMigration
from django.db import models
from modoboa.lib.models import Parameter
from modoboa.lib.dbutils import db_table_exists
from modoboa.lib.db_utils import db_table_exists
class Migration(DataMigration):
+14 -9
View File
@@ -1,16 +1,21 @@
import reversion
"""Models related to aliases management."""
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.exceptions import (
PermDeniedException, BadRequest, Conflict
)
import reversion
from .base import AdminObject
from .domain import Domain
from .mailbox import Mailbox
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import (
PermDeniedException, BadRequest, Conflict
)
class Alias(AdminObject):
"""
Mailbox alias.
"""
@@ -108,16 +113,16 @@ class Alias(AdminObject):
curmboxes = self.mboxes.all()
for rcpt in int_rcpts:
if isinstance(rcpt, Alias):
if not rcpt in curaliases:
if rcpt not in curaliases:
self.aliases.add(rcpt)
continue
if not rcpt in curmboxes:
if rcpt not in curmboxes:
self.mboxes.add(rcpt)
for rcpt in curaliases:
if not rcpt in int_rcpts:
if rcpt not in int_rcpts:
self.aliases.remove(rcpt)
for rcpt in curmboxes:
if not rcpt in int_rcpts:
if rcpt not in int_rcpts:
self.mboxes.remove(rcpt)
def get_recipients(self, with_external=True):
+2
View File
@@ -6,6 +6,7 @@ from modoboa.lib.permissions import (
class ObjectDates(models.Model):
"""Dates recording for admin objects
This table keeps creation and last modification dates for Domains,
@@ -32,6 +33,7 @@ class ObjectDates(models.Model):
class AdminObject(models.Model):
"""Abstract model to support dates
Inherit from this model to automatically add the "dates" feature
+12 -6
View File
@@ -1,12 +1,16 @@
import reversion
"""Models related to domains management."""
from django.contrib.contenttypes import generic
from django.db import models
from django.db.models.manager import Manager
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.contenttypes import generic
import reversion
from .base import AdminObject
from modoboa.core.models import User, ObjectAccess
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import BadRequest, Conflict
from modoboa.core.models import User, ObjectAccess
from .base import AdminObject
class DomainManager(Manager):
@@ -23,6 +27,7 @@ class DomainManager(Manager):
class Domain(AdminObject):
"""Mail domain.
"""
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
@@ -109,7 +114,7 @@ class Domain(AdminObject):
ungrant_access_to_object(mb, account)
ungrant_access_to_object(mb.user, account)
for al in self.alias_set.all():
ungrant_access_to_object(al, account)
ungrant_access_to_object(al, account)
def delete(self, fromuser, keepdir=False):
"""Custom delete method.
@@ -157,7 +162,8 @@ class Domain(AdminObject):
try:
self.quota = int(row[2].strip())
except ValueError:
raise BadRequest(_("Invalid quota value for domain '%s'" % self.name))
raise BadRequest(
_("Invalid quota value for domain '%s'" % self.name))
self.enabled = (row[3].strip() in ["True", "1", "yes", "y"])
self.save(creator=user)
@@ -1,9 +1,13 @@
import reversion
"""Models related to domain aliases management."""
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib.exceptions import BadRequest, Conflict
import reversion
from .base import AdminObject
from .domain import Domain
from modoboa.lib.exceptions import BadRequest, Conflict
class DomainAlias(AdminObject):
+21 -12
View File
@@ -1,19 +1,24 @@
"""Models related to mailboxes management."""
import os
import pwd
import reversion
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Q
from django.db.models.manager import Manager
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _, ugettext_lazy
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
from .base import AdminObject
from .domain import Domain
from modoboa.core.models import User
from modoboa.lib import parameters, events
from modoboa.lib.exceptions import BadRequest, InternalError
from modoboa.lib.sysutils import exec_cmd
from modoboa.core.models import User
from .base import AdminObject
from .domain import Domain
class Quota(models.Model):
@@ -72,7 +77,8 @@ class Mailbox(AdminObject):
address = models.CharField(
ugettext_lazy('address'), max_length=252,
help_text=ugettext_lazy("Mailbox address (without the @domain.tld part)")
help_text=ugettext_lazy(
"Mailbox address (without the @domain.tld part)")
)
quota = models.PositiveIntegerField()
use_domain_quota = models.BooleanField(default=False)
@@ -112,7 +118,7 @@ class Mailbox(AdminObject):
@property
def mail_home(self):
"""Retrieve the home directory of this mailbox.
The home directory refers to the place on the file system
where the mailbox data is stored.
@@ -131,9 +137,10 @@ class Mailbox(AdminObject):
options['sudo_user'] = mbowner
code, output = exec_cmd(
"doveadm user %s -f home" % self.full_address, **options
)
)
if code:
raise InternalError(_("Failed to retrieve mailbox location (%s)" % output))
raise InternalError(
_("Failed to retrieve mailbox location (%s)" % output))
self.__mail_home = output.strip()
return self.__mail_home
@@ -273,7 +280,9 @@ class Mailbox(AdminObject):
We try to delete the associated quota in the same time (it may
has already been removed if we're deleting a domain).
:param bool keepdir: delete the mailbox home dir on the filesystem or not
:param bool keepdir: delete the mailbox home dir on the
filesystem or not
"""
try:
q = Quota.objects.get(username=self.full_address)
@@ -322,4 +331,4 @@ class MailboxOperation(models.Model):
def __str__(self):
if self.type == 'rename':
return 'Rename %s -> %s' % (self.argument, self.mailbox.mail_home)
return 'Delete %s' % self.argument
return 'Delete %s' % self.argument
@@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import events
from modoboa.lib.templatetags.lib_tags import render_link
from modoboa.lib.webutils import render_actions
from modoboa.lib.web_utils import render_actions
register = template.Library()
@@ -147,7 +147,8 @@ def domain_actions(user, domain):
if domain.__class__.__name__ == 'Domain':
actions = [
{"name": "listidentities",
"url": reverse("admin:identity_list") + "#list/?searchquery=@%s" % domain.name,
"url": "{0}#list/?searchquery=@{1}".format(
reverse("admin:identity_list"), domain.name),
"title": _("View the domain's identities"),
"img": "fa fa-user"}
]
@@ -277,5 +278,3 @@ def get_extra_admin_content(user, target, currentpage):
"ExtraAdminContent", user, target, currentpage
)
return "".join(res)
+13 -7
View File
@@ -1,10 +1,11 @@
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin import factories
from modoboa.extensions.admin.models import (
Domain, Mailbox
)
from modoboa.extensions.admin import factories
from modoboa.lib.tests import ModoTestCase
class AccountTestCase(ModoTestCase):
@@ -62,10 +63,14 @@ class AccountTestCase(ModoTestCase):
def test_set_nul_quota_as_domainadmin(self):
self.clt.logout()
self.assertTrue(self.clt.login(username="admin@test.com", password="toto"))
self.assertTrue(
self.clt.login(username="admin@test.com", password="toto")
)
self._set_quota("user@test.com", 0, 400)
self.clt.logout()
self.assertTrue(self.clt.login(username="admin@test2.com", password="toto"))
self.assertTrue(
self.clt.login(username="admin@test2.com", password="toto")
)
self._set_quota("user@test2.com", 0)
@@ -119,8 +124,9 @@ class PermissionsTestCase(ModoTestCase):
def test_self_modif(self):
self.clt.logout()
self.assertEqual(self.clt.login(username="admin@test.com", password="toto"),
True)
self.assertTrue(
self.clt.login(username="admin@test.com", password="toto")
)
admin = User.objects.get(username="admin@test.com")
values = dict(
username="admin@test.com", first_name="Admin",
@@ -132,7 +138,7 @@ class PermissionsTestCase(ModoTestCase):
values
)
self.assertEqual(admin.group, "DomainAdmins")
self.assertEqual(admin.can_access(Domain.objects.get(name="test.com")), True)
self.assertTrue(admin.can_access(Domain.objects.get(name="test.com")))
values["role"] = "SuperAdmins"
self.ajax_post(
+5 -4
View File
@@ -1,10 +1,11 @@
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin import factories
from modoboa.extensions.admin.models import (
Alias
)
from modoboa.extensions.admin import factories
from modoboa.lib.tests import ModoTestCase
class AliasTestCase(ModoTestCase):
@@ -80,8 +81,8 @@ class AliasTestCase(ModoTestCase):
self.assertEqual(fwd.get_recipients_count(), 1)
self.ajax_post(
reverse("admin:alias_delete") + "?selection=%d" \
% fwd.id, {}
reverse("admin:alias_delete") + "?selection=%d"
% fwd.id, {}
)
self.assertRaises(Alias.DoesNotExist, Alias.objects.get,
address="forward2", domain__name="test.com")
+5 -4
View File
@@ -1,6 +1,7 @@
from django.core.urlresolvers import reverse
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin import factories
from modoboa.lib.tests import ModoTestCase
class ExportTestCase(ModoTestCase):
@@ -12,8 +13,8 @@ class ExportTestCase(ModoTestCase):
def __export_identities(self, idtfilter="", grpfilter=""):
self.clt.get(
reverse("admin:_identity_list") \
+ "?grpfilter=%s&idtfilter=%s" % (grpfilter, idtfilter)
reverse("admin:_identity_list")
+ "?grpfilter=%s&idtfilter=%s" % (grpfilter, idtfilter)
)
return self.clt.post(
reverse("admin:identity_export"),
@@ -39,7 +40,7 @@ class ExportTestCase(ModoTestCase):
idtfilter="account", grpfilter="SimpleUsers"
)
self.assertListEqual(
"account;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10",
"account;user@test.com;{PLAIN}toto;;;True;SimpleUsers;user@test.com;10\r\naccount;user@test2.com;{PLAIN}toto;;;True;SimpleUsers;user@test2.com;10",
response.content.strip()
)
+10 -5
View File
@@ -1,12 +1,13 @@
# coding: utf-8
from django.core.urlresolvers import reverse
from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin import factories
from modoboa.extensions.admin.models import (
Domain, Alias, DomainAlias
)
from modoboa.extensions.admin import factories
from modoboa.lib.tests import ModoTestCase
class ImportTestCase(ModoTestCase):
@@ -76,7 +77,9 @@ dlist; dlist@test.com; True; user1@test.com; user@extdomain.com
self.assertEqual(mb1.quota, 0)
self.assertTrue(admin.is_owner(mb1))
self.assertEqual(mb1.full_address, "user1@test.com")
self.assertTrue(self.clt.login(username="user1@test.com", password="toto"))
self.assertTrue(
self.clt.login(username="user1@test.com", password="toto")
)
da = User.objects.get(username="truc@test.com")
damb = da.mailbox_set.all()[0]
@@ -159,7 +162,9 @@ account; truc@test.com; toto; René; Truc; True; DomainAdmins; truc@test.com; 0;
Expected result: no
"""
self.clt.logout()
self.assertTrue(self.clt.login(username="admin@test.com", password="toto"))
self.assertTrue(
self.clt.login(username="admin@test.com", password="toto")
)
f = ContentFile(b"""
account; sa@test.com; toto; Super; Admin; True; SuperAdmins; superadmin@test.com; 50
""", name="identities.csv")
+1 -1
View File
@@ -7,7 +7,7 @@ from django.contrib.auth.decorators import (
login_required, permission_required
)
from modoboa.lib import events
from modoboa.lib.webutils import render_to_json_response
from modoboa.lib.web_utils import render_to_json_response
from modoboa.lib.exceptions import PermDeniedException, Conflict
from modoboa.extensions.admin.forms import AliasForm
from modoboa.extensions.admin.models import Alias
+1 -1
View File
@@ -15,7 +15,7 @@ import reversion
from modoboa.core.models import User
from modoboa.lib import parameters, events
from modoboa.lib.webutils import (
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
)
from modoboa.lib.exceptions import (
+14 -7
View File
@@ -1,17 +1,22 @@
import csv
"""Export related views."""
import cStringIO
import csv
from rfc6266 import build_header
from django.http import HttpResponse
from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from modoboa.extensions.admin.lib import get_domains, get_identities
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.shortcuts import render
from django.utils.translation import ugettext as _
from modoboa.extensions.admin.forms import (
ExportIdentitiesForm, ExportDomainsForm
)
from modoboa.extensions.admin.lib import get_domains, get_identities
def _export(content, filename):
@@ -46,7 +51,9 @@ def export_identities(request):
form.is_valid()
fp = cStringIO.StringIO()
csvwriter = csv.writer(fp, delimiter=form.cleaned_data["sepchar"])
for ident in get_identities(request.user, **request.session['identities_filters']):
identities = get_identities(
request.user, **request.session['identities_filters'])
for ident in identities:
ident.to_csv(csvwriter)
content = fp.getvalue()
fp.close()
+23 -17
View File
@@ -1,30 +1,29 @@
"""
Identity related views.
"""
from django.shortcuts import render
from django.utils.translation import ugettext as _, ungettext
"""Identity related views."""
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from django.shortcuts import render
from django.utils.translation import ugettext as _, ungettext
from django.views.decorators.csrf import ensure_csrf_cookie
import reversion
from modoboa.core.models import User
from modoboa.extensions.admin.forms import (
AccountForm
)
from modoboa.extensions.admin.lib import get_identities
from modoboa.extensions.admin.models import Mailbox, Domain
from modoboa.lib import parameters, events
from modoboa.lib.exceptions import (
PermDeniedException, BadRequest
)
from modoboa.lib.webutils import (
_render_to_string, render_to_json_response
)
from modoboa.core.models import User
from modoboa.extensions.admin.models import Mailbox, Domain
from modoboa.lib.listing import (
get_sort_order, get_listing_page
)
from modoboa.extensions.admin.lib import get_identities
from modoboa.extensions.admin.forms import (
AccountForm
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
)
@@ -67,7 +66,7 @@ def _identities(request):
@login_required
@permission_required("admin.add_mailbox")
def list_quotas(request):
from modoboa.lib.dbutils import db_type
from modoboa.lib.db_utils import db_type
sort_order, sort_dir = get_sort_order(request.GET, "address")
mboxes = Mailbox.objects.get_for_admin(
@@ -80,9 +79,15 @@ def list_quotas(request):
where = "admin_mailbox.address||'@'||admin_domain.name"
db_type = db_type()
if db_type == "postgres":
select = '(admin_quota.bytes::float / (CAST(admin_mailbox.quota AS BIGINT) * 1048576)) * 100'
select = (
"(admin_quota.bytes::float / (CAST(admin_mailbox.quota "
"AS BIGINT) * 1048576)) * 100"
)
else:
select = 'admin_quota.bytes / (admin_mailbox.quota * 1048576) * 100'
select = (
"admin_quota.bytes / (admin_mailbox.quota "
"* 1048576) * 100"
)
if db_type == "mysql":
where = "CONCAT(admin_mailbox.address,'@',admin_domain.name)"
mboxes = mboxes.extra(
@@ -191,7 +196,8 @@ def remove_permission(request):
domain = Domain.objects.get(pk=domid)
except (User.DoesNotExist, Domain.DoesNotExist):
raise BadRequest(_("Invalid request"))
if not request.user.can_access(account) or not request.user.can_access(domain):
if not request.user.can_access(account) or \
not request.user.can_access(domain):
raise PermDeniedException
domain.remove_admin(account)
return render_to_json_response({})
+15 -9
View File
@@ -1,18 +1,23 @@
"""Import related views."""
import csv
import reversion
from django.utils.translation import ugettext as _
from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from modoboa.lib import events
from modoboa.lib.exceptions import ModoboaException, Conflict
from django.core.urlresolvers import reverse
from django.shortcuts import render
from django.utils.translation import ugettext as _
import reversion
from modoboa.core.models import User
from modoboa.extensions.admin.forms import ImportIdentitiesForm, ImportDataForm
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Alias
)
from modoboa.extensions.admin.forms import ImportIdentitiesForm, ImportDataForm
from modoboa.lib import events
from modoboa.lib.exceptions import ModoboaException, Conflict
def import_domain(user, row, formopts):
@@ -41,7 +46,8 @@ def import_domains(request):
</ul>
<p>The first element of each line is mandatory and must be equal to one of the previous values.</p>
<p>You can use a different character as separator.</p>
""" % ''.join([unicode(hlp) for hlp in events.raiseQueryEvent('ExtraDomainImportHelp')]))
""" % ''.join([unicode(hlp) for hlp in
events.raiseQueryEvent('ExtraDomainImportHelp')]))
ctx = dict(
title=_("Import domains"),
@@ -130,7 +136,7 @@ def importdata(request, formclass=ImportDataForm):
return render(request, "admin/import_done.html", {
"status": "ok", "msg": msg
})
except (ModoboaException), e:
except (ModoboaException) as e:
error = str(e)
return render(request, "admin/import_done.html", {
+1 -1
View File
@@ -1,7 +1,7 @@
import reversion
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from modoboa.lib.webutils import (
from modoboa.lib.web_utils import (
render_to_json_response, _render_to_string
)
from modoboa.extensions.admin.lib import needs_mailbox
+1 -1
View File
@@ -3,7 +3,7 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from modoboa.lib.formutils import SeparatorField, YesNoField, InlineRadioSelect
from modoboa.lib.form_utils import SeparatorField, YesNoField, InlineRadioSelect
from modoboa.lib.parameters import AdminParametersForm, UserParametersForm
+2 -2
View File
@@ -7,7 +7,7 @@ from django import forms
from django.utils.translation import ugettext as _
from modoboa.lib import parameters
from modoboa.lib.formutils import InlineRadioSelect
from modoboa.lib.form_utils import InlineRadioSelect
from .models import Policy, Users
@@ -48,7 +48,7 @@ class DomainPolicyForm(forms.ModelForm):
def save(self, user, commit=True):
policy = super(DomainPolicyForm, self).save(commit=False)
for field in ['bypass_spam_checks', 'bypass_virus_checks',
'bypass_banned_checks']:
'bypass_banned_checks']:
if getattr(policy, field) == '':
setattr(policy, field, None)
+2 -2
View File
@@ -11,10 +11,10 @@ from django.core.urlresolvers import reverse
from modoboa.extensions.admin.models import Mailbox, Alias
from modoboa.lib import parameters
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import InternalError
from modoboa.lib.sysutils import exec_cmd
from modoboa.lib.webutils import NavigationParameters
from modoboa.lib.web_utils import NavigationParameters
from .models import Users, Policy
@@ -10,7 +10,7 @@ from django.utils.translation import ugettext as _
from modoboa.core.management.commands import CloseConnectionMixin
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib.emailutils import sendmail_simple
from modoboa.lib.email_utils import sendmail_simple
from modoboa.extensions.admin.models import Domain
from modoboa.extensions.amavis import Amavis
+60 -17
View File
@@ -2,10 +2,11 @@
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# Feel free to rename the models, but don't rename db_table values or field names.
# Feel free to rename the models, but don't rename db_table values
# or field names.
#
# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
# into your database.
# Also note: You'll have to insert the output of
# 'django-admin.py sqlcustom [appname]'into your database.
#
# Original Amavis version : 2.6.2
@@ -95,42 +96,81 @@ class Policy(models.Model):
('Y', ugettext_lazy('no')),
('', ugettext_lazy('default'))),
max_length=3,
help_text=ugettext_lazy("Bypass virus checks or not. Choose 'default' to use global settings.")
help_text=ugettext_lazy(
"Bypass virus checks or not. Choose 'default' to use global "
"settings."
)
)
bypass_spam_checks = models.CharField(
ugettext_lazy("Spam filter"), default='', null=True,
choices=(('N', ugettext_lazy('yes')),
('Y', ugettext_lazy('no')),
('', ugettext_lazy('default'))),
max_length=3,
help_text=ugettext_lazy("Bypass spam checks or not. Choose 'default' to use global settings.")
help_text=ugettext_lazy(
"Bypass spam checks or not. Choose 'default' to use global "
"settings."
)
)
bypass_banned_checks = models.CharField(
ugettext_lazy("Banned filter"), default='', null=True,
choices=(('N', ugettext_lazy('yes')),
('Y', ugettext_lazy('no')),
('', ugettext_lazy('default'))),
max_length=3,
help_text=ugettext_lazy("Bypass banned checks or not. Choose 'default' to use global settings.")
help_text=ugettext_lazy(
"Bypass banned checks or not. Choose 'default' to use global "
"settings."
)
)
bypass_header_checks = models.CharField(max_length=3, blank=True, null=True)
virus_quarantine_to = models.CharField(max_length=192, blank=True, null=True)
virus_quarantine_to = models.CharField(
max_length=192,
blank=True,
null=True)
spam_quarantine_to = models.CharField(max_length=192, blank=True, null=True)
banned_quarantine_to = models.CharField(max_length=192, blank=True, null=True)
unchecked_quarantine_to = models.CharField(max_length=192, blank=True, null=True)
bad_header_quarantine_to = models.CharField(max_length=192, blank=True, null=True)
clean_quarantine_to = models.CharField(max_length=192, blank=True, null=True)
archive_quarantine_to = models.CharField(max_length=192, blank=True, null=True)
banned_quarantine_to = models.CharField(
max_length=192,
blank=True,
null=True)
unchecked_quarantine_to = models.CharField(
max_length=192,
blank=True,
null=True)
bad_header_quarantine_to = models.CharField(
max_length=192,
blank=True,
null=True)
clean_quarantine_to = models.CharField(
max_length=192,
blank=True,
null=True)
archive_quarantine_to = models.CharField(
max_length=192,
blank=True,
null=True)
spam_tag_level = models.FloatField(null=True, blank=True)
spam_tag2_level = models.FloatField(null=True, blank=True)
spam_tag3_level = models.FloatField(null=True, blank=True)
spam_kill_level = models.FloatField(null=True, blank=True)
spam_dsn_cutoff_level = models.FloatField(null=True, blank=True)
spam_quarantine_cutoff_level = models.FloatField(null=True, blank=True)
addr_extension_virus = models.CharField(max_length=192, blank=True, null=True)
addr_extension_spam = models.CharField(max_length=192, blank=True, null=True)
addr_extension_banned = models.CharField(max_length=192, blank=True, null=True)
addr_extension_bad_header = models.CharField(max_length=192, blank=True, null=True)
addr_extension_virus = models.CharField(
max_length=192,
blank=True,
null=True)
addr_extension_spam = models.CharField(
max_length=192,
blank=True,
null=True)
addr_extension_banned = models.CharField(
max_length=192,
blank=True,
null=True)
addr_extension_bad_header = models.CharField(
max_length=192,
blank=True,
null=True)
warnvirusrecip = models.CharField(max_length=3, blank=True, null=True)
warnbannedrecip = models.CharField(max_length=3, blank=True, null=True)
warnbadhrecip = models.CharField(max_length=3, blank=True, null=True)
@@ -143,8 +183,11 @@ class Policy(models.Model):
spam_subject_tag2 = models.CharField(
ugettext_lazy("Spam marker"), default=None,
max_length=192, blank=True, null=True,
help_text=ugettext_lazy("Modify spam subject using the specified text. Choose 'default' to use global settings.")
help_text=ugettext_lazy(
"Modify spam subject using the specified text. Choose 'default' to "
"use global settings."
)
)
spam_subject_tag3 = models.CharField(max_length=192, blank=True, null=True)
message_size_limit = models.IntegerField(null=True, blank=True)
banned_rulenames = models.CharField(max_length=192, blank=True, null=True)
+3 -3
View File
@@ -6,7 +6,7 @@ import datetime
from django.db.models import Q
from modoboa.extensions.admin.models import Domain
from modoboa.lib.dbutils import db_type
from modoboa.lib.db_utils import db_type
from .models import Quarantine, Msgrcpt, Maddr
@@ -65,7 +65,7 @@ class SQLconnector(object):
rcpts = [self.user.email]
if self.user.mailbox_set.count():
rcpts += self.user.mailbox_set.all()[0].alias_addresses
return flt & Q(rid__email__in=rcpts)
return flt & Q(rid__email__in=rcpts)
elif not self.user.is_superuser:
doms = Domain.objects.get_for_admin(self.user)
regexp = "(%s)" % '|'.join([dom.name for dom in doms])
@@ -238,7 +238,7 @@ class PgSQLconnector(SQLconnector):
if self.user.mailbox_set.count():
rcpts += self.user.mailbox_set.all()[0].alias_addresses
self._where.append(
"convert_from(maddr.email, 'UTF8') IN (%s)" \
"convert_from(maddr.email, 'UTF8') IN (%s)"
% (','.join(["'%s'" % rcpt for rcpt in rcpts])))
elif not self.user.is_superuser:
doms = Domain.objects.get_for_admin(self.user)
+1 -1
View File
@@ -5,7 +5,7 @@ An email representation based on a database record.
from django.template.loader import render_to_string
from modoboa.lib.emailutils import Email
from modoboa.lib.email_utils import Email
from .sql_connector import get_connector
@@ -80,20 +80,23 @@ def viewm_menu(user, mail_id, rcpt):
@register.simple_tag
def viewm_menu_simple(user, mail_id, rcpt, secret_id=""):
release_url = "{0}?rcpt={1}".format(
reverse("amavis:mail_release", args=[mail_id]), rcpt)
delete_url = "{0}?rcpt={1}".format(
reverse("amavis:mail_delete", args=[mail_id]), rcpt)
if secret_id:
release_url += "&secret_id={0}".format(secret_id)
delete_url += "&secret_id={0}".format(secret_id)
entries = [
{"name": "release",
"img": "fa fa-check",
"class": "btn-success",
"url": reverse('amavis:mail_release', args=[mail_id]) \
+ ("?rcpt=%s" % rcpt \
+ (("&secret_id=%s" % secret_id) if secret_id != "" else "")),
"url": release_url,
"label": _("Release")},
{"name": "delete",
"img": "fa fa-trash",
"class": "btn-danger",
"url": reverse('amavis:mail_delete', args=[mail_id]) \
+ "?rcpt=%s" % rcpt \
+ ("&secret_id=%s" % secret_id if secret_id != "" else ""),
"url": delete_url,
"label": _("Delete")},
]
+2 -2
View File
@@ -16,7 +16,7 @@ from django.contrib.auth.decorators import login_required
from modoboa.lib import parameters
from modoboa.lib.exceptions import BadRequest
from modoboa.lib.paginator import Paginator
from modoboa.lib.webutils import (
from modoboa.lib.web_utils import (
getctx, render_to_json_response, _render_to_string
)
from modoboa.extensions.admin.models import Mailbox, Domain
@@ -179,7 +179,7 @@ def viewheaders(request, mail_id):
content = ""
for qm in get_connector().get_mail_content(mail_id):
content += qm.mail_text
if type(content) is unicode:
if isinstance(content, unicode):
content = content.encode("utf-8")
msg = email.message_from_string(content)
headers = []
+17 -7
View File
@@ -1,42 +1,52 @@
"""Custom settings."""
from django import forms
from django.utils.translation import ugettext_lazy as _
from modoboa.lib.form_utils import SeparatorField
from modoboa.lib.parameters import AdminParametersForm
from modoboa.lib.formutils import SeparatorField
class ParametersForm(AdminParametersForm):
app = "limits"
defv_sep = SeparatorField(label=_("Default limits"))
deflt_domain_admins_limit = forms.IntegerField(
label=_("Domain admins"),
initial=0,
help_text=_("Maximum number of allowed domain administrators for a new administrator"),
help_text=_(
"Maximum number of allowed domain administrators for a new "
"administrator"
),
widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"})
)
deflt_domains_limit = forms.IntegerField(
label=_("Domains"),
initial=0,
help_text=_("Maximum number of allowed domains for a new administrator"),
help_text=_(
"Maximum number of allowed domains for a new administrator"),
widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"})
)
deflt_domain_aliases_limit = forms.IntegerField(
label=_("Domain aliases"),
initial=0,
help_text=_("Maximum number of allowed domain aliases for a new administrator"),
help_text=_(
"Maximum number of allowed domain aliases for a new administrator"),
widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"})
)
deflt_mailboxes_limit = forms.IntegerField(
label=_("Mailboxes"),
initial=0,
help_text=_("Maximum number of allowed mailboxes for a new administrator"),
help_text=_(
"Maximum number of allowed mailboxes for a new administrator"),
widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"})
)
deflt_mailbox_aliases_limit = forms.IntegerField(
label=_("Mailbox aliases"),
initial=0,
help_text=_("Maximum number of allowed aliases for a new administrator"),
help_text=_(
"Maximum number of allowed aliases for a new administrator"),
widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"})
)
+5 -5
View File
@@ -4,10 +4,10 @@
----------------------------------------------------------------------------
"""
from modoboa.lib import events
from modoboa.lib.permissions import get_object_owner
from .lib import LimitReached, inc_limit_usage, dec_limit_usage
from .models import LimitTemplates, LimitsPool
from modoboa.lib import events
from modoboa.lib.permissions import get_object_owner
def check_limit(user, lname, count=1):
@@ -124,7 +124,7 @@ def can_create_new_object(user, objtype, count=1):
@events.observe("AccountCreated")
def create_pool(user):
owner = get_object_owner(user)
if not owner.group in ['SuperAdmins', 'Resellers']:
if owner.group not in ['SuperAdmins', 'Resellers']:
return
if user.group == 'DomainAdmins':
@@ -177,7 +177,7 @@ def on_account_modified(old, new):
p.save()
p.create_limits(owner)
if not new.group in ["DomainAdmins", "Resellers"]:
if new.group not in ["DomainAdmins", "Resellers"]:
move_pool_resource(owner, new)
if old.oldgroup == "DomainAdmins":
@@ -193,7 +193,7 @@ def on_account_modified(old, new):
@events.observe("AccountDeleted")
def on_account_deleted(account, byuser, **kwargs):
owner = get_object_owner(account)
if not owner.group in ["SuperAdmins", "Resellers"]:
if owner.group not in ["SuperAdmins", "Resellers"]:
return
move_pool_resource(owner, account)
+15 -8
View File
@@ -1,9 +1,13 @@
# coding: utf-8
"""Custom forms."""
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import parameters
from .models import LimitTemplates
from django.utils.translation import ugettext as _
from .lib import BadLimitValue, UnsufficientResource
from .models import LimitTemplates
from modoboa.lib import parameters
class ResourcePoolForm(forms.Form):
@@ -15,8 +19,9 @@ class ResourcePoolForm(forms.Form):
del kwargs["instance"]
super(ResourcePoolForm, self).__init__(*args, **kwargs)
for tpl in LimitTemplates().templates:
if len(tpl) > 3 and self.account is not None and self.account.group != tpl[3]:
continue
if len(tpl) > 3:
if self.account is not None and self.account.group != tpl[3]:
continue
self.fields[tpl[0]] = forms.IntegerField(
label=tpl[1], help_text=tpl[2]
)
@@ -31,7 +36,7 @@ class ResourcePoolForm(forms.Form):
def clean(self):
cleaned_data = super(ResourcePoolForm, self).clean()
for lname in self.fields.keys():
if not lname in self._errors and cleaned_data[lname] < -1:
if lname not in self._errors and cleaned_data[lname] < -1:
self._errors[lname] = self.error_class([_('Invalid limit')])
del cleaned_data[lname]
return cleaned_data
@@ -42,7 +47,9 @@ class ResourcePoolForm(forms.Form):
# The following lines will become useless in a near
# future.
if self.fields[lname].initial == -2:
self.fields[lname].initial = parameters.get_admin("DEFLT_%s" % lname.upper())
self.fields[lname].initial = parameters.get_admin(
"DEFLT_%s" %
lname.upper())
def allocate_from_pool(self, limit, pool):
"""Allocate resource using an existing pool.
@@ -76,7 +83,7 @@ class ResourcePoolForm(forms.Form):
owner = get_object_owner(self.account)
for ltpl in LimitTemplates().templates:
if not ltpl[0] in self.cleaned_data:
if ltpl[0] not in self.cleaned_data:
continue
l = self.account.limitspool.limit_set.get(name=ltpl[0])
if not owner.is_superuser:
@@ -1,7 +1,10 @@
"""General event callbacks."""
from django.utils.translation import ugettext as _
from modoboa.lib import events
from modoboa.extensions.limits.models import LimitTemplates
from modoboa.extensions.limits.forms import ResourcePoolForm
from modoboa.extensions.limits.models import LimitTemplates
from modoboa.lib import events
@events.observe("GetExtraRoles")
@@ -37,10 +40,10 @@ def display_pool_usage(user, target, currentpage):
@events.observe("ExtraAccountForm")
def extra_account_form(user, account=None):
if not user.group in ["SuperAdmins", "Resellers"]:
if user.group not in ["SuperAdmins", "Resellers"]:
return []
if account is not None and \
not account.group in ["Resellers", "DomainAdmins"]:
account.group not in ["Resellers", "DomainAdmins"]:
return []
return [
+2 -1
View File
@@ -5,8 +5,9 @@
"""
from django.utils.translation import ugettext as _
from modoboa.lib.exceptions import ModoboaException
from modoboa.extensions.limits.models import LimitsPool
from modoboa.lib.exceptions import ModoboaException
class LimitReached(ModoboaException):
+18 -9
View File
@@ -1,10 +1,13 @@
# coding: utf-8
"""Models for the limits extensions."""
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import parameters
from modoboa.lib import parameters, events
from modoboa.lib.singleton import Singleton
from django.conf import settings
from modoboa.lib import events
class LimitTemplates(Singleton):
@@ -12,23 +15,27 @@ class LimitTemplates(Singleton):
def __init__(self):
self.__templates = [
("domain_admins_limit", ugettext_lazy("Domain admins"),
ugettext_lazy("Maximum number of domain administrators this user can create"),
ugettext_lazy("Maximum number of domain administrators this user "
"can create"),
'Resellers'),
("domains_limit", ugettext_lazy("Domains"),
ugettext_lazy("Maximum number of domains this user can create"),
'Resellers'),
("domain_aliases_limit", ugettext_lazy("Domain aliases"),
ugettext_lazy("Maximum number of domain aliases this user can create"),
ugettext_lazy(
"Maximum number of domain aliases this user can create"),
'Resellers'),
("mailboxes_limit", ugettext_lazy("Mailboxes"),
ugettext_lazy("Maximum number of mailboxes this user can create")),
("mailbox_aliases_limit", ugettext_lazy("Mailbox aliases"),
ugettext_lazy("Maximum number of mailbox aliases this user can create"))
ugettext_lazy("Maximum number of mailbox aliases this user "
"can create"))
]
@property
def templates(self):
return self.__templates + events.raiseQueryEvent('GetExtraLimitTemplates')
return self.__templates + \
events.raiseQueryEvent('GetExtraLimitTemplates')
class LimitsPool(models.Model):
@@ -47,8 +54,10 @@ class LimitsPool(models.Model):
try:
Limit.objects.get(name=ltpl[0], pool=self)
except Limit.DoesNotExist:
maxvalue = int(parameters.get_admin("DEFLT_%s" % ltpl[0].upper())) \
maxvalue = (
int(parameters.get_admin("DEFLT_%s" % ltpl[0].upper()))
if creator.is_superuser else 0
)
Limit.objects.create(
name=ltpl[0], pool=self, maxvalue=maxvalue
)
+16 -8
View File
@@ -1,13 +1,16 @@
"""Test cases for the limits extension."""
from django.core.urlresolvers import reverse
from modoboa.lib.tests import ExtTestCase
from modoboa.lib import parameters
from modoboa.core.models import User
from modoboa.core.factories import UserFactory
from modoboa.core.models import User
from modoboa.extensions.admin.factories import (
populate_database
)
from modoboa.extensions.admin.models import Alias, Domain
from modoboa.extensions.limits.models import LimitTemplates
from modoboa.lib import parameters
from modoboa.lib.tests import ExtTestCase
class PermissionsTestCase(ExtTestCase):
@@ -142,8 +145,8 @@ class DomainAdminTestCase(ResourceTestCase):
resp = self._create_alias('alias3@test.com', status=403)
self._check_limit('mailbox_aliases', 2, 2)
self.ajax_post(
reverse('admin:alias_delete') + '?selection=%d' \
% Alias.objects.get(address='alias2', domain__name='test.com').id,
reverse('admin:alias_delete') + '?selection=%d'
% Alias.objects.get(address='alias2', domain__name='test.com').id,
{}
)
self._check_limit('mailbox_aliases', 1, 2)
@@ -206,10 +209,14 @@ class ResellerTestCase(ResourceTestCase):
self._check_limit('domain_admins', 1, 2)
self._create_account('admin2@domain.tld', role='DomainAdmins')
self._check_limit('domain_admins', 2, 2)
resp = self._create_account('admin3@domain.tld', role='DomainAdmins', status=400)
resp = self._create_account(
'admin3@domain.tld',
role='DomainAdmins',
status=400)
self.assertEqual(
resp['form_errors']['role'][0],
'Select a valid choice. DomainAdmins is not one of the available choices.'
'Select a valid choice. DomainAdmins is not one of the available '
'choices.'
)
self._check_limit('domain_admins', 2, 2)
@@ -227,7 +234,8 @@ class ResellerTestCase(ResourceTestCase):
)
self.assertEqual(
resp['form_errors']['role'][0],
'Select a valid choice. DomainAdmins is not one of the available choices.'
'Select a valid choice. DomainAdmins is not one of the available '
'choices.'
)
self._check_limit('domain_admins', 2, 2)
+1 -1
View File
@@ -3,4 +3,4 @@ from django.conf.urls import patterns
urlpatterns = patterns(
'modoboa.extensions.limits.views',
)
)
@@ -7,23 +7,27 @@ functionality into Postfix.
"""
import sys
from django.utils.translation import ugettext_lazy
from modoboa.lib import events, parameters
from modoboa.core.extensions import ModoExtension, exts_pool
from modoboa.extensions.postfix_autoreply.models import (
Transport, Alias
)
from modoboa.lib import events, parameters
class PostfixAutoreply(ModoExtension):
"""
Auto-reply (vacation) functionality using Postfix.
"""
name = "postfix_autoreply"
label = "Postfix autoreply"
version = "1.0"
description = ugettext_lazy("Auto-reply (vacation) functionality using Postfix")
description = ugettext_lazy(
"Auto-reply (vacation) functionality using Postfix")
def init(self):
from modoboa.extensions.admin.models import Domain
@@ -45,10 +49,14 @@ class PostfixAutoreply(ModoExtension):
onMailboxCreated(None, mb)
def load(self):
from modoboa.extensions.postfix_autoreply.app_settings import ParametersForm
parameters.register(ParametersForm, ugettext_lazy("Automatic replies"))
from modoboa.extensions.postfix_autoreply.app_settings import (
ParametersForm
)
parameters.register(
ParametersForm, ugettext_lazy("Automatic replies"))
from modoboa.extensions.postfix_autoreply import general_callbacks
if 'modoboa.extensions.postfix_autoreply.general_callbacks' in sys.modules:
modname = "modoboa.extensions.postfix_autoreply.general_callbacks"
if modname in sys.modules:
reload(general_callbacks)
def destroy(self):
@@ -1,8 +1,12 @@
# coding: utf-8
from django.utils.translation import ugettext_lazy
"""Custom settings."""
from django import forms
from django.utils.translation import ugettext_lazy
from modoboa.lib.form_utils import SeparatorField
from modoboa.lib.parameters import AdminParametersForm
from modoboa.lib.formutils import SeparatorField
class ParametersForm(AdminParametersForm):
@@ -13,7 +17,9 @@ class ParametersForm(AdminParametersForm):
autoreplies_timeout = forms.IntegerField(
label=ugettext_lazy("Automatic reply timeout"),
initial=86400,
help_text=ugettext_lazy("Timeout in seconds between two auto-replies to the same recipient"),
help_text=ugettext_lazy(
"Timeout in seconds between two auto-replies to the same recipient"
),
widget=forms.TextInput(attrs={"class": "form-control"})
)
@@ -21,7 +27,8 @@ class ParametersForm(AdminParametersForm):
label=ugettext_lazy("Default subject"),
initial=ugettext_lazy("I'm off"),
help_text=ugettext_lazy(
"Default subject used when an auto-reply message is created automatically"
"Default subject used when an auto-reply message is created "
"automatically"
),
widget=forms.TextInput(attrs={"class": "form-control"})
)
+18 -12
View File
@@ -1,11 +1,16 @@
# coding: utf-8
"""Custom forms."""
from django import forms
from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_lazy
from .models import ARmessage
class ARmessageForm(forms.ModelForm):
fromdate = forms.DateTimeField(
label=ugettext_lazy('From'),
required=False,
@@ -23,15 +28,15 @@ class ARmessageForm(forms.ModelForm):
)
)
subject = forms.CharField(
widget=forms.TextInput(
attrs={'class': 'form-control'}
)
widget=forms.TextInput(
attrs={'class': 'form-control'}
)
)
content = forms.CharField(
widget=forms.Textarea(
attrs={'class': 'form-control'}
)
widget=forms.Textarea(
attrs={'class': 'form-control'}
)
)
class Meta:
model = ARmessage
@@ -67,13 +72,14 @@ class ARmessageForm(forms.ModelForm):
else:
self.cleaned_data['fromdate'] = timezone.now()
if self.cleaned_data["untildate"] is not None:
if self.cleaned_data["untildate"] < timezone.now():
untildate = self.cleaned_data["untildate"]
if untildate < timezone.now():
self._errors["untildate"] = self.error_class(
[_("This date is over")])
del self.cleaned_data['untildate']
elif 'fromdate' in self.cleaned_data and \
self.cleaned_data['untildate'] < self.cleaned_data['fromdate']:
del self.cleaned_data["untildate"]
elif "fromdate" in self.cleaned_data and \
untildate < self.cleaned_data["fromdate"]:
self._errors["untildate"] = \
self.error_class([_("Must be greater than start date")])
del self.cleaned_data['untildate']
del self.cleaned_data["untildate"]
return self.cleaned_data
@@ -1,8 +1,11 @@
"""Event callbacks."""
from django.utils import timezone
from django.utils.translation import ugettext_lazy
from modoboa.lib import events, parameters
from modoboa.extensions.postfix_autoreply.models import Transport, Alias
from .models import ARmessage
from modoboa.extensions.postfix_autoreply.models import Transport, Alias
from modoboa.lib import events, parameters
@events.observe("ExtraUprefsJS")
@@ -45,8 +48,11 @@ def onDomainModified(domain):
return
Transport.objects.filter(domain="autoreply.%s" % domain.oldname) \
.update(domain="autoreply.%s" % domain.name)
for al in Alias.objects.filter(full_address__contains="@%s" % domain.oldname):
new_address = al.full_address.replace("@%s" % domain.oldname, "@%s" % domain.name)
for al in Alias.objects.filter(
full_address__contains="@%s" % domain.oldname):
new_address = al.full_address.replace(
"@%s" % domain.oldname,
"@%s" % domain.name)
al.full_address = new_address
al.autoreply_address = "%s@autoreply.%s" % (new_address, domain.name)
al.save()
@@ -103,7 +109,7 @@ def extra_mailform_fields(form_name, mailbox=None):
:param str form_name: form name (must be 'mailform')
:param Mailbox mailbox: mailbox
"""
from modoboa.lib.formutils import YesNoField
from modoboa.lib.form_utils import YesNoField
if form_name != "mailform":
return []
@@ -8,7 +8,7 @@ from django.utils import timezone
from modoboa.core.management.commands import CloseConnectionMixin
from modoboa.lib import parameters
from modoboa.lib.emailutils import split_mailbox, sendmail_simple
from modoboa.lib.email_utils import split_mailbox, sendmail_simple
from modoboa.extensions.admin.models import Mailbox
from modoboa.extensions.postfix_autoreply import PostfixAutoreply
from modoboa.extensions.postfix_autoreply.models import ARmessage, ARhistoric
@@ -18,7 +18,7 @@ def send_autoreply(sender, mailbox, armessage):
if armessage.fromdate > timezone.now():
return
if armessage.untildate is not None \
and armessage.untildate < timezone.now():
and armessage.untildate < timezone.now():
armessage.enabled = False
armessage.save()
return
@@ -51,7 +51,8 @@ class Command(BaseCommand, CloseConnectionMixin):
def handle(self, *args, **options):
if len(args) < 2:
raise CommandError("usage: ./manage.py autoreply <sender> <recipient ...>")
raise CommandError(
"usage: ./manage.py autoreply <sender> <recipient ...>")
sender = args[0]
for fulladdress in args[1:]:
@@ -3,7 +3,7 @@ import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
from modoboa.lib.dbutils import db_table_exists
from modoboa.lib.db_utils import db_table_exists
class Migration(SchemaMigration):
@@ -82,7 +82,6 @@ class EventsTestCase(ExtTestCase):
ARmessage.objects.get(
mbox__address='user', mbox__domain__name='test.com')
def test_modify_mailbox_event(self):
values = {
'username': "leon@test.com",
@@ -2,7 +2,7 @@
from datetime import date
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from modoboa.lib.webutils import (
from modoboa.lib.web_utils import (
render_to_json_response, _render_to_string
)
from modoboa.extensions.admin.lib import needs_mailbox
@@ -1,13 +1,16 @@
# coding: utf-8
import sys
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy
from modoboa.lib import events, parameters
from modoboa.core.extensions import ModoExtension, exts_pool
from .models import RelayDomain, RelayDomainAlias, Service
extension_events = [
from .models import RelayDomain, RelayDomainAlias, Service
from modoboa.core.extensions import ModoExtension, exts_pool
from modoboa.lib import events, parameters
EXTENSION_EVENTS = [
"RelayDomainCreated",
"RelayDomainDeleted",
"RelayDomainModified",
@@ -66,6 +69,9 @@ def init_amavis_dependant_features():
class PostfixRelayDomains(ModoExtension):
"""Extension declaration."""
name = "postfix_relay_domains"
label = "Postfix relay domains"
version = "1.0"
@@ -88,17 +94,18 @@ class PostfixRelayDomains(ModoExtension):
parameters.register(
AdminParametersForm, ugettext_lazy("Relay domains")
)
events.declare(extension_events)
events.declare(EXTENSION_EVENTS)
from modoboa.extensions.postfix_relay_domains import general_callbacks
if 'modoboa.extensions.postfix_relay_domains.general_callbacks' in sys.modules:
prefix = "modoboa.extensions.postfix_relay_domains."
if prefix + "general_callbacks" in sys.modules:
reload(general_callbacks)
if exts_pool.is_extension_enabled('limits'):
import limits_callbacks
if 'modoboa.extensions.postfix_relay_domains.limits_callbacks' in sys.modules:
if prefix + "limits_callbacks" in sys.modules:
reload(limits_callbacks)
if exts_pool.is_extension_enabled('amavis'):
import amavis_callbacks
if 'modoboa.extensions.postfix_relay_domains.amavis_callbacks' in sys.modules:
if prefix + "amavis_callbacks" in sys.modules:
reload(amavis_callbacks)
def destroy(self):
@@ -6,7 +6,7 @@ from django import forms
from django.utils.translation import ugettext_lazy
from modoboa.lib import parameters
from modoboa.lib.formutils import SeparatorField
from modoboa.lib.form_utils import SeparatorField
class AdminParametersForm(parameters.AdminParametersForm):
@@ -2,14 +2,15 @@
Postfix relay domains extension forms.
"""
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.core.urlresolvers import reverse
from modoboa.lib import events
from modoboa.lib.formutils import DynamicForm, DomainNameField, TabForms
from modoboa.lib.webutils import render_to_json_response
from modoboa.extensions.admin.models import Domain, DomainAlias
from .models import RelayDomain, RelayDomainAlias
from modoboa.extensions.admin.models import Domain, DomainAlias
from modoboa.lib import events
from modoboa.lib.form_utils import DynamicForm, DomainNameField, TabForms
from modoboa.lib.web_utils import render_to_json_response
class RelayDomainFormGeneral(forms.ModelForm, DynamicForm):
@@ -113,7 +114,7 @@ class RelayDomainFormGeneral(forms.ModelForm, DynamicForm):
continue
aliases.append(v)
for rdalias in rd.relaydomainalias_set.all():
if not rdalias.name in aliases:
if rdalias.name not in aliases:
rdalias.delete()
else:
aliases.remove(rdalias.name)
@@ -137,11 +138,13 @@ class RelayDomainFormGeneral(forms.ModelForm, DynamicForm):
class RelayDomainForm(TabForms):
"""Specific edition form for relay domains.
We use a *tabs* compatible form because extensions can add their
own tab. (ex: amavis)
"""
def __init__(self, request, *args, **kwargs):
self.user = request.user
self.forms = []
@@ -1,10 +1,13 @@
from django.core.urlresolvers import reverse
from django.template import Template, Context
"""General event callbacks."""
from django.conf import settings
from django.utils.translation import ugettext_lazy
from django.core.urlresolvers import reverse
from django.db.models import Q
from modoboa.lib import events
from django.template import Template, Context
from django.utils.translation import ugettext_lazy
from .models import RelayDomain, RelayDomainAlias
from modoboa.lib import events
@events.observe('GetStaticContent')
@@ -101,7 +104,8 @@ def extra_limit_templates():
ugettext_lazy('Maximum number of relay domains this user can create'),
'Resellers'),
("relay_domain_aliases_limit", ugettext_lazy("Relay domain aliases"),
ugettext_lazy('Maximum number of relay domain aliases this user can create'),
ugettext_lazy(
'Maximum number of relay domain aliases this user can create'),
'Resellers'),
]
@@ -1,8 +1,11 @@
"""Callbacks related to limits extension."""
from django.utils.translation import ugettext_lazy
from .models import RelayDomainAlias
from modoboa.extensions.limits.lib import inc_limit_usage, dec_limit_usage
from modoboa.lib import events
from modoboa.lib.permissions import get_object_owner
from modoboa.extensions.limits.lib import inc_limit_usage, dec_limit_usage
from .models import RelayDomainAlias
@events.observe('GetExtraParameters')
@@ -16,17 +19,23 @@ def extra_parameters(app, level):
label=ugettext_lazy("Relay domains"),
initial=0,
help_text=ugettext_lazy(
"Maximum number of allowed relay domains for a new administrator"
"Maximum number of allowed relay domains for a new "
"administrator"
),
widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"})
widget=forms.widgets.TextInput(
attrs={
"class": "col-md-1 form-control"})
),
'deflt_relay_domain_aliases_limit': forms.IntegerField(
label=ugettext_lazy("Relay domain aliases"),
initial=0,
help_text=ugettext_lazy(
"Maximum number of allowed relay domain aliases for a new administrator"
"Maximum number of allowed relay domain aliases for a new "
"administrator"
),
widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"})
widget=forms.widgets.TextInput(
attrs={
"class": "col-md-1 form-control"})
)
}
@@ -1,12 +1,16 @@
import reversion
"""Relay domain related models."""
from django.contrib.contenttypes import generic
from django.db import models
from django.db.models.manager import Manager
from django.contrib.contenttypes import generic
from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
from modoboa.core.models import ObjectAccess
from modoboa.extensions.admin.models import AdminObject
from modoboa.lib import parameters
from modoboa.lib.exceptions import BadRequest, NotFound
from modoboa.extensions.admin.models import AdminObject
class RelayDomainManager(Manager):
@@ -46,12 +50,13 @@ class ServiceManager(Manager):
services.append(parts[0])
to_delete = []
for service in self.all():
if not service.name in services:
if service.name not in services:
to_delete.append(service.name)
Service.objects.filter(name__in=to_delete).delete()
class Service(models.Model):
"""Postfix service.
"""
name = models.CharField(
@@ -66,6 +71,7 @@ class Service(models.Model):
class RelayDomain(AdminObject):
"""Relay domain.
A relay domain differs from a usual domaine because its final
@@ -101,7 +107,7 @@ class RelayDomain(AdminObject):
def tags(self):
return [
{"name": "relaydomain", "label": _("Relay Domain"), "type": "dom"},
{"name": self.service.name, "label": "%s:" % self.service.name,
{"name": self.service.name, "label": "%s:" % self.service.name,
"type": "srv", "color": "info"}
]
@@ -118,7 +124,7 @@ class RelayDomain(AdminObject):
:param csvwriter:
"""
csvwriter.writerow(
["relaydomain", self.name, self.target_host,
["relaydomain", self.name, self.target_host,
self.service.name, self.enabled, self.verify_recipients]
)
for rdalias in self.relaydomainalias_set.all():
@@ -134,7 +140,8 @@ class RelayDomain(AdminObject):
raise BadRequest(_("Invalid line"))
self.name = row[1].strip()
self.target_host = row[2].strip()
self.service, created = Service.objects.get_or_create(name=row[3].strip())
self.service, created = Service.objects.get_or_create(
name=row[3].strip())
self.enabled = (row[4].strip() == 'True')
self.verify_recipients = (row[5].strip() == 'True')
self.save(creator=user)
@@ -152,6 +159,7 @@ reversion.register(RelayDomain)
class RelayDomainAlias(AdminObject):
"""Relay domain alias.
"""
@@ -9,7 +9,7 @@ from django.contrib.auth.decorators import (
)
from modoboa.lib import events
from modoboa.lib.exceptions import PermDeniedException
from modoboa.lib.webutils import render_to_json_response
from modoboa.lib.web_utils import render_to_json_response
from .models import RelayDomain, Service
from .forms import RelayDomainForm, RelayDomainFormGeneral
+7 -3
View File
@@ -3,9 +3,11 @@ Radicale management frontend.
"""
import sys
from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy
from modoboa.lib import events, parameters
from modoboa.lib.permissions import add_permissions_to_group
from modoboa.core.extensions import ModoExtension, exts_pool
@@ -28,14 +30,16 @@ def init_limits_dependant_features():
class Radicale(ModoExtension):
"""
"""
"""Radicale extension declaration."""
name = "radicale"
label = ugettext_lazy("Radicale management")
version = "1.0"
url = "calendars"
description = ugettext_lazy(
"Management frontend for Radicale, a simple calendar and contact server."
"Management frontend for Radicale, a simple calendar and contact "
"server."
)
def init(self):
+1 -1
View File
@@ -1,7 +1,7 @@
from django.utils.translation import ugettext_lazy
from django import forms
from modoboa.lib.parameters import AdminParametersForm
from modoboa.lib.formutils import SeparatorField, YesNoField
from modoboa.lib.form_utils import SeparatorField, YesNoField
class ParametersForm(AdminParametersForm):
+6 -4
View File
@@ -1,13 +1,15 @@
"""
Radicale extension forms.
"""
from django import forms
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib.webutils import render_to_json_response
from modoboa.lib.formutils import WizardForm, TabForms, DynamicForm
from modoboa.extensions.admin.models import Domain, Mailbox
from modoboa.extensions.radicale.models import UserCalendar, SharedCalendar
from modoboa.lib.form_utils import WizardForm, TabForms, DynamicForm
from modoboa.lib.web_utils import render_to_json_response
class UserCalendarForm(forms.ModelForm):
@@ -109,7 +111,7 @@ class RightsForm(forms.Form, DynamicForm):
def save(self):
"""Custom save method."""
import re
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import BadRequest
from modoboa.extensions.admin.models import Mailbox
from .models import AccessRule
@@ -122,7 +124,7 @@ class RightsForm(forms.Form, DynamicForm):
pos = int(res.group(1)) if res else None
usernames[value] = pos
for rule in self.calendar.rules.select_related().all():
if not rule.mailbox.full_address in usernames:
if rule.mailbox.full_address not in usernames:
rule.delete()
for username, pos in usernames.iteritems():
local_part, domname = split_mailbox(username)
@@ -2,15 +2,17 @@
Custom template tags.
"""
from django import template
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from modoboa.lib.webutils import render_actions
from modoboa.lib.templatetags.lib_tags import render_link
from modoboa.lib.web_utils import render_actions
register = template.Library()
@register.simple_tag
def radicale_left_menu(user):
"""Menu located inside the left column.
@@ -96,7 +98,7 @@ def render_rule_fields(form):
result = ""
while True:
fname = "username_%d" % cpt
if not fname in form.fields:
if fname not in form.fields:
break
rfieldname = "read_access_%d" % cpt
wfieldname = "write_access_%d" % cpt
+1 -1
View File
@@ -11,7 +11,7 @@ from django.contrib.auth.decorators import (
from django.utils.translation import ugettext as _
from modoboa.lib.listing import get_sort_order, get_listing_page
from modoboa.lib.webutils import (
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
)
from modoboa.lib.exceptions import NotFound, PermDeniedException
@@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _
from django import forms
from sievelib.managesieve import SUPPORTED_AUTH_MECHS
from modoboa.lib.parameters import AdminParametersForm, UserParametersForm
from modoboa.lib.formutils import SeparatorField, YesNoField, InlineRadioSelect
from modoboa.lib.form_utils import SeparatorField, YesNoField, InlineRadioSelect
def supported_auth_mechs():
+46 -16
View File
@@ -1,10 +1,13 @@
"""Custom forms."""
from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from django.forms.widgets import RadioFieldRenderer, RadioSelect, RadioInput
from django.utils.safestring import mark_safe
from django.utils.html import conditional_escape
from django.http import QueryDict
from django.utils.encoding import force_unicode
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.extensions.admin.templatetags.admin_tags import gender
@@ -20,6 +23,7 @@ class FiltersSetForm(forms.Form):
class CustomRadioInput(RadioInput):
def __unicode__(self):
return self.render()
@@ -39,13 +43,16 @@ class CustomRadioInput(RadioInput):
class CustomRadioFieldRenderer(RadioFieldRenderer):
def __iter__(self):
for i, choice in enumerate(self.choices):
yield CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, i)
yield CustomRadioInput(
self.name, self.value, self.attrs.copy(), choice, i)
def __getitem__(self, idx):
choice = self.choices[idx]
return CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
return CustomRadioInput(
self.name, self.value, self.attrs.copy(), choice, idx)
def render(self):
return mark_safe(u'\n'.join([force_unicode(w) for w in self]))
@@ -56,6 +63,7 @@ class CustomRadioSelect(RadioSelect):
class FilterForm(forms.Form):
def __init__(self, conditions, actions, request, *args, **kwargs):
super(FilterForm, self).__init__(*args, **kwargs)
@@ -80,10 +88,18 @@ class FilterForm(forms.Form):
]
self.cond_templates = [
{"name": "Subject", "label": _("Subject"), "operators": self.header_operators},
{"name": "From", "label": _("Sender"), "operators": self.header_operators},
{"name": "To", "label": _("Recipient"), "operators": self.header_operators},
{"name": "Cc", "label": _("Cc"), "operators": self.header_operators},
{"name": "Subject",
"label": _("Subject"),
"operators": self.header_operators},
{"name": "From",
"label": _("Sender"),
"operators": self.header_operators},
{"name": "To",
"label": _("Recipient"),
"operators": self.header_operators},
{"name": "Cc",
"label": _("Cc"),
"operators": self.header_operators},
{"name": "size", "label": _("Size"),
"operators": [("over", _("is greater than"), "number"),
("under", _("is less than"), "number")]},
@@ -160,10 +176,14 @@ class FilterForm(forms.Form):
arg = args[cnt]
aname = "action_arg_%d_%d" % (self.actions_cnt, cnt)
if arg["type"] == "string":
self.fields[aname] = forms.CharField(max_length=255, initial=value)
self.fields[aname] = forms.CharField(
max_length=255,
initial=value)
elif arg["type"] == "list":
choices = getattr(self, arg["vloader"])(request)
self.fields[aname] = forms.ChoiceField(initial=value, choices=choices)
self.fields[aname] = forms.ChoiceField(
initial=value,
choices=choices)
self.actions_cnt += 1
def _build_redirect_field(self, request, value):
@@ -177,7 +197,10 @@ class FilterForm(forms.Form):
for fd in folders:
value = fd["path"] if "path" in fd else fd["name"]
if parentmb:
ret += [(value, fd["name"].replace("%s%s" % (parentmb, imapc.hdelimiter), ""))]
ret += [
(value, fd["name"].replace(
"%s%s" % (parentmb, imapc.hdelimiter), ""))
]
else:
ret += [(value, fd["name"])]
if "sub" in fd:
@@ -206,7 +229,10 @@ class FilterForm(forms.Form):
argcpt = 0
while True:
try:
naction += (self.cleaned_data["action_arg_%d_%d" % (cpt, argcpt)],)
naction += (
self.cleaned_data[
"action_arg_%d_%d" %
(cpt, argcpt)],)
except KeyError:
break
argcpt += 1
@@ -228,8 +254,12 @@ def build_filter_form_from_qdict(request):
if cpt == int(request.POST["conds"]):
break
if "cond_target_%d" % i in request.POST:
qdict["cond_target_%d" % cpt] = request.POST["cond_target_%d" % i]
qdict["cond_operator_%d" % cpt] = request.POST["cond_operator_%d" % i]
qdict["cond_target_%d" % cpt] = (
request.POST["cond_target_%d" % i]
)
qdict["cond_operator_%d" % cpt] = (
request.POST["cond_operator_%d" % i]
)
qdict["cond_value_%d" % cpt] = request.POST["cond_value_%d" % i]
condtarget = request.POST["cond_target_%d" % i]
condop = request.POST["cond_operator_%d" % i]
+17 -9
View File
@@ -1,12 +1,16 @@
# coding: utf-8
from django.utils.translation import ugettext as _
"""Internal tools."""
from sievelib.factory import FiltersSet
from sievelib.managesieve import Client, Error
from sievelib.parser import Parser
from sievelib.factory import FiltersSet
from django.utils.translation import ugettext as _
from modoboa.lib import parameters
from modoboa.lib.exceptions import ModoboaException
from modoboa.lib.connections import ConnectionsManager, ConnectionError
from modoboa.lib.exceptions import ModoboaException
class SieveClientError(ModoboaException):
@@ -19,7 +23,7 @@ class SieveClient(object):
def __init__(self, user=None, password=None):
try:
ret, msg = self.login(user, password)
except Error, e:
except Error as e:
raise ConnectionError(str(e))
if not ret:
raise ConnectionError(msg)
@@ -38,7 +42,10 @@ class SieveClient(object):
except Error:
ret = False
if not ret:
return False, _("Connection to MANAGESIEVE server failed, check your configuration")
return False, _(
"Connection to MANAGESIEVE server failed, check your "
"configuration"
)
return True, None
def logout(self):
@@ -51,13 +58,13 @@ class SieveClient(object):
if self.msc is not None:
try:
self.msc.capability()
except Error, e:
except Error as e:
pass
else:
return
try:
ret, msg = self.login(user, password)
except (Error, ssl.SSLError), e:
except (Error, ssl.SSLError) as e:
raise ConnectionError(e)
if not ret:
raise ConnectionError(msg)
@@ -80,10 +87,11 @@ class SieveClient(object):
return fs
def pushscript(self, name, content, active=False):
if type(content) is unicode:
if isinstance(content, unicode):
content = content.encode("utf-8")
if not self.msc.havespace(name, len(content)):
error = "%s (%s)" % (_("Not enough space on server"), self.msc.errmsg)
error = "%s (%s)" % (
_("Not enough space on server"), self.msc.errmsg)
raise SieveClientError(error)
if not self.msc.putscript(name, content):
raise SieveClientError(self.msc.errmsg)
@@ -3,7 +3,7 @@ from django.core.urlresolvers import reverse
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from modoboa.lib.webutils import render_actions
from modoboa.lib.web_utils import render_actions
from modoboa.lib.templatetags.form_tags import configure_field_classes
register = template.Library()
@@ -34,7 +34,7 @@ def fset_menu(mode, setname):
"img": "fa fa-plus",
"label": _("New filter"),
"url": reverse("sievefilters:filter_add",
args=[setname]),
args=[setname]),
"modal": True,
"autowidth": True,
"modalcb": "filterform_cb"}
@@ -45,7 +45,7 @@ def fset_menu(mode, setname):
"img": "fa fa-download-alt",
"label": _("Save filters set"),
"url": reverse("sievefilters:fs_save",
args=[setname])},
args=[setname])},
]
entries += [
@@ -53,7 +53,7 @@ def fset_menu(mode, setname):
"img": "fa fa-check",
"label": _("Activate filters set"),
"url": reverse("sievefilters:fs_activate",
args=[setname])},
args=[setname])},
{"name": "removefs",
"img": "fa fa-trash",
"label": _("Remove filters set"),
+1 -2
View File
@@ -2,7 +2,7 @@ if __name__ == "__main__":
clt = ManageSieveClient("mail.koalabs.org", 2000)
if clt.connect("tonio@ngyn.org", "aka/12;c", starttls=True):
print clt.get_sasl_mechanisms()
print clt.get_sieve_capabilities()
print clt.get_sieve_capabilities()
print clt.havespace("test", 45)
if not clt.putscript("myscript", """require ["fileinto", "envelope"];
@@ -40,4 +40,3 @@ InvalidSieveCommand
else:
print clt.errmsg
clt.logout()
+34 -25
View File
@@ -1,25 +1,31 @@
# coding: utf-8
from sievelib.managesieve import Error
from sievelib.commands import BadArgument, BadValue
"""Custom views."""
from rfc6266 import build_header
from django.shortcuts import render
from django.http import HttpResponse
from django.template.loader import render_to_string
from sievelib.commands import BadArgument, BadValue
from sievelib.managesieve import Error
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from modoboa.lib import parameters
from modoboa.lib.webutils import _render_error, \
ajax_response, render_to_json_response
from modoboa.lib.connections import ConnectionError
from modoboa.lib.exceptions import BadRequest
from modoboa.extensions.admin.lib import needs_mailbox
from .lib import SieveClient, SieveClientError
from django.http import HttpResponse
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from .forms import (
FilterForm, build_filter_form_from_qdict, build_filter_form_from_filter,
FiltersSetForm
)
from .lib import SieveClient, SieveClientError
from .templatetags.sfilters_tags import fset_menu
from modoboa.extensions.admin.lib import needs_mailbox
from modoboa.lib import parameters
from modoboa.lib.connections import ConnectionError
from modoboa.lib.exceptions import BadRequest
from modoboa.lib.web_utils import (
_render_error, ajax_response, render_to_json_response
)
@login_required
@@ -30,12 +36,12 @@ def index(request, tplname="sievefilters/index.html"):
try:
sc = SieveClient(user=request.user.username,
password=request.session["password"])
except ConnectionError, e:
except ConnectionError as e:
return _render_error(request, user_context={"error": e})
try:
active_script, scripts = sc.listscripts()
except Error, e:
except Error as e:
return _render_error(request, user_context={"error": e})
if active_script is None:
@@ -73,7 +79,7 @@ def getfs(request, name):
error = None
try:
content = sc.getscript(name, format=editormode)
except SieveClientError, e:
except SieveClientError as e:
error = str(e)
else:
if content is None:
@@ -104,7 +110,8 @@ def build_filter_ctx(ctx, form):
return ctx
def submitfilter(request, setname, okmsg, tplname, tplctx, update=False, sc=None):
def submitfilter(
request, setname, okmsg, tplname, tplctx, update=False, sc=None):
form = build_filter_form_from_qdict(request)
if form.is_valid():
if sc is None:
@@ -169,11 +176,13 @@ def editfilter(request, setname, fname, tplname="sievefilters/filter.html"):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
if request.method == "POST":
return submitfilter(request, setname, _("Filter modified"), tplname, ctx,
update=True, sc=sc)
return submitfilter(
request, setname, _("Filter modified"), tplname, ctx,
update=True, sc=sc
)
fset = sc.getscript(setname, format="fset")
if type(fname) is unicode:
if isinstance(fname, unicode):
fname = fname.encode("utf-8")
f = fset.getfilter(fname)
form = build_filter_form_from_filter(request, fname, f)
@@ -199,13 +208,13 @@ def removefilter(request, setname, fname):
@login_required
@needs_mailbox()
def savefs(request, name):
if not "scriptcontent" in request.POST:
if "scriptcontent" not in request.POST:
return
sc = SieveClient(user=request.user.username,
password=request.session["password"])
try:
sc.pushscript(name, request.POST["scriptcontent"])
except SieveClientError, e:
except SieveClientError as e:
error = str(e)
return ajax_response(request, "ko", respmsg=error)
return ajax_response(request, respmsg=_("Filters set saved"))
@@ -258,7 +267,7 @@ def activate_filters_set(request, name):
password=request.session["password"])
try:
sc.activatescript(name)
except SieveClientError, e:
except SieveClientError as e:
return ajax_response(request, "ko", respmsg=str(e))
return ajax_response(request, respmsg=_("Filters set activated"))
@@ -270,7 +279,7 @@ def download_filters_set(request, name):
password=request.session["password"])
try:
script = sc.getscript(name)
except SieveClientError, e:
except SieveClientError as e:
return ajax_response(request, "ko", respmsg=str(e))
resp = HttpResponse(script)
@@ -285,7 +294,7 @@ def download_filters_set(request, name):
def toggle_filter_state(request, setname, fname):
sc = SieveClient(user=request.user.username,
password=request.session["password"])
if type(fname) is unicode:
if isinstance(fname, unicode):
fname = fname.encode("utf-8")
fset = sc.getscript(setname, format="fset")
if fset.is_filter_disabled(fname):
+1 -1
View File
@@ -2,7 +2,7 @@
from django.utils.translation import ugettext_lazy
from django import forms
from modoboa.lib.parameters import AdminParametersForm
from modoboa.lib.formutils import SeparatorField
from modoboa.lib.form_utils import SeparatorField
class ParametersForm(AdminParametersForm):
+7 -1
View File
@@ -7,10 +7,12 @@ from modoboa.lib.sysutils import exec_cmd
class Curve(object):
"""Graphic curve.
Simple way to represent a graphic curve.
"""
def __init__(self, dsname, color, legend, cfunc="AVERAGE"):
"""Constructor.
"""
@@ -37,9 +39,11 @@ class Curve(object):
class Graphic(object):
"""Graphic.
"""
def __init__(self):
"""Constructor.
"""
@@ -76,7 +80,7 @@ class Graphic(object):
cmdargs += curve.to_rrd_command_args(rrdfile)
cmd = "rrdtool xport --start %s --end %s " % (str(start), str(end))
cmd += " ".join(cmdargs)
if type(cmd) is unicode:
if isinstance(cmd, unicode):
cmd = cmd.encode("utf-8")
code, output = exec_cmd(cmd)
if code:
@@ -95,6 +99,7 @@ class Graphic(object):
class AverageTraffic(Graphic):
"""Average traffic.
"""
title = ugettext_lazy('Average traffic (msgs/min)')
@@ -109,6 +114,7 @@ class AverageTraffic(Graphic):
class AverageTrafficSize(Graphic):
"""Average traffic size.
"""
title = ugettext_lazy('Average normal traffic size (bytes/min)')
+1 -1
View File
@@ -12,7 +12,7 @@ def date_to_timestamp(timetuple):
:return: an integer
"""
date = " ".join(
[("%d" % elem) if type(elem) is int else elem for elem in timetuple]
[("%d" % elem) if isinstance(elem, int) else elem for elem in timetuple]
)
fmt = "%Y %m %d %H %M %S" \
if timetuple[1].isdigit() else "%Y %b %d %H %M %S"
@@ -21,10 +21,11 @@ import time
import sys
import os
import re
import rrdtool
import string
from optparse import make_option
import rrdtool
from django.core.management.base import BaseCommand
from modoboa.core.extensions import exts_pool
@@ -71,9 +72,11 @@ class LogParser(object):
self.workdict = {}
self.lupdates = {}
self._s_date_expr = \
re.compile(r"(?P<month>\w+)\s+(?P<day>\d+)\s+(?P<hour>\d+):(?P<min>\d+):(?P<sec>\d+)(?P<eol>.*)")
re.compile(
r"(?P<month>\w+)\s+(?P<day>\d+)\s+(?P<hour>\d+):(?P<min>\d+):(?P<sec>\d+)(?P<eol>.*)")
self._hp_date_expr = \
re.compile(r"(?P<year>\d+)-(?P<month>\d+)-(?P<day>\d+)T(?P<hour>\d+):(?P<min>\d+):(?P<sec>\d+)\.\d+\+\d+:\d+(?P<eol>.*)")
re.compile(
r"(?P<year>\d+)-(?P<month>\d+)-(?P<day>\d+)T(?P<hour>\d+):(?P<min>\d+):(?P<sec>\d+)\.\d+\+\d+:\d+(?P<eol>.*)")
self.date_expr = None
self.line_expr = \
re.compile(r"\s+([-\w]+)\s+(\w+)/?\w*[[](\d+)[]]:\s+(.*)")
@@ -200,7 +203,7 @@ class LogParser(object):
self.lupdates[fname] = self.init_rrd(fname, m)
self._dprint("[rrd] create new RRD file %s" % fname)
else:
if not fname in self.lupdates:
if fname not in self.lupdates:
self.lupdates[fname] = rrdtool.last(str(fname))
if m <= self.lupdates[fname]:
@@ -250,11 +253,11 @@ class LogParser(object):
def inc_counter(self, dom, counter, val=1):
if dom is not None and dom in self.domains:
if not self.cur_t in self.data[dom]:
if self.cur_t not in self.data[dom]:
self.initcounters(dom)
self.data[dom][self.cur_t][counter] += val
if not self.cur_t in self.data["global"]:
if self.cur_t not in self.data["global"]:
self.initcounters("global")
self.data["global"][self.cur_t][counter] += val
@@ -299,7 +302,9 @@ class LogParser(object):
return
(line_id, line_log) = m.groups()
if line_id == "NOQUEUE":
addrto = re.match("reject: .*from=<.*> to=<[^@]+@([^>]+)>", line_log)
addrto = re.match(
"reject: .*from=<.*> to=<[^@]+@([^>]+)>",
line_log)
if addrto and addrto.group(1) in self.domains:
self.inc_counter(addrto.group(1), 'reject')
return
@@ -316,9 +321,9 @@ class LogParser(object):
m = re.search("to=<([^>]*)>.*status=(\S+)", line_log)
if m is not None:
if not line_id in self.workdict:
self._dprint("Inconsistent mail (%s: %s), skipping" \
% (line_id, m.group(1)))
if line_id not in self.workdict:
self._dprint("Inconsistent mail (%s: %s), skipping"
% (line_id, m.group(1)))
return
if not m.group(2) in variables:
self._dprint("Unsupported status %s, skipping" % m.group(2))
+6 -3
View File
@@ -1,4 +1,7 @@
# coding: utf-8
"""Custom views."""
import re
import time
from django.shortcuts import render
@@ -10,7 +13,7 @@ from django.contrib.auth.decorators import (
from modoboa.core.extensions import exts_pool
from modoboa.lib import events
from modoboa.lib.exceptions import BadRequest, PermDeniedException, NotFound
from modoboa.lib.webutils import (
from modoboa.lib.web_utils import (
render_to_json_response
)
from modoboa.extensions.admin.models import (
@@ -83,7 +86,7 @@ def check_domain_access(user, pattern):
def graphs(request):
gset = request.GET.get("gset", None)
gsets = events.raiseDictEvent("GetGraphSets")
if not gset in gsets:
if gset not in gsets:
raise NotFound(_("Unknown graphic set"))
searchq = request.GET.get("searchquery", None)
period = request.GET.get("period", "day")
@@ -94,7 +97,7 @@ def graphs(request):
tplvars["domain"] = domain
if period == "custom":
if not "start" in request.GET or not "end" in request.GET:
if "start" not in request.GET or "end" not in request.GET:
raise BadRequest(_("Bad custom period"))
start = request.GET["start"]
end = request.GET["end"]
+3 -2
View File
@@ -2,7 +2,7 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from modoboa.lib.parameters import AdminParametersForm, UserParametersForm
from modoboa.lib.formutils import SeparatorField, YesNoField, InlineRadioSelect
from modoboa.lib.form_utils import SeparatorField, YesNoField, InlineRadioSelect
class ParametersForm(AdminParametersForm):
@@ -13,7 +13,8 @@ class ParametersForm(AdminParametersForm):
max_attachment_size = forms.CharField(
label=_("Maximum attachment size"),
initial="2048",
help_text=_("Maximum attachment size in bytes (or KB, MB, GB if specified)")
help_text=_(
"Maximum attachment size in bytes (or KB, MB, GB if specified)")
)
sep1 = SeparatorField(label=_("IMAP settings"))
+2
View File
@@ -25,6 +25,7 @@ class WebmailInternalError(InternalError):
class UnknownAction(ModoboaException):
"""
Use this exception when the webmail encounter an unknown action.
"""
@@ -35,6 +36,7 @@ class UnknownAction(ModoboaException):
class ImapError(ModoboaException):
def __init__(self, reason):
self.reason = reason
+5 -4
View File
@@ -9,7 +9,7 @@ from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from modoboa.lib import parameters
from modoboa.lib.emailutils import set_email_headers
from modoboa.lib.email_utils import set_email_headers
from .lib import (
ImapEmail, create_mail_attachment
)
@@ -169,8 +169,8 @@ class ComposeMailForm(forms.Form):
class ForwardMailForm(ComposeMailForm):
"""Forward mail form.
"""
"""Forward mail form."""
def _build_msg(self, request):
"""Convert form's content to a MIME message.
@@ -203,7 +203,8 @@ class FolderForm(forms.Form):
widget=forms.HiddenInput(attrs={"class": "form-control"}),
required=False
)
name = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}))
name = forms.CharField(
widget=forms.TextInput(attrs={"class": "form-control"}))
class AttachmentForm(forms.Form):
@@ -4,7 +4,7 @@ from django.conf import settings
from django.core.files.uploadhandler import FileUploadHandler, SkipFile
from modoboa.lib import parameters
from modoboa.lib.exceptions import InternalError
from modoboa.lib.webutils import size2integer
from modoboa.lib.web_utils import size2integer
def set_compose_session(request):
@@ -80,18 +80,19 @@ def create_mail_attachment(attdef, payload=None):
res = MIMEBase(maintype, subtype)
if payload is None:
with open(os.path.join(
settings.MEDIA_ROOT, "webmail", attdef["tmpname"]), "rb") as fp:
settings.MEDIA_ROOT, "webmail", attdef["tmpname"]), "rb") as fp:
res.set_payload(fp.read())
else:
res.set_payload(payload)
Encoders.encode_base64(res)
if type(attdef['fname']) is str:
if isinstance(attdef['fname'], str):
attdef['fname'] = attdef['fname'].decode('utf-8')
res['Content-Disposition'] = build_header(attdef['fname'])
return res
class AttachmentUploadHandler(FileUploadHandler):
"""
Simple upload handler to limit the size of the attachments users
can upload.
+16 -8
View File
@@ -10,16 +10,19 @@ retrieve multiple attributes of a message.
This simple module tries to fix that *problem*.
"""
class ParseError(Exception):
pass
class Token(object):
def __init__(self, value):
self.value = value
class Literal(Token):
def __init__(self, value):
super(Literal, self).__init__(value)
self.next_token_len = int(self.value[1:-1])
@@ -50,7 +53,8 @@ def parse_next_token(buf):
end = pos - 1
break
if end == -1:
raise ParseError("End of buffer reached while looking for a token end")
raise ParseError(
"End of buffer reached while looking for a token end")
end += 1
token = klass(buf[:end])
return token, end
@@ -94,7 +98,7 @@ def parse_bodystructure(buf, depth=0, prefix=""):
# make a difference between a content-disposition with
# multiple args beeing parsed and a mime part! (see
# the last example at the end of the file)
if type(ret[0]) == list or len(ret) >= 7:
if isinstance(ret[0], list) or len(ret) >= 7:
partnum = prefix
if partnum is not None:
return [{"partnum": partnum, "struct": ret}], pos
@@ -111,7 +115,8 @@ def parse_bodystructure(buf, depth=0, prefix=""):
else:
ret.append(token.value.strip('"'))
raise ParseError("End of buffer reached while looking for a BODY/BODYSTRUCTURE end")
raise ParseError(
"End of buffer reached while looking for a BODY/BODYSTRUCTURE end")
def parse_fetch_response(data):
@@ -128,7 +133,7 @@ def parse_fetch_response(data):
while cpt < len(data):
content = ()
while cpt < len(data) and data[cpt] != ')':
if type(data[cpt]) == str:
if isinstance(data[cpt], str):
# FIXME : probably an unsolicited response
cpt += 1
continue
@@ -192,13 +197,13 @@ def parse_fetch_response(data):
def dump_bodystructure(bs, depth=0):
if depth:
print " " * (depth * 4),
if type(bs[0]) == dict:
if isinstance(bs[0], dict):
print "%s :" % bs[0]["partnum"],
struct = bs[0]["struct"]
else:
struct = bs
if type(struct[0]) == list:
if isinstance(struct[0], list):
print "multipart/%s" % struct[1]
for part in struct[0]:
dump_bodystructure(part, depth + 1)
@@ -209,9 +214,12 @@ def dump_bodystructure(bs, depth=0):
if __name__ == "__main__":
# resp = [('855 (UID 46931 BODYSTRUCTURE ((("text" "plain" ("charset" "iso-8859-1") NIL NIL "quoted-printable" 886 32 NIL NIL NIL NIL)("text" "html" ("charset" "us-ascii") NIL NIL "quoted-printable" 1208 16 NIL NIL NIL NIL) "alternative" ("boundary" "----=_NextPart_001_0003_01CCC564.B2F64FF0") NIL NIL NIL)("application" "octet-stream" ("name" "Carte Verte_2.pdf") NIL NIL "base64" 285610 NIL ("attachment" ("filename" "Carte Verte_2.pdf")) NIL NIL) "mixed" ("boundary" "----=_NextPart_000_0002_01CCC564.B2F64FF0") NIL NIL NIL) BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)] {153}', 'From: <Service.client10@maaf.fr>\r\nTo: <TONIO@NGYN.ORG>\r\nCc: \r\nSubject: Notre contact du 28/12/2011 - 192175092\r\nDate: Wed, 28 Dec 2011 13:29:17 +0100\r\n\r\n'), ')']
# #resp = [('856 (UID 46936 BODYSTRUCTURE (("text" "plain" ("charset" "ISO-8859-1") NIL NIL "quoted-printable" 724 22 NIL NIL NIL NIL)("text" "html" ("charset" "ISO-8859-1") NIL NIL "quoted-printable" 2662 48 NIL NIL NIL NIL) "alternative" ("boundary" "----=_Part_1326887_254624357.1325083973970") NIL NIL NIL) BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)] {258}', 'Date: Wed, 28 Dec 2011 15:52:53 +0100 (CET)\r\nFrom: =?ISO-8859-1?Q?Malakoff_M=E9d=E9ric?= <communication@communication.malakoffmederic.com>\r\nTo: Antoine Nguyen <tonio@ngyn.org>\r\nSubject: =?ISO-8859-1?Q?Votre_inscription_au_grand_Jeu_Malakoff_M=E9d=E9ric?=\r\n\r\n'), ')']
# resp = [('856 (UID 46936 BODYSTRUCTURE (("text" "plain" ("charset" "ISO-8859-1") NIL NIL "quoted-printable" 724 22 NIL NIL NIL NIL)("text" "html" ("charset" "ISO-8859-1") NIL NIL "quoted-printable" 2662 48 NIL NIL NIL NIL) "alternative" ("boundary" "----=_Part_1326887_254624357.1325083973970") NIL NIL NIL) BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)] {258}', 'Date: Wed, 28 Dec 2011 15:52:53 +0100 (CET)\r\nFrom: =?ISO-8859-1?Q?Malakoff_M=E9d=E9ric?= <communication@communication.malakoffmederic.com>\r\nTo: Antoine Nguyen <tonio@ngyn.org>\r\nSubject: =?ISO-8859-1?Q?Votre_inscription_au_grand_Jeu_Malakoff_M=E9d=E9ric?=\r\n\r\n'), ')']
resp = [('856 (UID 11111 BODYSTRUCTURE ((("text" "plain" ("charset" "UTF-8") NIL NIL "7bit" 0 0 NIL NIL NIL NIL) "mixed" ("boundary" "----=_Part_407172_3159001.1321948277321") NIL NIL NIL)("application" "octet-stream" ("name" "26274308.pdf") NIL NIL "base64" 14906 NIL ("attachment" ("filename" "26274308.pdf")) NIL NIL) "mixed" ("boundary" "----=_Part_407171_9686991.1321948277321") NIL NIL NIL)',), ')']
resp = [
('856 (UID 11111 BODYSTRUCTURE ((("text" "plain" ("charset" "UTF-8") NIL NIL "7bit" 0 0 NIL NIL NIL NIL) "mixed" ("boundary" "----=_Part_407172_3159001.1321948277321") NIL NIL NIL)("application" "octet-stream" ("name" "26274308.pdf") NIL NIL "base64" 14906 NIL ("attachment" ("filename" "26274308.pdf")) NIL NIL) "mixed" ("boundary" "----=_Part_407171_9686991.1321948277321") NIL NIL NIL)',
),
')']
# resp = [('19 (UID 19 FLAGS (\\Seen) BODYSTRUCTURE (("text" "plain" ("charset" "ISO-8859-1" "format" "flowed") NIL NIL "7bit" 2 1 NIL NIL NIL NIL)("message" "rfc822" ("name*" "ISO-8859-1\'\'%5B%49%4E%53%43%52%49%50%54%49%4F%4E%5D%20%52%E9%63%E9%70%74%69%6F%6E%20%64%65%20%76%6F%74%72%65%20%64%6F%73%73%69%65%72%20%64%27%69%6E%73%63%72%69%70%74%69%6F%6E%20%46%72%65%65%20%48%61%75%74%20%44%E9%62%69%74") NIL NIL "8bit" 3632 ("Wed, 13 Dec 2006 20:30:02 +0100" {70}',
# "[INSCRIPTION] R\xe9c\xe9ption de votre dossier d'inscription Free Haut D\xe9bit"),
+3 -3
View File
@@ -12,7 +12,7 @@ from django.conf import settings
from django.utils.translation import ugettext as _
from modoboa.lib import parameters, u2u_decode
from modoboa.lib.emailutils import Email, EmailAddress
from modoboa.lib.email_utils import Email, EmailAddress
from .imaputils import (
get_imapconnector, BodyStructure
)
@@ -82,7 +82,7 @@ class ImapEmail(Email):
)
self.bs = BodyStructure(self._msg['BODYSTRUCTURE'])
self._find_attachments()
if not self.dformat in ["plain", "html"]:
if self.dformat not in ["plain", "html"]:
self.dformat = parameters.get_user(
self.request.user, self.dformat
)
@@ -312,7 +312,7 @@ class ForwardModifier(Modifier):
return "----- %s -----" % _("Original message")
def _header_begin_html(self):
return "----- %s -----" % _("Original message")
return "----- %s -----" % _("Original message")
def _header_line_plain(self, key, value):
return "%s: %s\n" % (key, value)
+2 -3
View File
@@ -2,7 +2,7 @@
Set of functions used to parse and transform email headers.
"""
import chardet
from modoboa.lib.emailutils import EmailAddress
from modoboa.lib.email_utils import EmailAddress
__all__ = [
'parse_from', 'parse_to', 'parse_message_id', 'parse_date',
@@ -13,7 +13,7 @@ __all__ = [
def to_unicode(value):
"""Try to convert a string to unicode.
"""
if value is None or type(value) is unicode:
if value is None or isinstance(value, unicode):
return value
try:
value = value.decode('utf-8')
@@ -111,4 +111,3 @@ def parse_subject(value, **kwargs):
except UnicodeDecodeError:
subject = value
return to_unicode(subject)

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