This commit is contained in:
Vincent Génieux
2015-04-26 16:16:47 +05:30
14 changed files with 101 additions and 306 deletions
+7 -7
View File
@@ -26,13 +26,13 @@ frameworks.
Main features
*************
* Administration panel
* `Amavisd-new <http://www.amavis.org>`_ frontend
* Webmail
* Per-user Sieve filters
* Autoreply messages for Postfix
* Graphical statistics about email traffic
* `Radicale <http://radicale.org/>`_ management frontend
* `Administration panel <https://github.com/modoboa/modoboa-admin>`_
* `Amavisd-new <http://www.amavis.org>`_ `frontend <https://github.com/modoboa/modoboa-amavis>`_
* `Webmail <https://github.com/modoboa/modoboa-webmail>`_
* `Per-user Sieve filters <https://github.com/modoboa/modoboa-sievefilters>`_
* `Autoreply messages for Postfix <https://github.com/modoboa/modoboa-postfix-autoreply>`_
* `Graphical statistics about email traffic <https://github.com/modoboa/modoboa-stats>`_
* `Radicale <http://radicale.org/>`_ `management frontend <https://github.com/modoboa/modoboa-radicale>`_
*************
Documentation
+1 -1
View File
@@ -110,7 +110,7 @@ Deployment
prefix. The primary connection must use the ``default:`` prefix (as
shown in the example above). For the `amavis extension
<http://modoboa-amavis.readthedocs.org>`_ extension, use the
``amavis:`` prefix like this ``--dburl amavis:<database url>``.
``amavis:`` prefix like this ``amavis:<database url>``.
Your database url should meet the following syntax
``scheme://[user:pass@][host:port]/dbname`` **OR**
+6 -5
View File
@@ -14,9 +14,10 @@ class Command(object):
A valid administrative command must inherit from this class.
"""
help = "No help available."
def __init__(self, commands, verbose=False, **kwargs):
def __init__(self, commands, verbose=False):
self._commands = commands
self._parser = argparse.ArgumentParser()
self._verbose = verbose
@@ -25,10 +26,10 @@ class Command(object):
self._templates_dir = "%s/templates" % os.path.dirname(__file__)
def _render_template(self, tplfile, env):
fp = open(tplfile)
t = Template(fp.read())
fp.close()
return t.render(Context(env))
"""Render an HTML template."""
with open(tplfile) as fp:
tpl = Template(fp.read())
return tpl.render(Context(env))
def run(self, cmdline):
args = self._parser.parse_args(cmdline)
+24 -12
View File
@@ -143,6 +143,16 @@ class DeployCommand(Command):
info['PASSWORD'] = getpass.getpass('Password: ')
return info
def _get_extension_list(self):
"""Ask the API to get the list of all extensions.
We hardcode the API url here to avoid a loading of
django's settings since they are not available yet...
"""
url = "http://api.modoboa.org/"
official_exts = ModoAPIClient(url).list_extensions()
return [extension["name"] for extension in official_exts]
def install_extensions(self, extensions):
"""Install one or more extensions.
@@ -150,23 +160,19 @@ class DeployCommand(Command):
include in the final configuration.
"""
if "all" in extensions:
official_exts = ModoAPIClient().list_extensions()
extensions = [extension["name"] for extension in official_exts]
pip_args = ["install"] + extensions
pip_args = ["install"] + [extension[0] for extension in extensions]
pip.main(pip_args)
extra_settings = []
for extension in extensions:
extension = extension.replace("-", "_")
module = __import__(extension, locals(), globals(), [])
module = __import__(extension[1], locals(), globals(), [])
basedir = os.path.dirname(module.__file__)
if not os.path.exists("{0}/settings.py".format(basedir)):
continue
extra_settings.append(extension)
extra_settings.append(extension[1])
return extra_settings
def handle(self, parsed_args):
import pdb
django.setup()
management.call_command(
'startproject', parsed_args.name, verbosity=False
@@ -201,10 +207,16 @@ class DeployCommand(Command):
)
if not allowed_host:
allowed_host = "localhost"
extra_settings = []
if parsed_args.dont_install_extensions and parsed_args.extensions:
extra_settings = self.install_extensions(parsed_args.extensions)
extensions = parsed_args.extensions
if extensions:
if "all" in extensions:
extensions = self._get_extension_list()
extensions = [(extension, extension.replace("-", "_"))
for extension in extensions]
if not parsed_args.dont_install_extensions:
extra_settings = self.install_extensions(extensions)
extensions = [extension[1] for extension in extensions]
bower_components_dir = os.path.realpath(
os.path.join(os.path.dirname(__file__), "../../bower_components")
@@ -221,7 +233,7 @@ class DeployCommand(Command):
'timezone': parsed_args.timezone,
'bower_components_dir': bower_components_dir,
'devmode': parsed_args.devel,
'extensions': parsed_args.extensions,
'extensions': extensions,
'extra_settings': extra_settings
}
)
+4 -2
View File
@@ -11,7 +11,6 @@ from django.template import Context, Template
import dj_database_url
from modoboa.lib.api_client import ModoAPIClient
from . import Command
MAP_FILE_TEMPLATE = """user = {{ dbuser }}
@@ -85,8 +84,11 @@ class MapFilesGenerator(object):
def __load_extensions(self, extensions):
"""Load specified extensions."""
if "all" in extensions:
from modoboa.lib.api_client import ModoAPIClient
# Retrieve extension list from the API
official_exts = ModoAPIClient().list_extensions()
url = "http://api.modoboa.org/"
official_exts = ModoAPIClient(url).list_extensions()
extensions = [extension["name"] for extension in official_exts]
for extension in extensions:
@@ -164,5 +164,5 @@ LOGGING = {
}
}
{% for extension in extra_settings %}
import {{ extension }}.settings
from {{ extension }}.settings import *
{% endfor %}
+6
View File
@@ -9,6 +9,8 @@ from . import models
class PermissionFactory(factory.DjangoModelFactory):
"""A base factory to handle permissions."""
class Meta:
abstract = True
@@ -21,6 +23,8 @@ class PermissionFactory(factory.DjangoModelFactory):
class GroupFactory(factory.DjangoModelFactory):
"""A factory to create Group instances."""
class Meta:
model = Group
@@ -29,6 +33,8 @@ class GroupFactory(factory.DjangoModelFactory):
class UserFactory(PermissionFactory):
"""A factory to create User instances."""
class Meta:
model = models.User
django_get_or_create = ("username", )
+8 -9
View File
@@ -6,11 +6,6 @@ import pkg_resources
import requests
from requests.exceptions import RequestException
from django.conf import settings
from versionfield.constants import DEFAULT_NUMBER_BITS
from versionfield.version import Version
class ModoAPIClient(object):
@@ -18,10 +13,11 @@ class ModoAPIClient(object):
def __init__(self, api_url=None):
"""Constructor."""
self._api_url = (
settings.MODOBOA_API_URL if api_url is None
else api_url
)
if api_url is None:
from django.conf import settings
self._api_url = settings.MODOBOA_API_URL
else:
self._api_url = api_url
self._local_core_version = None
self._latest_core_version = None
self.changelog_url = None
@@ -55,6 +51,9 @@ class ModoAPIClient(object):
def new_core_version(self, site):
"""Check the latest version."""
from versionfield.constants import DEFAULT_NUMBER_BITS
from versionfield.version import Version
url = os.path.join(self._api_url, "current_version/")
params = {"client_version": self.local_core_version, "site": site}
resp = self.__send_request(url, params)
-234
View File
@@ -1,234 +0,0 @@
[
{
"pk": 2,
"model": "lib.parameter",
"fields": {
"name": "core.PASSWORD_SCHEME",
"value": "crypt"
}
},
{
"pk": 3,
"model": "lib.parameter",
"fields": {
"name": "core.ITEMS_PER_PAGE",
"value": "30"
}
},
{
"pk": 4,
"model": "lib.parameter",
"fields": {
"name": "core.DEFAULT_TOP_REDIRECTION",
"value": "user"
}
},
{
"pk": 8,
"model": "lib.parameter",
"fields": {
"name": "core.AUTHENTICATION_TYPE",
"value": "local"
}
},
{
"pk": 9,
"model": "lib.parameter",
"fields": {
"name": "admin.MAILBOXES_OWNER",
"value": "vmail"
}
},
{
"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.AM_PDP_SOCKET",
"value": "/var/amavis/amavisd.sock"
}
},
{
"pk": 20,
"model": "lib.parameter",
"fields": {
"name": "amavis.AM_PDP_MODE",
"value": "inet"
}
},
{
"pk": 21,
"model": "lib.parameter",
"fields": {
"name": "amavis.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.RELEASED_MSGS_CLEANUP",
"value": "no"
}
},
{
"pk": 30,
"model": "lib.parameter",
"fields": {
"name": "amavis.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.AM_PDP_HOST",
"value": "localhost"
}
},
{
"pk": 37,
"model": "lib.parameter",
"fields": {
"name": "stats.LOGFILE",
"value": "/var/log/mail.log"
}
}
]
+1
View File
@@ -216,6 +216,7 @@ def __is_defined(app, level, name):
def save_admin(name, value, app=None):
"""Set a new value for a given parameter."""
from .models import Parameter
if app is None:
+2 -2
View File
@@ -12,7 +12,7 @@ from django import template
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.shortcuts import render
from django.template.loader import render_to_string
@@ -88,7 +88,7 @@ def ajax_response(request, status="ok", respmsg=None,
if url is not None:
jsonctx["url"] = url
jsonctx["norefresh"] = norefresh
return HttpResponse(json.dumps(jsonctx), mimetype="application/json")
return JsonResponse(jsonctx)
def render_to_json_response(context, **response_kwargs):
+30 -30
View File
@@ -22,13 +22,13 @@ msgstr ""
#: core/__init__.py:60 core/__init__.py:61
msgid "General"
msgstr "Grundsätzlich"
msgstr "Allgemein"
#: core/__init__.py:80
#, fuzzy
#| msgid "An update is available!"
msgid "One or more updates are available"
msgstr "Ein Update ist Verfügbar!"
msgstr "Ein Update ist verfügbar!"
#: core/app_settings.py:34
msgid "Authentication"
@@ -44,11 +44,11 @@ msgstr "Lokal"
#: core/app_settings.py:41
msgid "The backend used for authentication"
msgstr "Das backend, das zur Authentifizierung genutzt wird"
msgstr "Backend für die Authentifizierung"
#: core/app_settings.py:46
msgid "Default password scheme"
msgstr "Standard Passwortschema"
msgstr "Standard-Passwortschema"
#: core/app_settings.py:49
msgid "md5crypt (weak)"
@@ -136,7 +136,7 @@ msgstr "Direkter Bind"
#: core/app_settings.py:106
msgid "Choose the authentication method to use"
msgstr "Wählen Sie die Authentifizierungsmethode aus"
msgstr "Wählen Sie die Authentifizierungsmethode"
#: core/app_settings.py:111
msgid "Bind DN"
@@ -174,7 +174,7 @@ msgid ""
"An optional filter string (e.g. '(objectClass=person)'). In order to be "
"valid, it must be enclosed in parentheses."
msgstr ""
"Ein optionaler Filter z.B. (objectClass=person). Das Filter muss "
"Ein optionaler Filter, z.B. '(objectClass=person)'. Der Filter muss "
"entsprechend der LDAP-Konvention in Klammern gesetzt werden."
#: core/app_settings.py:155
@@ -187,7 +187,7 @@ msgid ""
"The template used to construct a user's DN. It should contain one "
"placeholder (ie. %(user)s)"
msgstr ""
"Die Vorlage für einen User DN. Sie sollte einen Platzhalter z.B. %(user)s "
"Die Vorlage für einen User DN. Sie sollte einen Platzhalter, z.B. %(user)s "
"enthalten."
#: core/app_settings.py:166
@@ -216,7 +216,7 @@ msgid ""
"Use ';' characters to separate groups."
msgstr ""
"Mitglieder dieser LDAP Posix-Gruppe werden als Domain-Administratoren "
"angelegt werden. Nutzen sie ';', um Gruppen zu separieren."
"angelegt werden. Nutzen Sie ';', um Gruppen zu separieren."
#: core/app_settings.py:190
msgid "Groups search base"
@@ -232,11 +232,11 @@ msgstr "Verschiedenes"
#: core/app_settings.py:201
msgid "Top notifications check interval"
msgstr "Benachrichtigungs Prüfintervall"
msgstr "Benachrichtigungs-Prüfintervall"
#: core/app_settings.py:203
msgid "Interval between two top notification checks"
msgstr "Prüfintervall nach neuen Benachrichtigungen"
msgstr "Intervall zwischen Benachrichtigungs-Prüfungen"
#: core/app_settings.py:208
msgid "Check new versions"
@@ -248,7 +248,7 @@ msgstr "Überprüft automatisch ob eine neuere Version verfügbar ist."
#: core/app_settings.py:215
msgid "Maximum log record age"
msgstr "Maximales Alter eines Log-Eintrags"
msgstr "Maximales Alter für Log-Einträge"
#: core/app_settings.py:217
msgid "The maximum age in days of a log record"
@@ -264,7 +264,7 @@ msgstr "Anzahl dargestellter Einträge pro Seite"
#: core/app_settings.py:229
msgid "Default top redirection"
msgstr "Standard Top-Redirect"
msgstr "Standardumleitung"
#: core/app_settings.py:233
msgid "The default redirection used when no application is specified"
@@ -352,7 +352,7 @@ msgstr "Ungültige Zeile"
#: core/models.py:347
msgid "You can't import an account with a role greater than yours"
msgstr "Sie können kein Konto einer höheren Rolle importieren"
msgstr "Sie können kein Konto mit einer höheren Rolle als Ihrer eigenen importieren"
#: core/models.py:359
#, python-format
@@ -395,17 +395,17 @@ msgstr "Verschiedene Informationen über Modoboa"
#, fuzzy
#| msgid "An update is available!"
msgid "One or more updates are available!"
msgstr "Ein Update ist Verfügbar!"
msgstr "Ein Update ist verfügbar!"
#: core/templates/core/information.html:6
msgid "Check the following list to find the component(s) that can be updated."
msgstr ""
msgstr "Überprüfen Sie die folgende Liste auf Komponenten, die aktualisiert werden können."
#: core/templates/core/information.html:10
#, fuzzy
#| msgid "Authentication"
msgid "Applications"
msgstr "Authentifizierung"
msgstr "Anwendungen"
#: core/templates/core/information.html:14
#: core/templates/core/information.html:36
@@ -414,13 +414,13 @@ msgstr "Name"
#: core/templates/core/information.html:15
msgid "Installed version"
msgstr ""
msgstr "Installierte Version"
#: core/templates/core/information.html:16
#, fuzzy
#| msgid "Last login"
msgid "Last version"
msgstr "Letzte Anmeldung"
msgstr "Letzte Version"
#: core/templates/core/information.html:17
msgid "Description"
@@ -452,7 +452,7 @@ msgstr "Datum"
#: core/templates/core/logs.html:7
msgid "Level"
msgstr "Level"
msgstr "Stufe"
#: core/templates/core/logs.html:8
msgid "Logger"
@@ -498,7 +498,7 @@ msgstr "Profil"
#: core/templates/core/user_profile.html:2
msgid "Update your personal information"
msgstr "Aktualisieren sie ihre persönlichen Daten"
msgstr "Aktualisieren Sie Ihre persönlichen Daten"
#: core/templates/core/user_profile.html:8 lib/form_utils.py:333
msgid "Update"
@@ -516,7 +516,7 @@ msgstr "Logout"
#, fuzzy
#| msgid "Welcome to Modoboa"
msgid "The core part of Modoboa"
msgstr "Willkommen zu Modoboa"
msgstr "Der Kern von Modoboa"
#: core/views/admin.py:52
msgid "Parameters saved"
@@ -525,7 +525,7 @@ msgstr "Parameter gespeichert"
#: core/views/auth.py:33
#, python-format
msgid "User '%s' successfully logged in"
msgstr "Identität '%s' hat sich erfolgreich angemeldet"
msgstr "Benutzer '%s' hat sich erfolgreich angemeldet"
#: core/views/auth.py:48
msgid "Your username and password didn't match. Please try again."
@@ -536,7 +536,7 @@ msgstr ""
#: core/views/auth.py:76
#, python-format
msgid "User '%s' logged out"
msgstr "Identität '%s' hat sich abgemeldet"
msgstr "Benutzer '%s' hat sich abgemeldet"
#: core/views/user.py:39
msgid "Profile updated"
@@ -554,11 +554,11 @@ msgstr "Verbindung zu Datenbank %s nicht konfiguriert"
#: lib/exceptions.py:67
#, python-format
msgid "Permission denied: %s"
msgstr "Erlaubnis verweigert: %s"
msgstr "Zugriff verweigert: %s"
#: lib/exceptions.py:68
msgid "Permission denied"
msgstr "Berechtigung verweigert"
msgstr "Zugriff verweigert"
#: lib/form_utils.py:88
msgid "Submit"
@@ -599,11 +599,11 @@ msgstr "Entschuldigung"
#: templates/404.html:5
msgid "The requested page does not exist."
msgstr "Die angeforderte Seite existiert nicht"
msgstr "Die angeforderte Seite existiert nicht."
#: templates/500.html:5
msgid "An internal error occured."
msgstr "Ein interne Fehler ist aufgetreten"
msgstr "Ein interner Fehler ist aufgetreten."
#: templates/common/email_searchbar.html:5
msgid "Search..."
@@ -611,7 +611,7 @@ msgstr "Suchen..."
#: templates/common/email_searchbar.html:10
msgid "From address"
msgstr "From:-Adresse"
msgstr "Absender-Adresse"
#: templates/common/email_searchbar.html:17
msgid "Subject"
@@ -634,7 +634,7 @@ msgstr "Hilfe:"
#: templates/common/generic_modal.html:13 templates/common/wizard_forms.html:6
#: templates/common/wizard_forms.html:27
msgid "Close"
msgstr "Schliessen"
msgstr "Schließen"
#: templates/common/wizard_forms.html:31
msgid "Previous"
@@ -650,7 +650,7 @@ msgstr "Willkommen zu Modoboa"
#: templates/registration/login.html:41
msgid "Remember me"
msgstr "Erinnere mich"
msgstr "An mich erinnern"
#: templates/registration/login.html:44
msgid "Log in"
+10 -2
View File
@@ -247,12 +247,20 @@ function parse_qs(raw) {
*
* Ref: http://stackoverflow.com/questions/901115/get-query-string-values-in-javascript
*/
function get_parameter_by_name(url, name) {
function get_parameter_by_name(url, name, dont_replace_plus) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(url);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
if (results === null) {
return "";
}
var result = results[1];
if (dont_replace_plus === undefined || !dont_replace_plus) {
result = result.replace("/\+/g", "");
}
return decodeURIComponent(result);
}
/**
+1 -1
View File
@@ -39,7 +39,7 @@ TopNotifications.prototype = {
data.level + "'>" + data.counter + "</span> " +
data.text;
} else {
content = "<i class='icon-info-sign'></i> " + data.text;
content = "<span class='fa fa-info-circle'></span> " + data.text;
}
return content;
},