mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Ticket #48.
This is not finished yet. More work than what I expected but the main part is done. It will need a lot of testing... There is still a bug when activating a new extension in development mode (ie. with python web server), I think it does not occur behind a real web server.
This commit is contained in:
+53
-2
@@ -2,7 +2,7 @@ from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from mailng.lib import parameters
|
||||
from mailng.lib import exec_as_vuser
|
||||
from mailng.lib import exec_cmd, exec_as_vuser
|
||||
import os
|
||||
|
||||
class Domain(models.Model):
|
||||
@@ -38,7 +38,7 @@ class Domain(models.Model):
|
||||
class Mailbox(models.Model):
|
||||
name = models.CharField(_('name'), max_length=100,
|
||||
help_text=_("First name and last name of mailbox owner"))
|
||||
address = models.CharField(_('address'), max_length=100,
|
||||
address = models.CharField(_('address'), max_length=10,
|
||||
help_text=_("Mailbox address (without the @domain.tld part)"))
|
||||
full_address = models.CharField(max_length=150)
|
||||
password = models.CharField(_('password'), max_length=100)
|
||||
@@ -111,3 +111,54 @@ class Alias(models.Model):
|
||||
permissions = (
|
||||
("view_aliases", "View aliases"),
|
||||
)
|
||||
|
||||
class Extension(models.Model):
|
||||
name = models.CharField(max_length=150)
|
||||
enabled = models.BooleanField(_('enabled'),
|
||||
help_text=_("Check to enable this extension"))
|
||||
has_templates = models.BooleanField()
|
||||
|
||||
def on(self):
|
||||
from django.core.management import sql, color
|
||||
from django.db import connection, models
|
||||
|
||||
style = color.no_style()
|
||||
cursor = connection.cursor()
|
||||
module = __import__("mailng.extensions.%s" % self.name,
|
||||
globals(), locals(), ['main', 'models'])
|
||||
try:
|
||||
extmodels = module.models
|
||||
except AttributeError:
|
||||
extmodels = None
|
||||
if extmodels is not None:
|
||||
mods = []
|
||||
for k, v in extmodels.__dict__.iteritems():
|
||||
try:
|
||||
if not issubclass(v, models.Model):
|
||||
continue
|
||||
statements, pending = \
|
||||
connection.creation.sql_create_model(v, style)
|
||||
try:
|
||||
for sql in statements:
|
||||
cursor.execute(sql)
|
||||
except Exception, msg:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
if self.has_templates:
|
||||
code, output = exec_cmd("ln -s ../extensions/%s/templates templates/%s" \
|
||||
% (self.name, self.name))
|
||||
module.main.init()
|
||||
self.enabled = True
|
||||
self.save()
|
||||
|
||||
def off(self):
|
||||
if self.has_templates:
|
||||
code, output = exec_cmd("rm -f templates/%s" % self.name)
|
||||
print output
|
||||
self.enabled = False
|
||||
self.save()
|
||||
module = __import__("mailng.extensions.%s" % self.name,
|
||||
globals(), locals(), ['main', 'models'])
|
||||
module.main.destroy()
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ def param(app, definition):
|
||||
values = definition["values"]
|
||||
value = definition.has_key("value") \
|
||||
and definition["value"] or definition["default"]
|
||||
print name, values
|
||||
for v in values:
|
||||
selected = ""
|
||||
if value == v[0]:
|
||||
|
||||
@@ -20,6 +20,7 @@ urlpatterns = patterns('mailng.admin.views',
|
||||
(r'^settings/addperm/$', 'addpermission'),
|
||||
(r'^settings/delperm/(?P<mbox_id>\d+)/(?P<group>\w+)/$', 'deletepermission'),
|
||||
(r'^settings/extensions/$', 'viewextensions'),
|
||||
(r'^settings/extensions/save/$', 'saveextensions'),
|
||||
(r'^settings/parameters/$', 'viewparameters'),
|
||||
(r'^settings/parameters/save/$', 'saveparameters'),
|
||||
)
|
||||
|
||||
+46
-2
@@ -12,7 +12,7 @@ from django.contrib.auth.decorators \
|
||||
import login_required, permission_required, user_passes_test
|
||||
from django.contrib.auth.models import User, Group
|
||||
from mailng import admin, main
|
||||
from mailng.admin.models import Domain, Mailbox, Alias
|
||||
from mailng.admin.models import Domain, Mailbox, Alias, Extension
|
||||
from forms import MailboxForm, DomainForm, AliasForm, PermissionForm
|
||||
from mailng.lib.authbackends import crypt_password
|
||||
from mailng.lib import _render, _ctx_ok, _ctx_ko
|
||||
@@ -447,6 +447,50 @@ def saveparameters(request):
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def viewextensions(request):
|
||||
from mailng.extensions import list_extensions
|
||||
from mailng.lib import tables
|
||||
|
||||
class ExtensionsTable(tables.Table):
|
||||
idkey = "id"
|
||||
selection = tables.SelectionColumn("selection", width="4%", first=True)
|
||||
_1_name = tables.Column("name", label=_("Name"), width="15%")
|
||||
_2_version = tables.Column("version", label=_("Version"), width="6%")
|
||||
_3_descr = tables.Column("description", label=_("Description"))
|
||||
|
||||
exts = list_extensions()
|
||||
for ext in exts:
|
||||
try:
|
||||
dbext = Extension.objects.get(name=ext["id"])
|
||||
ext["selection"] = dbext.enabled
|
||||
except Extension.DoesNotExist:
|
||||
dbext = Extension()
|
||||
dbext.name = ext["id"]
|
||||
dbext.enabled = False
|
||||
dbext.has_templates = ext["templates"]
|
||||
dbext.save()
|
||||
ext["selection"] = False
|
||||
|
||||
tbl = ExtensionsTable(exts)
|
||||
return _render(request, 'admin/extensions.html', {
|
||||
"extensions" : list_extensions()
|
||||
"extensions" : tbl.render(request)
|
||||
})
|
||||
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def saveextensions(request):
|
||||
from mailng import extensions
|
||||
|
||||
actived_exts = Extension.objects.filter(enabled=True)
|
||||
found = []
|
||||
for k, v in request.POST.iteritems():
|
||||
if k.startswith("select_"):
|
||||
parts = k.split("_", 1)
|
||||
dbext = Extension.objects.get(name=parts[1])
|
||||
if not dbext in actived_exts:
|
||||
dbext.on()
|
||||
else:
|
||||
found += [dbext]
|
||||
for ext in actived_exts:
|
||||
if not ext in found:
|
||||
ext.off()
|
||||
|
||||
return HttpResponseRedirect(reverse(admin.views.viewextensions))
|
||||
|
||||
+17
-11
@@ -2,29 +2,29 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
extensions = []
|
||||
extensions = {}
|
||||
|
||||
def loadextensions():
|
||||
from django.conf import settings
|
||||
from admin.models import Extension
|
||||
|
||||
expr = re.compile("mailng\.extensions\.(.+)")
|
||||
for app in settings.INSTALLED_APPS:
|
||||
m = expr.match(app)
|
||||
if not m:
|
||||
continue
|
||||
module = __import__(m.group(1), globals(), locals(), ['main'])
|
||||
exts = Extension.objects.filter(enabled=True)
|
||||
for ext in exts:
|
||||
module = __import__(ext.name, globals(), locals(), ['main'])
|
||||
module.main.init()
|
||||
globals()['extensions'] += [module]
|
||||
globals()['extensions'][ext] = module
|
||||
|
||||
def loadmenus():
|
||||
result = []
|
||||
for mod in extensions:
|
||||
for name, mod in extensions.iteritems():
|
||||
u = mod.main.urls()
|
||||
if u == ():
|
||||
continue
|
||||
result += (u,)
|
||||
return result
|
||||
|
||||
def isenabled(ext):
|
||||
return extensions.has_key(ext)
|
||||
|
||||
def list_extensions():
|
||||
basedir = "extensions"
|
||||
result = []
|
||||
@@ -39,7 +39,13 @@ def list_extensions():
|
||||
if isvalid:
|
||||
module = __import__(d, globals(), locals(), ['main'])
|
||||
try:
|
||||
result += [module.main.infos()]
|
||||
infos = module.main.infos()
|
||||
infos["id"] = d
|
||||
if os.path.isdir("%s/%s/templates" % (basedir, d)):
|
||||
infos["templates"] = True
|
||||
else:
|
||||
infos["templates"] = False
|
||||
result += [infos]
|
||||
except AttributeError:
|
||||
pass
|
||||
return result
|
||||
|
||||
@@ -46,6 +46,10 @@ def init():
|
||||
"/var/amavis/amavisd.sock",
|
||||
help="")
|
||||
|
||||
def destroy():
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
parameters.unregister_app("amavis_quarantine")
|
||||
|
||||
def urls():
|
||||
return (r'^mailng/quarantine/',
|
||||
include('mailng.extensions.amavis_quarantine.urls'))
|
||||
|
||||
@@ -41,6 +41,9 @@ def init():
|
||||
parameters.register("stats", "IMG_ROOTDIR", "string", "/tmp/mailng",
|
||||
help=_("Path to directory where PNG files are stored"))
|
||||
|
||||
def destroy():
|
||||
events.unregister("AdminMenuDisplay", menu)
|
||||
|
||||
def urls():
|
||||
return (r'^mailng/stats/',
|
||||
include('mailng.extensions.stats.urls'))
|
||||
|
||||
@@ -16,7 +16,6 @@ from mailng.lib import tables, imap_utf7, parameters
|
||||
class WMtable(tables.Table):
|
||||
tableid = "emails"
|
||||
idkey = "imapid"
|
||||
# selection = tables.SelectionColumn("selection", width="20px", first=True)
|
||||
_1_flags = tables.ImgColumn("flags", width="4%",
|
||||
header="<input type='checkbox' name='toggleselect' id='toggleselect' />")
|
||||
_2_subject = tables.Column("subject", label=_("Subject"),
|
||||
|
||||
@@ -34,6 +34,11 @@ def init():
|
||||
parameters.register("webmail", "SMTP_SECURED", "list_yesno", "no",
|
||||
help=_("Use a secured connection to access SMTP server"))
|
||||
|
||||
def destroy():
|
||||
events.unregister("UserMenuDisplay", menu)
|
||||
events.unregister("UserLogin", userlogin)
|
||||
parameters.unregister_app("webmail")
|
||||
|
||||
def infos():
|
||||
return {
|
||||
"name" : "Webmail",
|
||||
@@ -139,14 +144,12 @@ def viewmail(request, folder, mail_id=None):
|
||||
def getmailcontent(request, folder, mail_id):
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
if "class" in msg.keys() and msg["class"] == "unseen":
|
||||
#IMAPconnector(request).msg_read(*msg["imapid"].split("/"))
|
||||
IMAPconnector(request).msg_read(folder, mail_id)
|
||||
email = ImapEmail(msg, mode="html", links="1")
|
||||
try:
|
||||
pageid = request.session["page"]
|
||||
except KeyError:
|
||||
pageid = "1"
|
||||
#folder, imapid = msg["imapid"].split("/")
|
||||
return _render(request, "common/viewmail.html", {
|
||||
"headers" : email.render_headers(folder, mail_id),
|
||||
"folder" : folder, "imapid" : mail_id, "mailbody" : email.body,
|
||||
|
||||
@@ -27,6 +27,13 @@ def register(event, callback):
|
||||
callbacks[event].append(callback)
|
||||
return 1
|
||||
|
||||
def unregister(event, callback):
|
||||
if not event in events:
|
||||
return False
|
||||
if not callbacks.has_key(event):
|
||||
return False
|
||||
callbacks[event].remove(callback)
|
||||
|
||||
def raiseEvent(event, **kwargs):
|
||||
if not event in events or not event in callbacks.keys():
|
||||
return 0
|
||||
|
||||
@@ -35,6 +35,12 @@ def register(app, name, type="string", deflt=None, help=None, **kwargs):
|
||||
for k, v in kwargs.iteritems():
|
||||
_params[app][name][k] = v
|
||||
|
||||
def unregister_app(app):
|
||||
if not _params.has_key(app):
|
||||
return False
|
||||
del _params[app]
|
||||
return True
|
||||
|
||||
def save(app, name, value):
|
||||
if not app in _params.keys() or not name in _params[app].keys():
|
||||
raise NotDefined(app, name)
|
||||
|
||||
+20
-24
@@ -19,13 +19,14 @@ class Column:
|
||||
label = getattr(self, "name")
|
||||
return label
|
||||
|
||||
|
||||
class SelectionColumn(Column):
|
||||
def __str__(self):
|
||||
return "<input type='checkbox' name='selectall' id='selectall' />"
|
||||
|
||||
def render(self, value):
|
||||
return "<input type='checkbox' name='%s' value='%s' />" \
|
||||
% (self.name, value)
|
||||
def render(self, value, selection):
|
||||
return "<input type='checkbox' id='%s' name='select_%s' value='%s' %s/>" \
|
||||
% (self.name, value, value, selection and "checked" or "")
|
||||
|
||||
class ImgColumn(Column):
|
||||
sortable = False
|
||||
@@ -56,33 +57,28 @@ class Table(object):
|
||||
nrow = {"id" : row[self.idkey], "cols" : []}
|
||||
for c in self.columns:
|
||||
newcol = {"name" : c.name}
|
||||
try:
|
||||
newcol["width"] = getattr(c, "width")
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
newcol["align"] = getattr(c, "align")
|
||||
except:
|
||||
pass
|
||||
for key in ["width", "align", "cssclass"]:
|
||||
try:
|
||||
newcol[key] = getattr(c, key)
|
||||
except AttributeError:
|
||||
newcol[key] = ""
|
||||
if isinstance(c, SelectionColumn):
|
||||
newcol["value"] = c.render(row[self.idkey])
|
||||
newcol["safe"] = True
|
||||
|
||||
if isinstance(c, ImgColumn):
|
||||
selection = row.has_key(c.name) and row[c.name] or False
|
||||
newcol["value"] = c.render(row[self.idkey], selection)
|
||||
newcol["safe"] = True
|
||||
elif isinstance(c, ImgColumn):
|
||||
key = "img_%s" % c.name
|
||||
if key in row.keys():
|
||||
newcol["value"] = c.render(row[key])
|
||||
newcol["safe"] = True
|
||||
|
||||
if row.has_key(c.name):
|
||||
try:
|
||||
cssclass = getattr(c, "cssclass")
|
||||
except AttributeError:
|
||||
cssclass = ""
|
||||
elif row.has_key(c.name):
|
||||
if "class" in row.keys():
|
||||
cssclass += (cssclass != "") and ", %s" % row["class"] or row["class"]
|
||||
newcol["value"] = self.parse(c.name, row[c.name])
|
||||
newcol["class"] = cssclass
|
||||
newcol["cssclass"] += newcol["cssclass"] != "" \
|
||||
and ", %s" % row["class"] or row["class"]
|
||||
try:
|
||||
newcol["value"] = self.parse(c.name, row[c.name])
|
||||
except AttributeError:
|
||||
newcol["value"] = row[c.name]
|
||||
nrow["cols"] += [newcol]
|
||||
self.rows += [nrow]
|
||||
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
{% endblock %}
|
||||
{% block setcontent %}
|
||||
{% load i18n %}
|
||||
<table>
|
||||
<tr><th>{% trans "Name" %}</th><th>{% trans "Version" %}</th><th>{% trans "Description" %}</th></tr>
|
||||
{% for ext in extensions %}
|
||||
<tr><td>{{ ext.name }}</td><td>{{ ext.version }}</td><td>{{ ext.description }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<form method="POST" action="{% url admin.views.saveextensions %}">
|
||||
{{ extensions }}
|
||||
<br />
|
||||
<input type="submit" name="apply" value="{% trans "Apply" %}" />
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% for row in table.rows %}
|
||||
<tr id="{{ row.id }}" class="{% cycle 'odd' 'even' %}">
|
||||
{% for cell in row.cols %}
|
||||
<td name="{{ cell.name }}" width="{{ cell.width }}" align="{{ cell.align }}" class="{{ cell.class }}">
|
||||
<td name="{{ cell.name }}" width="{{ cell.width }}" align="{{ cell.align }}" class="{{ cell.cssclass }}">
|
||||
{% if cell.safe %}{{ cell.value|safe }}{% else %}{{ cell.value }}{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
@@ -5,7 +5,7 @@ from django.conf import settings
|
||||
# from django.contrib import admin
|
||||
# admin.autodiscover()
|
||||
|
||||
from mailng.extensions import loadextensions, loadmenus
|
||||
from mailng.extensions import loadextensions, loadmenus, isenabled
|
||||
from mailng.lib import parameters
|
||||
loadextensions()
|
||||
|
||||
@@ -27,6 +27,7 @@ urlpatterns = patterns('',
|
||||
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog',
|
||||
{'packages': ('mailng',),})
|
||||
)
|
||||
|
||||
menus = loadmenus()
|
||||
if menus != ():
|
||||
urlpatterns += patterns('', *menus)
|
||||
@@ -38,7 +39,7 @@ if settings.DEBUG:
|
||||
{'document_root': settings.STATIC_ROOTDIR})
|
||||
)
|
||||
# Stats
|
||||
if 'mailng.extensions.stats' in settings.INSTALLED_APPS:
|
||||
if isenabled('stats'):
|
||||
urlpatterns += patterns(
|
||||
'',
|
||||
(r'^img/stats/(?P<path>.*)/$', 'django.views.static.serve',
|
||||
|
||||
Reference in New Issue
Block a user