Merge from bitbucket

This commit is contained in:
Antoine Nguyen
2013-02-08 20:35:44 +01:00
29 changed files with 558 additions and 279 deletions
+3
View File
@@ -7,3 +7,6 @@ modoboa.egg-info
doc/.build
doc/.static
doc/.templates
DEV
.idea
modoboa.iml
+6 -5
View File
@@ -1,16 +1,17 @@
# coding: utf-8
from django import forms
from django.utils.translation import ugettext_lazy as _
class LoginForm(forms.Form):
username = forms.CharField(
label=_("Username"),
widget=forms.TextInput(attrs={"class" : "span3"})
)
widget=forms.TextInput(attrs={"class": "span3"})
)
password = forms.CharField(
label=_("Password"),
widget=forms.PasswordInput(attrs={"class" : "span3"})
)
widget=forms.PasswordInput(attrs={"class": "span3"})
)
rememberme = forms.BooleanField(
initial=False,
required=False
)
)
-4
View File
@@ -1,8 +1,4 @@
# coding: utf-8
import crypt, hashlib
import string
from random import Random
from Crypto.Cipher import AES
import base64
from modoboa.lib import parameters
+1 -1
View File
@@ -1,3 +1,4 @@
# coding: utf-8
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
@@ -20,4 +21,3 @@ Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}
+7 -7
View File
@@ -4,31 +4,31 @@ from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from modoboa.lib import events
from modoboa.lib import events, parameters
from modoboa.lib.webutils import _render_to_string
from modoboa.lib.sysutils import log_warning
from modoboa.admin import views as admviews
from forms import LoginForm
from modoboa.auth.lib import *
from modoboa.auth.lib import encrypt
def dologin(request):
error = None
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
user = authenticate(username=form.cleaned_data["username"],
user = authenticate(username=form.cleaned_data["username"],
password=form.cleaned_data["password"])
if user and user.is_active:
if not form.cleaned_data["rememberme"]:
request.session.set_expiry(0)
login(request, user)
login(request, user)
if request.user.id != 1:
request.session["password"] = encrypt(request.POST["password"])
request.session["django_language"] = \
parameters.get_user(request.user, "LANG", app="general")
events.raiseEvent("UserLogin", request,
events.raiseEvent("UserLogin", request,
form.cleaned_data["username"],
form.cleaned_data["password"])
@@ -38,7 +38,7 @@ def dologin(request):
return HttpResponseRedirect(next)
error = _("Your username and password didn't match. Please try again.")
log_warning("Failed connection attempt from %(addr)s as user %(user)s" \
% {"addr" : request.META["REMOTE_ADDR"],
% {"addr" : request.META["REMOTE_ADDR"],
"user" : form.cleaned_data["username"]})
next = request.POST.get("next", None)
@@ -49,7 +49,7 @@ def dologin(request):
httpcode = 200
return HttpResponse(_render_to_string(request, "registration/login.html", {
"form" : form, "error" : error, "next" : next,
"form" : form, "error" : error, "next" : next,
"annoucements" : events.raiseQueryEvent("GetAnnouncement", "loginpage")
}), status=httpcode)
+7 -7
View File
@@ -1,3 +1,4 @@
# coding: utf-8
import sys
import os
import shutil
@@ -53,7 +54,7 @@ class DeployCommand(Command):
output = None
if p.returncode:
if output:
print >>sys.stderr, "\n".join(filter(lambda l: l != None, output))
print >>sys.stderr, "\n".join(filter(lambda l: l is not None, output))
print >>sys.stderr, "%s failed, check your configuration" % cmd
def ask_db_info(self, name='default'):
@@ -65,8 +66,7 @@ class DeployCommand(Command):
:param name: the connection name
"""
print "Configuring database connection: %s" % name
info = {'conn_name' : name}
info['dbtype'] = raw_input('Database type (mysql or postgres): ')
info = {'conn_name': name, 'dbtype': raw_input('Database type (mysql or postgres): ')}
if info['dbtype'] not in ['mysql', 'postgres']:
info['dbtype'] = 'mysql'
if info['dbtype'] == 'postgres':
@@ -78,7 +78,7 @@ class DeployCommand(Command):
info['username'] = raw_input('Username: ')
info['password'] = getpass.getpass('Password: ')
return info
def handle(self, parsed_args):
management.call_command('startproject', parsed_args.name, verbosity=False)
if os.path.exists("%(name)s/%(name)s" % {'name' : parsed_args.name}):
@@ -90,7 +90,7 @@ class DeployCommand(Command):
path = parsed_args.name
sys.path.append(".")
django14 = False
t = Template(dbconn_tpl)
default_conn = t.render(Context(self.ask_db_info()))
amavis_conn = t.render(Context(self.ask_db_info('amavis'))) if parsed_args.with_amavis \
@@ -107,10 +107,10 @@ class DeployCommand(Command):
fp.close()
shutil.copyfile("%s/urls.py" % self._templates_dir, "%s/urls.py" % path)
os.mkdir("%s/media" % path)
if parsed_args.syncdb:
self._exec_django_command("syncdb", parsed_args.name, '--migrate', '--noinput')
self._exec_django_command("loaddata", parsed_args.name, 'initial_users.json')
if parsed_args.collectstatic:
self._exec_django_command("collectstatic", parsed_args.name, '--noinput')
+2 -1
View File
@@ -1,3 +1,4 @@
# coding: utf-8
import sys
from modoboa.core.management import Command
@@ -8,7 +9,7 @@ class HelpCommand(Command):
super(HelpCommand, self).__init__(*args, **kwargs)
self._parser.add_argument('name', type=str,
help='A command name')
def handle(self, parsed_args):
if not self._commands.has_key(parsed_args.name):
print >>sys.stderr, "Unknown command: %s" % parsed_args.name
+6 -5
View File
@@ -1,3 +1,4 @@
# coding: utf-8
import getpass
import inspect
import os
@@ -84,14 +85,14 @@ class PostfixMapsCommand(Command):
help='Used database type')
self._parser.add_argument('--dbhost', type=str, default='127.0.0.1',
help='Database host address')
self._parser.add_argument('--categories', type=str, nargs='*', default=['std'],
self._parser.add_argument('--categories', type=str, nargs='*', default=['std'],
help='Map file categories to generate (choices: std, autoreply)')
def handle(self, parsed_args):
dbname = raw_input('Database name: ')
dbuser = raw_input('Username: ')
dbpass = getpass.getpass('Password: ')
try:
os.mkdir(parsed_args.destdir)
except OSError:
@@ -102,6 +103,6 @@ class PostfixMapsCommand(Command):
continue
if not v.category in parsed_args.categories:
continue
v().render(parsed_args.destdir, parsed_args.dbtype,
dbname=dbname, dbuser=dbuser, dbpassword=dbpass,
v().render(parsed_args.destdir, parsed_args.dbtype,
dbname=dbname, dbuser=dbuser, dbpassword=dbpass,
dbhost=parsed_args.dbhost)
+5 -4
View File
@@ -1,6 +1,7 @@
from django.conf.urls.defaults import *
# coding: utf-8
from django.conf.urls.defaults import patterns
urlpatterns = patterns('modoboa.demo.views',
(r'^sendvirus/$', 'send_virus'),
(r'^sendspam/$', 'send_spam'),
)
(r'^sendvirus/$', 'send_virus'),
(r'^sendspam/$', 'send_spam'),
)
+11 -11
View File
@@ -2,7 +2,6 @@
from django.utils.translation import ugettext as _, ugettext_lazy
from django.core.urlresolvers import reverse
from modoboa.lib import events, parameters
from modoboa.lib.webutils import static_url
from modoboa.extensions import ModoExtension, exts_pool
class Webmail(ModoExtension):
@@ -11,23 +10,23 @@ class Webmail(ModoExtension):
version = "1.0"
description = ugettext_lazy("Simple IMAP webmail")
needs_media = True
def load(self):
parameters.register_app(uparams_opts=dict(needs_mailbox=True))
parameters.register_admin("IMAP_SERVER", type="string",
parameters.register_admin("IMAP_SERVER", type="string",
deflt="127.0.0.1",
help=ugettext_lazy("Address of your IMAP server"))
parameters.register_admin("IMAP_SECURED", type="list_yesno",
parameters.register_admin("IMAP_SECURED", type="list_yesno",
deflt="no",
help=ugettext_lazy("Use a secured connection to access IMAP server"))
parameters.register_admin("IMAP_PORT", type="int", deflt="143",
help=ugettext_lazy("Listening port of your IMAP server"))
parameters.register_admin("SMTP_SERVER", type="string",
parameters.register_admin("SMTP_SERVER", type="string",
deflt="127.0.0.1",
help=ugettext_lazy("Address of your SMTP server"))
parameters.register_admin("SMTP_SECURED_MODE", type="list",
values=[("none", ugettext_lazy("None")),
parameters.register_admin("SMTP_SECURED_MODE", type="list",
values=[("none", ugettext_lazy("None")),
("starttls", "STARTTLS"),
("ssl", "SSL/TLS")],
deflt="none",
@@ -39,7 +38,7 @@ class Webmail(ModoExtension):
help=ugettext_lazy("Server needs authentication"))
parameters.register_admin("MAX_ATTACHMENT_SIZE", type="int", deflt=2048,
help=ugettext_lazy("Maximum attachment size in bytes (or KB, MB, GB if specified)"))
parameters.register_user("MESSAGES_PER_PAGE", type="int", deflt=40,
label=ugettext_lazy("Number of displayed emails per page"),
help=ugettext_lazy("Sets the maximum number of messages displayed in a page"))
@@ -99,9 +98,10 @@ def userlogout(request):
try:
m = IMAPconnector(user=request.user.username,
password=request.session["password"])
except Exception, e:
except Exception:
# TODO silent exception are bad : we should at least log it
return
# The following statement may fail under Python 2.6...
try:
m.logout()
+3 -3
View File
@@ -12,11 +12,11 @@ class WebmailError(ModoboaException):
errorexpr = re.compile("\[([^\]]+)\]\s*([^\.]+)")
def __init__(self, reason, ajax=False):
m = WebmailError.errorexpr.match(reason)
if m is None:
match = WebmailError.errorexpr.match(reason)
if not match:
self.reason = reason
else:
self.reason = "%s: %s" % (_("Server response"), m.group(2))
self.reason = "%s: %s" % (_("Server response"), match.group(2))
self.ajax = ajax
def __str__(self):
+3 -4
View File
@@ -61,8 +61,7 @@ def parse_bodystructure(buf, depth=0, prefix=""):
if nb_bodystruct == 0:
ret.append(subret)
elif nb_bodystruct == 1:
newret = [ret.pop(-1)]
newret.append(subret)
newret = [ret.pop(-1), subret]
ret.append(newret)
else:
ret[-1].append(subret)
@@ -97,7 +96,7 @@ def parse_fetch_response(data):
We extract the message number, the UID and its value and consider
what remains as the response data.
:param data: the data returned by the ``imaplib`` command
:return: a dictionnary
"""
@@ -182,7 +181,7 @@ def dump_bodystructure(bs, depth=0):
dump_bodystructure(part, depth + 1)
else:
print "%s/%s" % (struct[0], struct[1])
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'), ')']
+2 -1
View File
@@ -1,3 +1,4 @@
# coding: utf-8
from django import forms
from django.utils.translation import ugettext_lazy as _
@@ -9,7 +10,7 @@ class ComposeMailForm(forms.Form):
subject = forms.CharField(label=_("Subject"), max_length=255, required=False)
origmsgid = forms.CharField(label="", widget=forms.HiddenInput(), required=False)
class FolderForm(forms.Form):
oldname = forms.CharField(label="", widget=forms.HiddenInput(), required=False)
name = forms.CharField()
+33 -33
View File
@@ -9,10 +9,10 @@ import time
from functools import wraps
from django.utils.translation import ugettext as _
from modoboa.lib import parameters
from modoboa.lib.connections import *
from modoboa.lib.connections import ConnectionsManager
from modoboa.lib.webutils import static_url
from exceptions import ImapError, WebmailError
from fetch_parser import *
from fetch_parser import parse_fetch_response
#imaplib.Debug = 4
@@ -75,12 +75,12 @@ class BodyStructure(object):
* Any other MIME type is considered as an attachment (for now)
:param definition: a part definition (list)
:param prefix: the part's number
:param pnum: the part's number
:param multisubtype: the multipart subtype
"""
pnum = "1" if pnum is None else pnum
params = dict(pnum=pnum, params=definition[2], cid=definition[3],
description=definition[4], encoding=definition[5],
description=definition[4], encoding=definition[5],
size=definition[6])
mtype = definition[0].lower()
subtype = definition[1].lower()
@@ -122,7 +122,7 @@ class BodyStructure(object):
for part in struct[0]:
self.load_from_definition(part, struct[1])
return
self.__store_part(struct, pnum, multisubtype)
def has_attachments(self):
@@ -214,7 +214,7 @@ class IMAPconnector(object):
cpt += 1
else:
if bodystruct[0].lower() == mtype and bodystruct[1].lower() == stype:
return ("1" if not len(prefix) else prefix,
return ("1" if not len(prefix) else prefix,
bodystruct[5], int(bodystruct[6]))
return (None, None, 0)
@@ -230,12 +230,12 @@ class IMAPconnector(object):
if hasattr(self, "current_mailbox"):
del self.current_mailbox
else:
return
return
self.login(user, password)
def login(self, user, passwd):
"""Custom login method
We connect to the server, issue a LOGIN command. If
successfull, we try to record a eventuel CAPABILITY untagged
response. Otherwise, we issue the command.
@@ -305,9 +305,9 @@ class IMAPconnector(object):
"""Issue a SELECT/EXAMINE command to the server
The given name is first 'imap-utf7' encoded.
:param name: mailbox's name
:param readonly:
:param readonly:
"""
if hasattr(self, "current_mailbox"):
if self.current_mailbox == name and not force:
@@ -348,35 +348,35 @@ class IMAPconnector(object):
break
if sdescr is None:
sdescr = {"name" : parts[0], "path" : path, "sub" : []}
descr += [sdescr]
descr += [sdescr]
if self._parse_mailbox_name(sdescr["sub"], path, delimiter, parts[1:]):
sdescr["class"] = "subfolders"
return True
def _listmboxes_simple(self, topmailbox='INBOX', mailboxes=[], **kwargs):
def _listmboxes_simple(self, topmailbox='INBOX', mailboxes=None, **kwargs):
#data = self._cmd("LIST", "", "*")
if not mailboxes: mailboxes = []
(status, data) = self.m.list()
result = []
newmboxes = []
for mb in data:
flags, delimiter, name = self.list_response_pattern.match(mb).groups()
name = name.strip('"').decode("imap4-utf-7")
pos = -1
mdm_found = False
for idx, mdm in enumerate(mailboxes):
if mdm["name"] == name:
pos = idx
mdm_found = True
descr = mailboxes[idx]
break
if pos == -1:
if not mdm_found:
descr = dict(name=name)
newmboxes += [descr]
else:
descr = mailboxes[idx]
if re.search("\%s" % delimiter, name):
parts = name.split(".")
if not descr.has_key("path"):
descr["path"] = parts[0]
descr["sub"] = []
if self._parse_mailbox_name(descr["sub"], parts[0], delimiter,
if self._parse_mailbox_name(descr["sub"], parts[0], delimiter,
parts[1:]):
descr["class"] = "subfolders"
continue
@@ -385,7 +385,8 @@ class IMAPconnector(object):
mailboxes += sorted(newmboxes, key=itemgetter("name"))
@capability('LIST-EXTENDED', '_listmboxes_simple')
def _listmboxes(self, topmailbox='', mailboxes=[], until_mailbox=None):
def _listmboxes(self, topmailbox='', mailboxes=None, until_mailbox=None):
if not mailboxes: mailboxes = []
pattern = ("%s.%%" % topmailbox.encode("imap4-utf-7")) if len(topmailbox) else "%"
resp = self._cmd("LIST", "", pattern, "RETURN", "(CHILDREN)")
newmboxes = []
@@ -394,17 +395,16 @@ class IMAPconnector(object):
self.listextended_response_pattern.match(mb).groups()
flags = flags.split(' ')
name = name.decode("imap4-utf-7")
pos = -1
mdm_found = False
for idx, mdm in enumerate(mailboxes):
if mdm["name"] == name:
pos = idx
mdm_found = True
descr = mailboxes[idx]
break
if pos == -1:
if not mdm_found:
descr = dict(name=name)
newmboxes += [descr]
else:
descr = mailboxes[idx]
if r'\Marked' in flags or not r'\UnMarked' in flags:
descr["send_status"] = True
if r'\HasChildren' in flags:
@@ -433,7 +433,7 @@ class IMAPconnector(object):
md_mailboxes = []
else:
md_mailboxes = [{"name" : "INBOX", "class" : "icon-inbox"},
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
{"name" : parameters.get_user(user, "DRAFTS_FOLDER"),
"class" : "icon-file"},
{"name" : 'Junk', "class" : "icon-fire"},
{"name" : parameters.get_user(user, "SENT_FOLDER"),
@@ -515,7 +515,7 @@ class IMAPconnector(object):
"""Compact a specific mailbox
Issue an EXPUNGE command for the specified mailbox.
:param mbox: the mailbox's name
"""
self.select_mailbox(mbox, False)
@@ -547,12 +547,12 @@ class IMAPconnector(object):
self.quota_limit = self.quota_actual = None
return
data = self._cmd("GETQUOTAROOT", self._encode_mbox_name(mailbox),
data = self._cmd("GETQUOTAROOT", self._encode_mbox_name(mailbox),
responses=["QUOTAROOT", "QUOTA"])
if data is None:
self.quota_limit = self.quota_actual = None
return
quotadef = data[1][0]
m = re.match("[^\s]+ \(STORAGE (\d+) (\d+)\)", quotadef)
if not m:
@@ -628,14 +628,14 @@ class IMAPconnector(object):
:param mbox: the mailbox containing the message
:param mailid: the message's unique id
:param readonly:
:param extraheaders:
:param headers:
"""
self.select_mailbox(mbox, readonly)
if headers is None:
headers = ['DATE', 'FROM', 'TO', 'CC', 'SUBJECT']
bcmd = "BODY.PEEK" if readonly else "BODY"
data = self._cmd(
"FETCH", mailid,
"FETCH", mailid,
"(BODYSTRUCTURE %s[HEADER.FIELDS (%s)])" % (bcmd, " ".join(headers))
)
return data[int(mailid)]
@@ -658,7 +658,7 @@ def get_imapconnector(request):
:param request: a ``Request`` object
"""
imapc = IMAPconnector(user=request.user.username,
imapc = IMAPconnector(user=request.user.username,
password=request.session["password"])
return imapc
+29 -30
View File
@@ -1,31 +1,30 @@
# coding: utf-8
import sys, os
import re
import time
from datetime import datetime, timedelta
import email
import lxml
import chardet
from django.core.files.uploadhandler import FileUploadHandler, StopUpload, SkipFile
from django.http import HttpResponse
from django.core.files.uploadhandler import FileUploadHandler, SkipFile
from django.utils.translation import ugettext as _, ugettext_lazy
from django.conf import settings
from modoboa.lib import u2u_decode, tables, imap_utf7, parameters
from modoboa.lib.webutils import static_url, size2integer
from modoboa.lib.connections import ConnectionsManager
from modoboa.lib.email_listing import MBconnector, EmailListing
from modoboa.lib.emailutils import *
from imaputils import *
from exceptions import *
import os
import re
from modoboa.lib import u2u_decode, tables, parameters
from modoboa.lib.webutils import size2integer
from modoboa.lib.email_listing import EmailListing
from modoboa.extensions.webmail.exceptions import WebmailError
from modoboa.extensions.webmail.imaputils import IMAPconnector, get_imapconnector, BodyStructure
from modoboa.lib.emailutils import EmailAddress, Email
class WMtable(tables.Table):
tableid = "emails"
styles = "table-condensed"
idkey = "imapid"
select = tables.ImgColumn(
"select", cssclass="draggable left", width="2%",
"select", cssclass="draggable left", width="2%",
defvalue="%spics/grippy.png" % settings.STATIC_URL,
header="<input type='checkbox' name='toggleselect' id='toggleselect' />"
)
@@ -112,7 +111,7 @@ class ImapListing(EmailListing):
deflocation = "INBOX/"
defcallback = "wm_updatelisting"
reset_wm_url = False
def __init__(self, user, password, **kwargs):
self.user = user
self.mbc = IMAPconnector(user=user.username, password=password)
@@ -165,7 +164,7 @@ class ImapEmail(Email):
('Subject', True),
]
def __init__(self, mbox, mailid, request, dformat="DISPLAYMODE", addrfull=False,
def __init__(self, mbox, mailid, request, dformat="DISPLAYMODE", addrfull=False,
links=0):
mformat = parameters.get_user(request.user, "DISPLAYMODE")
self.dformat = parameters.get_user(request.user, dformat)
@@ -179,7 +178,7 @@ class ImapEmail(Email):
self.mailid = mailid
headers = msg['BODY[HEADER.FIELDS (%s)]' % self.headers_as_text]
fallback_fmt = "html" if self.dformat == "plain" else "plain"
self.bs = BodyStructure(msg['BODYSTRUCTURE'])
data = None
@@ -206,7 +205,7 @@ class ImapEmail(Email):
getattr(self, "viewmail_%s" % mformat)(bodyc, links=links)
else:
self.body = None
self._find_attachments()
msg = email.message_from_string(headers)
@@ -257,7 +256,7 @@ class ImapEmail(Email):
att.has_key("disposition") and len(att["disposition"]) > 1:
params = att["disposition"][1]
key = "filename"
if key and params:
for pos, value in enumerate(params):
if value == key:
@@ -311,7 +310,7 @@ class Modifier(ImapEmail):
def _modify_html(self):
pass
class ReplyModifier(Modifier):
headernames = ImapEmail.headernames + \
[("Reply-To", True),
@@ -358,13 +357,13 @@ class ReplyModifier(Modifier):
body += "\n"
body += ">%s" % l
self.body = body
class ForwardModifier(Modifier):
def __init__(self, mbox, mailid, request, form, **kwargs):
super(ForwardModifier, self).__init__(
mbox, mailid, request, dformat="EDITOR", **kwargs
)
self._header()
form.fields["from_"].initial = request.user.username
form.fields["subject"].initial = "Fwd: %s" % self.Subject
@@ -488,7 +487,7 @@ def find_images_in_body(body):
% (ct, os.path.basename(fname)))
p["Content-Disposition"] = "inline"
parts.append(p)
return lxml.html.tostring(html), parts
def set_compose_session(request):
@@ -557,7 +556,7 @@ def html2plaintext(content):
if p is None:
continue
plaintext += p + "\n"
return plaintext
def get_current_url(request):
@@ -603,7 +602,7 @@ def send_mail(request, posturl=None):
"""
from email.mime.multipart import MIMEMultipart
from forms import ComposeMailForm
from modoboa.lib.webutils import getctx, ajax_simple_response, _render_to_string
from modoboa.lib.webutils import _render_to_string
from modoboa.auth.lib import get_password
form = ComposeMailForm(request.POST)
@@ -623,13 +622,13 @@ def send_mail(request, posturl=None):
submsg.attach(MIMEText(textbody.encode(charset),
_subtype="plain", _charset=charset))
body, images = find_images_in_body(body)
submsg.attach(MIMEText(body.encode(charset), _subtype=editormode,
submsg.attach(MIMEText(body.encode(charset), _subtype=editormode,
_charset=charset))
msg.attach(submsg)
for img in images:
msg.attach(img)
else:
text = MIMEText(body.encode(charset),
text = MIMEText(body.encode(charset),
_subtype=editormode, _charset=charset)
if len(request.session["compose_mail"]["attachments"]):
msg = MIMEMultipart()
@@ -686,7 +685,7 @@ def send_mail(request, posturl=None):
del request.session["compose_mail"]
return True, dict(url=get_current_url(request))
listing = _render_to_string(request, "webmail/compose.html",
listing = _render_to_string(request, "webmail/compose.html",
{"form" : form, "noerrors" : True,
"body" : request.POST["id_body"].strip(),
"posturl" : posturl})
@@ -698,19 +697,19 @@ class AttachmentUploadHandler(FileUploadHandler):
Simple upload handler to limit the size of the attachments users
can upload.
"""
def __init__(self, request=None):
super(AttachmentUploadHandler, self).__init__(request)
self.total_upload = 0
self.toobig = False
self.maxsize = size2integer(parameters.get_admin("MAX_ATTACHMENT_SIZE"))
def receive_data_chunk(self, raw_data, start):
self.total_upload += len(raw_data)
if self.total_upload >= self.maxsize:
self.toobig = True
raise SkipFile()
return raw_data
def file_complete(self, file_size):
return None
+2 -3
View File
@@ -1,7 +1,6 @@
# coding: utf-8
from modoboa.lib.webutils import ajax_response, _render_error
from modoboa.extensions.webmail.exceptions import *
from modoboa.extensions.webmail.exceptions import WebmailError, ImapError
class WebmailErrorMiddleware(object):
def process_exception(self, request, exception):
@@ -10,6 +9,6 @@ class WebmailErrorMiddleware(object):
json = request.GET.get("json", False) if request.method == "GET" \
else request.POST.get("json", False)
if json:
return ajax_response(request, status="ko",
return ajax_response(request, status="ko",
respmsg=str(exception), norefresh=True)
return _render_error(request, user_context=dict(error=str(exception)))
@@ -47,68 +47,42 @@ Webmail.prototype = {
},
register_navcallbacks: function() {
this.navobject.register_callback("listmailbox",
$.proxy(this.listmailbox_callback, this));
this.navobject.register_callback("compose",
$.proxy(this.compose_callback, this));
this.navobject.register_callback("viewmail",
$.proxy(this.viewmail_callback, this));
this.navobject.register_callback("listmailbox", $.proxy(this.listmailbox_callback, this));
this.navobject.register_callback("compose", $.proxy(this.compose_callback, this));
this.navobject.register_callback("viewmail", $.proxy(this.viewmail_callback, this));
this.navobject.register_callback("reply",
$.proxy(this.compose_callback, this));
this.navobject.register_callback("replyall",
$.proxy(this.compose_callback, this));
this.navobject.register_callback("forward",
$.proxy(this.compose_callback, this));
this.navobject.register_callback("delete",
$.proxy(this.delete_callback, this));
this.navobject.register_callback("reply", $.proxy(this.compose_callback, this));
this.navobject.register_callback("replyall", $.proxy(this.compose_callback, this));
this.navobject.register_callback("forward", $.proxy(this.compose_callback, this));
this.navobject.register_callback("delete", $.proxy(this.delete_callback, this));
},
listen: function() {
$(window).resize(this.resize);
$(document).on("click", "a[name=compose]",
$.proxy(this.compose_loader, this));
$(document).on("click", "a[name*=mark-]",
$.proxy(this.send_mark_request, this));
$(document).on("click", "a[name=compress]",
$.proxy(this.compress, this));
$(document).on("click", "a[name=empty]",
$.proxy(this.empty, this));
$(document).on("click", "#bottom-bar a",
$.proxy(this.getpage_loader, this));
$(document).on("click", "a[name=compose]", $.proxy(this.compose_loader, this));
$(document).on("click", "a[name*=mark-]", $.proxy(this.send_mark_request, this));
$(document).on("click", "a[name=compress]", $.proxy(this.compress, this));
$(document).on("click", "a[name=empty]", $.proxy(this.empty, this));
$(document).on("click", "#bottom-bar a", $.proxy(this.getpage_loader, this));
$(document).on("click", "a[name=loadfolder]",
$.proxy(this.listmailbox_loader, this));
$(document).on("click", "a[name=selectfolder]",
this.select_parent_mailbox),
$(document).on("click", "div[class*=clickbox]",
$.proxy(this.mbox_state_callback, this));
$(document).on("click", "a[name=newmbox]",
$.proxy(this.new_mailbox, this));
$(document).on("click", "a[name=editmbox]",
$.proxy(this.edit_mbox, this));
$(document).on("click", "a[name=removembox]",
$.proxy(this.remove_mbox, this));
$(document).on("click", "a[name=loadfolder]", $.proxy(this.listmailbox_loader, this));
$(document).on("click", "a[name=selectfolder]", this.select_parent_mailbox);
$(document).on("click", "div[class*=clickbox]", $.proxy(this.mbox_state_callback, this));
$(document).on("click", "a[name=newmbox]", $.proxy(this.new_mailbox, this));
$(document).on("click", "a[name=editmbox]", $.proxy(this.edit_mbox, this));
$(document).on("click", "a[name=removembox]", $.proxy(this.remove_mbox, this));
$(document).on("dblclick", "tbody>tr",
$.proxy(this.viewmail_loader, this));
$(document).on("click", "tbody>tr", $.proxy(this.viewmail_loader, this));
$(document).on("click", "a[name=reply]",
$.proxy(this.reply_loader, this));
$(document).on("click", "a[name=replyall]",
$.proxy(this.reply_loader, this));
$(document).on("click", "a[name=forward]",
$.proxy(this.reply_loader, this));
$(document).on("click", "a[name=delete]",
$.proxy(this.delete_message, this));
$(document).on("click", "a[name=activate_links]",
$.proxy(function(e) { this.display_mode(e, "1"); }, this));
$(document).on("click", "a[name=disable_links]",
$.proxy(function(e) { this.disable_mode(e, "0"); }, this));
$(document).on("click", "a[name=reply]", $.proxy(this.reply_loader, this));
$(document).on("click", "a[name=replyall]", $.proxy(this.reply_loader, this));
$(document).on("click", "a[name=forward]", $.proxy(this.reply_loader, this));
$(document).on("click", "a[name=delete]", $.proxy(this.delete_message, this));
$(document).on("click", "a[name=activate_links]", $.proxy(function(e) { this.display_mode(e, "1"); }, this));
$(document).on("click", "a[name=disable_links]", $.proxy(function(e) { this.disable_mode(e, "0"); }, this));
$(document).on("click", "a[name=sendmail]",
$.proxy(this.sendmail, this));
$(document).on("click", "a[name=sendmail]", $.proxy(this.sendmail, this));
$(document).on("click", "#attachments", $.proxy(function(e) {
modalbox(e, undefined, $("#attachments").attr("name"),
@@ -5,15 +5,13 @@ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.conf import settings
from modoboa.extensions import webmail
from modoboa.extensions.webmail.lib import IMAPheader
from modoboa.extensions.webmail.imaputils import separate_mailbox
from modoboa.lib import parameters
from modoboa.lib.webutils import static_url
register = template.Library()
@register.simple_tag
def viewmail_menu(selection, folder, user, mail_id=None):
def viewmail_menu(selection, folder, user, mail_id=None):
entries = [
{"name" : "reply",
"url" : "action=reply&mbox=%s&mailid=%s" % (folder, mail_id),
@@ -34,21 +32,21 @@ def viewmail_menu(selection, folder, user, mail_id=None):
{"name" : "display_options",
"label" : _("Display options"),
"menu" : [
{"name" : "activate_links",
{"name" : "activate_links",
"label" : _("Activate links")},
{"name" : "disable_links",
{"name" : "disable_links",
"label" : _("Disable links")}
]
}
]
menu = render_to_string('common/buttons_list.html',
{"selection" : selection, "entries" : entries,
menu = render_to_string('common/buttons_list.html',
{"selection" : selection, "entries" : entries,
"user" : user, "extraclasses" : "pull-left"})
entries = [{"name" : "close",
"title" : _("Close this message"),
"img" : "icon-remove"}]
menu += render_to_string('common/buttons_list.html',
menu += render_to_string('common/buttons_list.html',
{"entries" : entries, "extraclasses" : "pull-right"})
return menu
@@ -65,8 +63,8 @@ def compose_menu(selection, backurl, user):
"img" : "icon-envelope",
"label" : _("Send")},
]
return render_to_string('common/buttons_list.html',
{"selection" : selection, "entries" : entries,
return render_to_string('common/buttons_list.html',
{"selection" : selection, "entries" : entries,
"user" : user})
@register.simple_tag
@@ -136,7 +134,7 @@ def print_mailboxes(tree, selected=None, withunseen=False, selectonly=False):
ul_state = "hidden"
div_state = "collapsed"
result += "<div class='clickbox %s'></div>" % div_state
cssclass = "block"
extra_attrs = ""
if withunseen and mbox.has_key("unseen"):
@@ -148,7 +146,7 @@ def print_mailboxes(tree, selected=None, withunseen=False, selectonly=False):
<i class="%s"></i>
%s
</a>
""" % (mbox.has_key("path") and mbox["path"] or mbox["name"], cssclass,
""" % (mbox.has_key("path") and mbox["path"] or mbox["name"], cssclass,
'selectfolder' if selectonly else 'loadfolder', extra_attrs, iclass, label)
if mbox.has_key("sub") and len(mbox["sub"]):
result += "<ul name='%s' class='nav nav-list %s'>" % (mbox["path"], ul_state) \
+5 -4
View File
@@ -1,4 +1,5 @@
from django.conf.urls.defaults import *
# coding: utf-8
from django.conf.urls.defaults import patterns
urlpatterns = patterns(
'modoboa.extensions.webmail.views',
@@ -6,7 +7,7 @@ urlpatterns = patterns(
(r'^submailboxes', "submailboxes"),
(r'^getmailcontent', 'getmailcontent'),
(r'^unseenmsgs', 'check_unseen_messages'),
(r'^delete/$', 'delete'),
(r'^move/$', "move"),
(r'^mark/(?P<name>.+)/$', "mark"),
@@ -15,8 +16,8 @@ urlpatterns = patterns(
(r'^newfolder/$', "newfolder"),
(r'^editfolder/$', 'editfolder'),
(r'^delfolder/$', 'delfolder'),
(r'^attachments/$', 'attachments'),
(r'^delattachment/$', 'delattachment'),
(r'^getattachment/$', 'getattachment'),
)
)
+35 -35
View File
@@ -1,36 +1,36 @@
# coding: utf-8
from django.http import HttpResponse, Http404
from django.conf import settings
import os
from django.http import HttpResponse
from django.template import Template, Context
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from modoboa.lib import parameters
from modoboa.lib.webutils import _render, _render_error, \
getctx, _render_to_string, ajax_response, ajax_simple_response
from modoboa.lib.email_listing import parse_search_parameters
from modoboa.lib.webutils import _render, _render_to_string, ajax_response, ajax_simple_response
from modoboa.lib.decorators import needs_mailbox
from modoboa.auth.lib import *
from lib import *
from forms import *
from exceptions import WebmailError
from forms import FolderForm, AttachmentForm, ComposeMailForm
from imaputils import get_imapconnector, IMAPconnector, separate_mailbox
from lib import decode_payload, AttachmentUploadHandler, save_attachment, ImapListing, EmailSignature, clean_attachments, set_compose_session, send_mail, ImapEmail
from templatetags import webextras
@login_required
@needs_mailbox()
def getattachment(request):
"""Fetch a message attachment
FIXME: par manque de caching, le bodystructure du message est
redemandé pour accéder aux headers de cette pièce jointe.
:param request: a ``Request`` object
:param folder
"""
mbox = request.GET.get("mbox", None)
mailid = request.GET.get("mailid", None)
pnum = request.GET.get("partnumber", None)
if not mbox or not mailid or not pnum:
raise WebmailError(_("Invalid request"))
headers = {"Content-Type" : "text/plain",
"Content-Transfer-Encoding" : None}
imapc = get_imapconnector(request)
@@ -51,7 +51,7 @@ def getattachment(request):
resp["Content-Disposition"] = cd
resp["Content-Length"] = partdef["size"]
return resp
@login_required
@needs_mailbox()
def move(request):
@@ -115,17 +115,17 @@ def compact(request, name):
@login_required
@needs_mailbox()
def newfolder(request, tplname="webmail/folder.html"):
mbc = IMAPconnector(user=request.user.username,
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
ctx = {"title" : _("Create a new mailbox"),
"formid" : "mboxform",
"action" : reverse(newfolder),
"action_label" : _("Create"),
"action_classes" : "submit",
"withunseen" : False,
"selectonly" : True}
ctx = {"title": _("Create a new mailbox"),
"formid": "mboxform",
"action": reverse(newfolder),
"action_label": _("Create"),
"action_classes": "submit",
"withunseen": False,
"selectonly": True,
"mboxes": mbc.getmboxes(request.user)}
ctx["mboxes"] = mbc.getmboxes(request.user)
if request.method == "POST":
form = FolderForm(request.POST)
if form.is_valid():
@@ -134,11 +134,11 @@ def newfolder(request, tplname="webmail/folder.html"):
return ajax_simple_response(dict(
status="ok", respmsg=_("Mailbox created"), newmb=form.cleaned_data["name"], parent=pf
))
ctx["form"] = form
ctx["selected"] = None
return ajax_response(request, status="ko", template=tplname, **ctx)
ctx["form"] = FolderForm()
ctx["selected"] = None
return _render(request, tplname, ctx)
@@ -146,7 +146,7 @@ def newfolder(request, tplname="webmail/folder.html"):
@login_required
@needs_mailbox()
def editfolder(request, tplname="webmail/folder.html"):
mbc = IMAPconnector(user=request.user.username,
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
ctx = {"title" : _("Edit mailbox"),
"formid" : "mboxform",
@@ -197,7 +197,7 @@ def delfolder(request):
name = request.GET.get("name", None)
if name is None:
raise WebmailError(_("Bad request"))
mbc = IMAPconnector(user=request.user.username,
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
mbc.delete_folder(name)
if request.session.has_key("mbox"):
@@ -217,7 +217,7 @@ def attachments(request, tplname="webmail/attachments.html"):
fobj = request.FILES["attachment"]
tmpname = save_attachment(fobj)
request.session["compose_mail"]["attachments"] \
+= [{"fname" : str(fobj),
+= [{"fname" : str(fobj),
"content-type" : fobj.content_type,
"size" : fobj.size,
"tmpname" : os.path.basename(tmpname)}]
@@ -225,7 +225,7 @@ def attachments(request, tplname="webmail/attachments.html"):
return _render(request, "webmail/upload_done.html", {
"status" : "ok", "fname" : request.FILES["attachment"],
"tmpname" : os.path.basename(tmpname)
});
})
except WebmailError, inst:
error = _("Failed to save attachment: ") + str(inst)
@@ -234,7 +234,7 @@ def attachments(request, tplname="webmail/attachments.html"):
% parameters.get_admin("MAX_ATTACHMENT_SIZE"))
return _render(request, "webmail/upload_done.html", {
"status" : "ko", "error" : error
});
})
ctx = {
"title" : _("Attachments"),
"formid" : "uploadfile",
@@ -274,7 +274,7 @@ def render_mboxes_list(request, imapc):
"""Return the HTML representation of a mailboxes list
:param request: a ``Request`` object
:param imap: an ``IMAPconnector` object
:param imapc: an ``IMAPconnector` object
:return: a string
"""
curmbox = request.session.get("mbox", "INBOX")
@@ -287,7 +287,7 @@ def render_mboxes_list(request, imapc):
def set_nav_params(request):
if not request.session.has_key("navparams"):
request.session["navparams"] = {}
request.session["pageid"] = \
int(request.GET.get("page", 1))
if not request.GET.get("order", False):
@@ -320,7 +320,7 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
lst = ImapListing(request.user, request.session["password"],
baseurl="?action=listmailbox&mbox=%s&" % mbox,
folder=mbox,
folder=mbox,
elems_per_page=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE")),
**request.session["navparams"])
return lst.render(request, request.session["pageid"])
@@ -348,7 +348,7 @@ def render_compose(request, form, posturl, email=None, insert_signature=False):
attachments = request.session["compose_mail"]["attachments"]
if len(attachments):
short_att_list = "(%s)" \
% ", ".join(map(lambda att: att["fname"],
% ", ".join(map(lambda att: att["fname"],
attachments[:2] + [{"fname" : "..."}] \
if len(attachments) > 2 else attachments))
else:
@@ -412,7 +412,7 @@ def getmailcontent(request):
raise WebmailError(_("Invalid request"))
email = ImapEmail(mbox, mailid, request, links=int(request.GET["links"]))
return _render(request, "common/viewmail.html", {
"headers" : email.render_headers(folder=mbox, mail_id=mailid),
"headers" : email.render_headers(folder=mbox, mail_id=mailid),
"folder" : mbox, "imapid" : mailid, "mailbody" : email.body if email.body else ""
})
@@ -484,7 +484,7 @@ def index(request):
if action is not None:
if not globals().has_key(action):
raise WebmailError(_("Undefined action"))
response = globals()[action](request)
response = globals()[action](request)
else:
if request.is_ajax():
raise WebmailError(_("Bad request"))
@@ -499,11 +499,11 @@ def index(request):
response["refreshrate"] = \
int(parameters.get_user(request.user, "REFRESH_INTERVAL"))
response["quota"] = ImapListing.computequota(imapc)
response["ro_mboxes"] = ["INBOX", "Junk",
response["ro_mboxes"] = ["INBOX", "Junk",
parameters.get_user(request.user, "SENT_FOLDER"),
parameters.get_user(request.user, "TRASH_FOLDER"),
parameters.get_user(request.user, "DRAFTS_FOLDER")]
return _render(request, "webmail/index.html", response)
if action in ["reply", "forward"]:
+290 -1
View File
@@ -1 +1,290 @@
[{"pk": 1, "model": "lib.parameter", "fields": {"name": "admin.STORAGE_PATH", "value": "/srv/vmail"}}, {"pk": 2, "model": "lib.parameter", "fields": {"name": "admin.PASSWORD_SCHEME", "value": "crypt"}}, {"pk": 3, "model": "lib.parameter", "fields": {"name": "admin.ITEMS_PER_PAGE", "value": "30"}}, {"pk": 4, "model": "lib.parameter", "fields": {"name": "general.DEFAULT_TOP_REDIRECTION", "value": "userprefs"}}, {"pk": 5, "model": "lib.parameter", "fields": {"name": "admin.CREATE_DIRECTORIES", "value": "yes"}}, {"pk": 6, "model": "lib.parameter", "fields": {"name": "auth.SECRET_KEY", "value": "abcdefghijklmnop"}}, {"pk": 7, "model": "lib.parameter", "fields": {"name": "admin.MAILBOX_TYPE", "value": "maildir"}}, {"pk": 8, "model": "lib.parameter", "fields": {"name": "admin.AUTHENTICATION_TYPE", "value": "local"}}, {"pk": 9, "model": "lib.parameter", "fields": {"name": "admin.VIRTUAL_UID", "value": "vmail"}}, {"pk": 10, "model": "lib.parameter", "fields": {"name": "admin.MAILDIR_ROOT", "value": ".maildir"}}, {"pk": 11, "model": "lib.parameter", "fields": {"name": "admin.VIRTUAL_GID", "value": "vmail"}}, {"pk": 12, "model": "lib.parameter", "fields": {"name": "stats.IMG_ROOTDIR", "value": "/srv/www/modoboa/media/stats"}}, {"pk": 13, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.USER_CAN_RELEASE", "value": "no"}}, {"pk": 14, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.CHECK_REQUESTS_INTERVAL", "value": "30"}}, {"pk": 15, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.AM_PDP_PORT", "value": "9998"}}, {"pk": 16, "model": "lib.parameter", "fields": {"name": "webmail.IMAP_SECURED", "value": "no"}}, {"pk": 17, "model": "lib.parameter", "fields": {"name": "postfix_autoreply.AUTOREPLIES_TIMEOUT", "value": "86400"}}, {"pk": 18, "model": "lib.parameter", "fields": {"name": "sievefilters.AUTHENTICATION_MECH", "value": "PLAIN"}}, {"pk": 19, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.AM_PDP_SOCKET", "value": "/var/amavis/amavisd.sock"}}, {"pk": 20, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.AM_PDP_MODE", "value": "inet"}}, {"pk": 21, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.SELF_SERVICE", "value": "no"}}, {"pk": 22, "model": "lib.parameter", "fields": {"name": "webmail.IMAP_PORT", "value": "143"}}, {"pk": 23, "model": "lib.parameter", "fields": {"name": "webmail.SMTP_SECURED_MODE", "value": "none"}}, {"pk": 24, "model": "lib.parameter", "fields": {"name": "sievefilters.SERVER", "value": "127.0.0.1"}}, {"pk": 25, "model": "lib.parameter", "fields": {"name": "webmail.SMTP_SERVER", "value": "127.0.0.1"}}, {"pk": 26, "model": "lib.parameter", "fields": {"name": "webmail.SMTP_AUTHENTICATION", "value": "no"}}, {"pk": 27, "model": "lib.parameter", "fields": {"name": "webmail.MAX_ATTACHMENT_SIZE", "value": "512K"}}, {"pk": 28, "model": "lib.parameter", "fields": {"name": "stats.RRD_ROOTDIR", "value": "/srv/www/modoboa/tmp"}}, {"pk": 29, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.RELEASED_MSGS_CLEANUP", "value": "no"}}, {"pk": 30, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.MAX_MESSAGES_AGE", "value": "14"}}, {"pk": 31, "model": "lib.parameter", "fields": {"name": "webmail.SMTP_PORT", "value": "25"}}, {"pk": 33, "model": "lib.parameter", "fields": {"name": "webmail.IMAP_SERVER", "value": "127.0.0.1"}}, {"pk": 34, "model": "lib.parameter", "fields": {"name": "sievefilters.STARTTLS", "value": "yes"}}, {"pk": 35, "model": "lib.parameter", "fields": {"name": "sievefilters.PORT", "value": "2000"}}, {"pk": 36, "model": "lib.parameter", "fields": {"name": "amavis_quarantine.AM_PDP_HOST", "value": "localhost"}}, {"pk": 37, "model": "lib.parameter", "fields": {"name": "stats.LOGFILE", "value": "/var/log/mail.log"}}]
[
{
"pk": 1,
"model": "lib.parameter",
"fields": {
"name": "admin.STORAGE_PATH",
"value": "/srv/vmail"
}
},
{
"pk": 2,
"model": "lib.parameter",
"fields": {
"name": "admin.PASSWORD_SCHEME",
"value": "crypt"
}
},
{
"pk": 3,
"model": "lib.parameter",
"fields": {
"name": "admin.ITEMS_PER_PAGE",
"value": "30"
}
},
{
"pk": 4,
"model": "lib.parameter",
"fields": {
"name": "general.DEFAULT_TOP_REDIRECTION",
"value": "userprefs"
}
},
{
"pk": 5,
"model": "lib.parameter",
"fields": {
"name": "admin.CREATE_DIRECTORIES",
"value": "yes"
}
},
{
"pk": 6,
"model": "lib.parameter",
"fields": {
"name": "auth.SECRET_KEY",
"value": "abcdefghijklmnop"
}
},
{
"pk": 7,
"model": "lib.parameter",
"fields": {
"name": "admin.MAILBOX_TYPE",
"value": "maildir"
}
},
{
"pk": 8,
"model": "lib.parameter",
"fields": {
"name": "admin.AUTHENTICATION_TYPE",
"value": "local"
}
},
{
"pk": 9,
"model": "lib.parameter",
"fields": {
"name": "admin.VIRTUAL_UID",
"value": "vmail"
}
},
{
"pk": 10,
"model": "lib.parameter",
"fields": {
"name": "admin.MAILDIR_ROOT",
"value": ".maildir"
}
},
{
"pk": 11,
"model": "lib.parameter",
"fields": {
"name": "admin.VIRTUAL_GID",
"value": "vmail"
}
},
{
"pk": 12,
"model": "lib.parameter",
"fields": {
"name": "stats.IMG_ROOTDIR",
"value": "/srv/www/modoboa/media/stats"
}
},
{
"pk": 13,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.USER_CAN_RELEASE",
"value": "no"
}
},
{
"pk": 14,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.CHECK_REQUESTS_INTERVAL",
"value": "30"
}
},
{
"pk": 15,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.AM_PDP_PORT",
"value": "9998"
}
},
{
"pk": 16,
"model": "lib.parameter",
"fields": {
"name": "webmail.IMAP_SECURED",
"value": "no"
}
},
{
"pk": 17,
"model": "lib.parameter",
"fields": {
"name": "postfix_autoreply.AUTOREPLIES_TIMEOUT",
"value": "86400"
}
},
{
"pk": 18,
"model": "lib.parameter",
"fields": {
"name": "sievefilters.AUTHENTICATION_MECH",
"value": "PLAIN"
}
},
{
"pk": 19,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.AM_PDP_SOCKET",
"value": "/var/amavis/amavisd.sock"
}
},
{
"pk": 20,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.AM_PDP_MODE",
"value": "inet"
}
},
{
"pk": 21,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.SELF_SERVICE",
"value": "no"
}
},
{
"pk": 22,
"model": "lib.parameter",
"fields": {
"name": "webmail.IMAP_PORT",
"value": "143"
}
},
{
"pk": 23,
"model": "lib.parameter",
"fields": {
"name": "webmail.SMTP_SECURED_MODE",
"value": "none"
}
},
{
"pk": 24,
"model": "lib.parameter",
"fields": {
"name": "sievefilters.SERVER",
"value": "127.0.0.1"
}
},
{
"pk": 25,
"model": "lib.parameter",
"fields": {
"name": "webmail.SMTP_SERVER",
"value": "127.0.0.1"
}
},
{
"pk": 26,
"model": "lib.parameter",
"fields": {
"name": "webmail.SMTP_AUTHENTICATION",
"value": "no"
}
},
{
"pk": 27,
"model": "lib.parameter",
"fields": {
"name": "webmail.MAX_ATTACHMENT_SIZE",
"value": "512K"
}
},
{
"pk": 28,
"model": "lib.parameter",
"fields": {
"name": "stats.RRD_ROOTDIR",
"value": "/srv/www/modoboa/tmp"
}
},
{
"pk": 29,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.RELEASED_MSGS_CLEANUP",
"value": "no"
}
},
{
"pk": 30,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.MAX_MESSAGES_AGE",
"value": "14"
}
},
{
"pk": 31,
"model": "lib.parameter",
"fields": {
"name": "webmail.SMTP_PORT",
"value": "25"
}
},
{
"pk": 33,
"model": "lib.parameter",
"fields": {
"name": "webmail.IMAP_SERVER",
"value": "127.0.0.1"
}
},
{
"pk": 34,
"model": "lib.parameter",
"fields": {
"name": "sievefilters.STARTTLS",
"value": "yes"
}
},
{
"pk": 35,
"model": "lib.parameter",
"fields": {
"name": "sievefilters.PORT",
"value": "2000"
}
},
{
"pk": 36,
"model": "lib.parameter",
"fields": {
"name": "amavis_quarantine.AM_PDP_HOST",
"value": "localhost"
}
},
{
"pk": 37,
"model": "lib.parameter",
"fields": {
"name": "stats.LOGFILE",
"value": "/var/log/mail.log"
}
}
]
+6 -8
View File
@@ -1,10 +1,4 @@
# coding: utf-8
import string
import inspect
import re
import copy
"""
This interface provides a simple way to declare and store parameters
in Modoboa's database.
@@ -15,6 +9,10 @@ will be available and modifiable directly from the web interface.
Only super users will be able to access this part of the web interface.
"""
import inspect
import re
import copy
_params = {}
_params_order = {}
_levels = {'A' : 'admin', 'U' : 'user'}
@@ -57,7 +55,7 @@ def register_app(app=None, aparams_opts=None, uparams_opts=None):
_params[app]['options']['A'] = aparams_opts
if uparams_opts:
_params[app]['options']['U'] = uparams_opts
def unregister_app(app):
"""Unregister an application
@@ -86,7 +84,7 @@ def __register(app, level, name, **kwargs):
:param name: the application's name
:param level: the level this parameter is available from
:param name: the parameter's name
"""
"""
if not app in _params.keys():
register_app(app)
+5 -10
View File
@@ -12,7 +12,7 @@ As the two products do not share the same schema, some informations
will be lost on the new database. Here is the list:
* Description, transport and backup MX for domains
* Logs
* Fetchmail table
* Fetchmail table
Mailboxes organisation on the filesystem will change. PostfixAdmin
uses the following layout::
@@ -42,7 +42,6 @@ compatible with PostfixAdmin ``md5crypt`` algorithm...
"""
import os, sys
from django.conf import settings
from django.db.utils import ConnectionDoesNotExist
from django.db.models import Q
from django.contrib.auth.models import Group
@@ -120,7 +119,7 @@ def migrate_mailboxes(domain, options):
new_mb.dates = migrate_dates(old_mb)
if old_mb.quota:
new_mb.quota = old_mb.quota / 1024000
new_mb.path = "%s/" % old_mb.local_part
if options.rename_dirs:
oldpath = os.path.join(options.mboxes_path, domain.name, old_mb.maildir)
@@ -131,7 +130,7 @@ def migrate_mailboxes(domain, options):
sys.exit(1)
new_mb.save(name=old_mb.name, password=old_mb.password, using=options.to)
def migrate_domain(old_dom, options):
print "Migrating domain %s" % old_dom.domain
@@ -201,9 +200,9 @@ if __name__ == "__main__":
help="Rename mailbox directories (default is no)")
parser.add_option("-p", "--mboxes-path", default=None,
help="Path where directories are stored on the filesystem (used only if --rename-dirs)")
options, args = parser.parse_args()
if options.rename_dirs and options.mboxes_path is None:
print "Error: you must provide the --mboxes-path option"
sys.exit(1)
@@ -212,7 +211,3 @@ if __name__ == "__main__":
do_migration(options)
except ConnectionDoesNotExist, e:
print e
+39 -13
View File
@@ -1,3 +1,4 @@
# coding: utf-8
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
@@ -15,10 +16,12 @@ class Admin(models.Model):
created = models.DateTimeField()
modified = models.DateTimeField()
active = models.IntegerField()
class Meta:
db_table = u'admin'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Alias(models.Model):
address = models.CharField(max_length=765, primary_key=True)
@@ -27,10 +30,12 @@ class Alias(models.Model):
created = models.DateTimeField()
modified = models.DateTimeField()
active = models.IntegerField()
class Meta:
db_table = u'alias'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class AliasDomain(models.Model):
alias_domain = models.CharField(max_length=765, primary_key=True)
@@ -38,19 +43,23 @@ class AliasDomain(models.Model):
created = models.DateTimeField()
modified = models.DateTimeField()
active = models.IntegerField()
class Meta:
db_table = u'alias_domain'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Config(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(unique=True, max_length=60)
value = models.CharField(max_length=60)
class Meta:
db_table = u'config'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Domain(models.Model):
domain = models.CharField(max_length=765, primary_key=True)
@@ -64,20 +73,24 @@ class Domain(models.Model):
created = models.DateTimeField()
modified = models.DateTimeField()
active = models.IntegerField()
class Meta:
db_table = u'domain'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class DomainAdmins(models.Model):
username = models.CharField(max_length=765)
domain = models.CharField(max_length=765)
created = models.DateTimeField(primary_key=True)
active = models.IntegerField()
class Meta:
db_table = u'domain_admins'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Fetchmail(models.Model):
id = models.IntegerField(primary_key=True)
@@ -96,10 +109,12 @@ class Fetchmail(models.Model):
returned_text = models.TextField(blank=True)
mda = models.CharField(max_length=765)
date = models.DateTimeField()
class Meta:
db_table = u'fetchmail'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Log(models.Model):
timestamp = models.DateTimeField()
@@ -107,10 +122,12 @@ class Log(models.Model):
domain = models.CharField(max_length=765)
action = models.CharField(max_length=765)
data = models.TextField()
class Meta:
db_table = u'log'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Mailbox(models.Model):
username = models.CharField(max_length=765, primary_key=True)
@@ -123,28 +140,34 @@ class Mailbox(models.Model):
created = models.DateTimeField()
modified = models.DateTimeField()
active = models.IntegerField()
class Meta:
db_table = u'mailbox'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Quota(models.Model):
username = models.CharField(max_length=765, primary_key=True)
path = models.CharField(max_length=300, primary_key=True)
current = models.BigIntegerField(null=True, blank=True)
class Meta:
db_table = u'quota'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Quota2(models.Model):
username = models.CharField(max_length=300, primary_key=True)
bytes = models.BigIntegerField()
messages = models.IntegerField()
class Meta:
db_table = u'quota2'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class Vacation(models.Model):
email = models.CharField(max_length=765)
@@ -154,16 +177,19 @@ class Vacation(models.Model):
domain = models.CharField(max_length=765)
created = models.DateTimeField()
active = models.IntegerField()
class Meta:
db_table = u'vacation'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
class VacationNotification(models.Model):
on_vacation = models.ForeignKey(Vacation, db_column='on_vacation')
notified = models.CharField(max_length=765, primary_key=True)
notified_at = models.DateTimeField()
class Meta:
db_table = u'vacation_notification'
app_label = 'pfxadmin_migrate'
managed = False
managed = False
+2 -3
View File
@@ -1,9 +1,8 @@
# coding: utf-8
from modoboa.lib import parameters, events
from django.utils.translation import ugettext_lazy
from modoboa.lib import parameters
parameters.register_user("LANG", type="list", deflt="en",
parameters.register_user("LANG", type="list", deflt="en",
label=ugettext_lazy("Prefered language"),
values=[("de", "deutsch"), ("en", "english"),
("es", "español"), ("fr", "français"),
+8 -5
View File
@@ -1,9 +1,12 @@
# coding: utf-8
from django import forms
from modoboa.admin.models import User, Domain, Mailbox
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.admin.models import User, Domain
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib import parameters
class BadDestination(Exception):
pass
@@ -25,7 +28,7 @@ class ProfileForm(forms.ModelForm):
del self.fields["oldpassword"]
del self.fields["newpassword"]
del self.fields["confirmation"]
def clean_oldpassword(self):
if self.cleaned_data["oldpassword"] == "":
return self.cleaned_data["oldpassword"]
@@ -53,13 +56,13 @@ class ProfileForm(forms.ModelForm):
class ForwardForm(forms.Form):
dest = forms.CharField(
label=ugettext_lazy("Recipient(s)"),
label=ugettext_lazy("Recipient(s)"),
widget=forms.Textarea,
required=False,
help_text=ugettext_lazy("Indicate one or more recipients separated by a ','")
)
keepcopies = forms.BooleanField(
label=ugettext_lazy("Keep local copies"),
label=ugettext_lazy("Keep local copies"),
required=False,
help_text=ugettext_lazy("Forward messages and store copies into your local mailbox")
)
@@ -79,4 +82,4 @@ class ForwardForm(forms.Form):
self.dests += [d]
else:
raise BadDestination(_("You can't define a forward to a local destination. Please ask your administrator to create an alias instead."))
@@ -1,10 +1,9 @@
# coding: utf-8
from django import template
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from modoboa import userprefs
from modoboa.lib import events
from modoboa.lib.webutils import static_url
register = template.Library()
@@ -29,7 +28,7 @@ def uprefs_menu(selection, user):
entries += events.raiseQueryEvent("UserMenuDisplay", "uprefs_menu", user)
entries = sorted(entries, key=lambda e: e["label"])
return render_to_string('common/menu.html', {
"entries" : entries,
"entries" : entries,
"css" : "nav nav-list",
"selection" : selection,
"user" : user
@@ -63,5 +62,5 @@ def user_menu(user, selection):
@register.simple_tag
def loadextmenu(selection, user):
menu = events.raiseQueryEvent("UserMenuDisplay", "top_menu", user)
return render_to_string('common/menulist.html',
return render_to_string('common/menulist.html',
{"selection" : selection, "entries" : menu, "user" : user})
+2 -1
View File
@@ -1,4 +1,5 @@
from django.conf.urls.defaults import *
# coding: utf-8
from django.conf.urls.defaults import patterns, url
from modoboa.lib import events
urlpatterns = patterns('modoboa.userprefs.views',
+7 -12
View File
@@ -1,19 +1,14 @@
# coding: utf-8
from django.shortcuts import render
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators \
import login_required, user_passes_test
from django.conf import settings
from django.contrib.auth.decorators import login_required, user_passes_test
from django.utils.translation import ugettext as _
from modoboa.lib import parameters, events
from modoboa.lib.exceptions import ModoboaException
from modoboa.lib.webutils import ajax_response, ajax_simple_response
from forms import *
from modoboa.admin.models import Mailbox, Alias
from modoboa.admin.lib import AdminError
from modoboa.auth.lib import encrypt
from forms import ForwardForm, BadDestination, ProfileForm
@login_required
def index(request, tplname="userprefs/index.html"):
@@ -31,7 +26,7 @@ def forward(request, tplname='userprefs/forward.html'):
except IndexError:
raise ModoboaException(_("You need a mailbox in order to define a forward"))
try:
al = Alias.objects.get(address=mb.address,
al = Alias.objects.get(address=mb.address,
domain__name=mb.domain.name)
except Alias.DoesNotExist:
al = None
@@ -55,7 +50,7 @@ def forward(request, tplname='userprefs/forward.html'):
error = str(e)
return ajax_simple_response(dict(
status="ko",
status="ko",
content=render_to_string(tplname, {"form" : form}),
respmsg=error
))
@@ -70,7 +65,7 @@ def forward(request, tplname='userprefs/forward.html'):
else:
form.fields["keepcopies"].initial = True
return ajax_simple_response({
"status" : "ok",
"status" : "ok",
"content" : render_to_string(tplname, {
"form" : form
})
@@ -92,13 +87,13 @@ def profile(request, tplname='userprefs/profile.html'):
status="ok", respmsg=_("Profile updated")
))
return ajax_simple_response({
"status" : "ko",
"status" : "ko",
"content" : render_to_string(tplname, {"form" : form})
})
form = ProfileForm(update_password, instance=request.user)
return ajax_simple_response({
"status" : "ok",
"status" : "ok",
"content" : render_to_string(tplname, {
"form" : form
})