Merge branch 'master' into feature/resizable_mailbox_list

This commit is contained in:
Antoine Nguyen
2013-12-10 10:55:52 +01:00
61 changed files with 17147 additions and 10373 deletions
+3 -11
View File
@@ -1,6 +1,6 @@
#######
Modoboa
#######
################################
`Modoboa <http://modoboa.org/>`_
################################
|travis| |latest-version| |downloads|
@@ -40,14 +40,6 @@ Documentation
A detailled `documentation <https://modoboa.readthedocs.org/>`_ will help you
to install, use or extend Modoboa.
******
Issues
******
Modoboa is currently using redmine for project management and issues
tracking. Report issues and/or ask for enhancements on
`dev.modoboa.org <http://dev.modoboa.org/>`_.
*************
External code
*************
+16 -5
View File
@@ -36,16 +36,27 @@ the *Modoboa > Parameters > Limits* page.
Postfix relay domains support
*****************************
This plugin adds the support for relay domains using postfix. This
feature is useful when the MTA managed by Modoboa is not the final
destination of one or many domains.
This plugin adds the support for relay domains using postfix. You can
use it when the MTA managed by Modoboa is not the final destination of
one or several domains.
If activated, two new objects will be available from the *domains*
If activated, two new objects will be available from the *Domains*
listing page: *relay domain* and *relay domain alias*.
This extension is compatible with the *amavis* and *limits*
The extension is compatible with the *amavis* and *limits*
ones. Resellers will be able to create both new objects.
To tell Postfix this feature exists, you must generate two new map
files and then update your configuration.
To generate the map files, run the following command::
$ modoboa-admin.py postfix_maps --categories relaydomains --dbtype <the database you use> <path>
Replace values between ``<>`` by yours.
Edit the :file:`/etc/postfix/main.cf` file.
.. _amavis_frontend:
********************
+4 -2
View File
@@ -53,8 +53,6 @@ Finally, follow the common procedure::
1.1.0: FIXME
============
#. Add ``'modoboa.extensions.postfix_relay_domains'`` to ``MODOBOA_APPS``
Due to code refactoring, some modifications need to be done into
:file:`settings.py`:
@@ -74,6 +72,8 @@ Due to code refactoring, some modifications need to be done into
'modoboa.extensions.sievefilters',
)
#. Add ``'modoboa.extensions.postfix_relay_domains'`` to ``MODOBOA_APPS``
#. ``AUTH_USER_MODEL`` must be set to ``core.User``
#. Into ``LOGGING``, replace ``modoboa.lib.logutils.SQLHandler`` by
@@ -85,6 +85,8 @@ Then, run the following commands to migrate your installation::
$ python manage.py migrate
$ python manage.py collectstatic
Finally, update both :ref:`Dovecot <dovecot_authentication>` and
:ref:`Postfix <postfix>` queries.
1.0.1: operations on mailboxes
==============================
+1
View File
@@ -89,6 +89,7 @@ And add the following line inside::
The result of each order is recorded into Modoboa's log. Go to
*Modoboa > Logs* to consult them.
.. _dovecot_authentication:
Authentication
==============
+1 -1
View File
@@ -37,7 +37,7 @@ base_events = [
"GetExtraParameters",
"ExtraFormFields",
"SaveExtraFormFields"
"SaveExtraFormFields",
]
+2 -1
View File
@@ -322,6 +322,7 @@ class UserSettings(parameters.UserParametersForm):
choices=[("cs", "čeština"), ("de", "deutsch"),
("en", "english"), ("es", "español"),
("fr", "français"), ("it", "italiano"),
("pt", "português"), ("sv", "svenska")],
("pt", "português"), ("ru", "русский"),
("sv", "svenska")],
help_text=ugettext_lazy("Prefered language to display pages")
)
+57 -23
View File
@@ -26,6 +26,43 @@ query = {{ query|safe }}
if not self.tpl_context['dbhost']:
self.tpl_context['dbhost'] = '127.0.0.1'
def __render_map(self, args, mapobject):
"""Render a map file.
:param args: command line arguments
:param mapobject: a ``MapFile`` subclass
"""
content = self.template.render(
Context(dict(
self.tpl_context.items(),
query=getattr(mapobject, args.dbtype)
))
)
with open("%s/%s" % (args.destdir, mapobject.filename), "w") as fp:
print >> fp, """# This file was generated on %s by running:
# %s %s
# DO NOT EDIT!
""" % (datetime.datetime.now().isoformat(),
os.path.basename(sys.argv[0]),
' '.join(sys.argv[1:]))
print >> fp, content
def __render_std_maps(self, args):
"""Render standard map files.
(ie. the ones belonging to the 'std' category)
:param args: command line arguments
"""
for v in globals().values():
if not inspect.isclass(v) or not issubclass(v, MapFile):
continue
if v is MapFile:
continue
if not v.category in args.categories:
continue
self.__render_map(args, v)
def render(self, args):
"""Render all map files.
@@ -35,26 +72,7 @@ query = {{ query|safe }}
os.mkdir(args.destdir)
except OSError:
pass
for v in globals().values():
if not inspect.isclass(v) or not issubclass(v, MapFile):
continue
if v is MapFile:
continue
if not v.category in args.categories:
continue
content = self.template.render(
Context(dict(self.tpl_context.items(),
query=getattr(v, args.dbtype)))
)
with open("%s/%s" % (args.destdir, v.filename), "w") as fp:
print >> fp, """# This file was generated on %s by running:
# %s %s
# DO NOT EDIT!
""" % (datetime.datetime.now().isoformat(),
os.path.basename(sys.argv[0]),
' '.join(sys.argv[1:]))
print >> fp, content
self.__render_std_maps(args)
class SQLiteMapFilesGenerator(MapFilesGenerator):
@@ -88,9 +106,9 @@ class DomainsAliasesMap(MapFile):
class MailboxesMap(MapFile):
filename = 'sql-mailboxes.cf'
mysql = "SELECT 1 FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN admin_user user ON mb.user_id=user.id WHERE dom.enabled=1 AND dom.name='%d' AND user.is_active=1 AND mb.address='%u'"
postgres = "SELECT 1 FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN admin_user u ON mb.user_id=u.id WHERE dom.enabled AND dom.name='%d' AND u.is_active AND mb.address='%u'"
sqlite = "SELECT 1 FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN admin_user user ON mb.user_id=user.id WHERE dom.enabled=1 AND dom.name='%d' AND user.is_active=1 AND mb.address='%u'"
mysql = "SELECT 1 FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user user ON mb.user_id=user.id WHERE dom.enabled=1 AND dom.name='%d' AND user.is_active=1 AND mb.address='%u'"
postgres = "SELECT 1 FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user u ON mb.user_id=u.id WHERE dom.enabled AND dom.name='%d' AND u.is_active AND mb.address='%u'"
sqlite = "SELECT 1 FROM admin_mailbox mb INNER JOIN admin_domain dom ON mb.domain_id=dom.id INNER JOIN core_user user ON mb.user_id=user.id WHERE dom.enabled=1 AND dom.name='%d' AND user.is_active=1 AND mb.address='%u'"
class AliasesMap(MapFile):
@@ -137,6 +155,22 @@ class AutoRepliesMap(MapFile):
sqlite = "SELECT full_address, autoreply_address FROM postfix_autoreply_alias WHERE full_address='%s'"
class RelayDomainsTransportMap(MapFile):
category = "relaydomains"
filename = "sql-relaydomains-transport.cf"
mysql = "SELECT CONCAT(srv.name, ':') FROM postfix_relaydomains_service AS srv INNER JOIN postfix_relaydomains_relaydomain AS rdom ON rdom.service_id=srv.id WHERE rdom.enabled=1 AND rdom.name='%s'"
postgres = "SELECT srv.name || ':' FROM postfix_relaydomains_service AS srv INNER JOIN postfix_relaydomains_relaydomain AS rdom ON rdom.service_id=srv.id WHERE rdom.enabled AND rdom.name='%s'"
sqlite = "SELECT srv.name || ':' FROM postfix_relaydomains_service AS srv INNER JOIN postfix_relaydomains_relaydomain AS rdom ON rdom.service_id=srv.id WHERE rdom.enabled=1 AND rdom.name='%s'"
class RelayDomainAliasesTransportMap(MapFile):
category = "relaydomains"
filename = "sql-relaydomain-aliases-transport.cf"
mysql = "SELECT CONCAT(srv.name, ':') FROM postfix_relaydomains_service AS srv INNER JOIN postfix_relaydomains_relaydomain AS rdom ON rdom.service_id=srv.id INNER JOIN postfix_relaydomains_relaydomainalias AS rdomalias ON rdom.id=rdomalias.target_id WHERE rdom.enabled=1 AND rdomalias.enabled=1 AND rdomalias.name='%s'"
postgres = "SELECT srv.name || ':' FROM postfix_relaydomains_service AS srv INNER JOIN postfix_relaydomains_relaydomain AS rdom ON rdom.service_id=srv.id INNER JOIN postfix_relaydomains_relaydomainalias AS rdomalias ON rdom.id=rdomalias.target_id WHERE rdom.enabled AND rdomalias.enabled AND rdomalias.name='%s'"
sqlite = "SELECT srv.name || ':' FROM postfix_relaydomains_service AS srv INNER JOIN postfix_relaydomains_relaydomain AS rdom ON rdom.service_id=srv.id INNER JOIN postfix_relaydomains_relaydomainalias AS rdomalias ON rdom.id=rdomalias.target_id WHERE rdom.enabled=1 AND rdomalias.enabled=1 AND rdomalias.name='%s'"
class PostfixMapsCommand(Command):
help = "Generate ready-to-use postfix map files"
+1 -1
View File
@@ -55,7 +55,7 @@ class PasswordHasher(object):
:return: True if passwords match, False otherwise
"""
return constant_time_compare(
self._encrypt(clearvalue, hashed_value),
self._b64encode(self._encrypt(clearvalue, hashed_value)),
hashed_value
)
+30 -7
View File
@@ -22,10 +22,20 @@ class AdminParametersForm(parameters.AdminParametersForm):
help_text=ugettext_lazy("The UNIX account who owns mailboxes on the filesystem")
)
default_domain_quota = forms.IntegerField(
label=ugettext_lazy("Default domain quota"),
initial=0,
help_text=ugettext_lazy(
"Default quota (in MB) applied to freshly created domains with no "
"value specified. A value of 0 means no quota."
),
widget=forms.TextInput(attrs={'class': 'span2'})
)
auto_account_removal = YesNoField(
label=ugettext_lazy("Automatic account removal"),
initial="no",
help_text=ugettext_lazy("When a mailbox is removed, also remove the associated account"),
help_text=ugettext_lazy("When a mailbox is removed, also remove the associated account")
)
# Visibility rules
@@ -36,13 +46,26 @@ class AdminParametersForm(parameters.AdminParametersForm):
def __init__(self, *args, **kwargs):
super(AdminParametersForm, self).__init__(*args, **kwargs)
hide_fields = False
try:
code, version = exec_cmd("dovecot --version")
except OSError:
hide_fields = True
else:
if code or not version.strip().startswith("2"):
code, output = exec_cmd("which dovecot")
if not code:
dpath = output.strip()
try:
code, version = exec_cmd("%s --version" % dpath)
except OSError:
hide_fields = True
else:
if code or not version.strip().startswith("2"):
hide_fields = True
else:
hide_fields = True
if hide_fields:
del self.fields["handle_mailboxes"]
del self.fields["mailboxes_owner"]
def clean_default_domain_quota(self):
"""Ensure quota is a positive integer."""
if self.cleaned_data['default_domain_quota'] < 0:
raise forms.ValidationError(
ugettext_lazy('Must be a positive integer')
)
return self.cleaned_data['default_domain_quota']
+16
View File
@@ -162,6 +162,22 @@ class AccountFormMail(forms.Form, DynamicForm):
"""Ensure lower case emails"""
return self.cleaned_data["email"].lower()
def clean(self):
"""Custom fields validation.
Check if quota is >= 0 only when the domain value is not used.
"""
super(AccountFormMail, self).clean()
if self._errors:
raise forms.ValidationError(self._errors)
if not self.cleaned_data["quota_act"] \
and self.cleaned_data['quota'] is not None:
if self.cleaned_data["quota"] < 0:
self._errors["quota"] = self.error_class(
[_("Must be a positive integer")])
del self.cleaned_data["quota"]
return self.cleaned_data
def create_mailbox(self, user, account):
locpart, domname = split_mailbox(self.cleaned_data["email"])
try:
+17 -4
View File
@@ -1,7 +1,7 @@
from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import events
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import Conflict
from modoboa.lib.formutils import (
DomainNameField, YesNoField, DynamicForm, TabForms
@@ -13,6 +13,15 @@ from modoboa.extensions.admin.models import (
class DomainFormGeneral(forms.ModelForm, DynamicForm):
quota = forms.IntegerField(
label=ugettext_lazy("Quota"),
required=False,
help_text=ugettext_lazy(
"Default quota in MB applied to mailboxes. Leave empty to use the "
"default value."
),
widget=forms.TextInput(attrs={"class": "span1"})
)
aliases = DomainNameField(
label=ugettext_lazy("Alias(es)"),
required=False,
@@ -25,9 +34,6 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
class Meta:
model = Domain
fields = ("name", "quota", "aliases", "enabled")
widgets = dict(
quota=forms.widgets.TextInput(attrs={"class": "span1"})
)
def __init__(self, *args, **kwargs):
self.oldname = None
@@ -43,6 +49,13 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
name = "aliases_%d" % (pos + 1)
self._create_field(forms.CharField, name, dalias.name, 3)
def clean_quota(self):
if self.cleaned_data['quota'] is None:
return int(parameters.get_admin('DEFAULT_DOMAIN_QUOTA'))
if self.cleaned_data['quota'] < 0:
raise forms.ValidationError(_("Must be a positive integer"))
return self.cleaned_data['quota']
def clean(self):
"""Custom fields validation.
+7 -3
View File
@@ -117,14 +117,18 @@ class Alias(AdminObject):
if not t in int_rcpts:
self.mboxes.remove(t)
def get_recipients(self):
"""Return the recipients list
def get_recipients(self, with_external=True):
"""Return the recipients list.
Internal and external addresses are mixed into a single list.
:param bool with_external: include external addresses or not
:rtype: list
:return: a list of addresses
"""
result = [al.full_address for al in self.aliases.all()]
result += [mb.full_address for mb in self.mboxes.all()]
if self.extmboxes != "":
if with_external and self.extmboxes != "":
result += self.extmboxes.split(',')
return result
+1 -3
View File
@@ -27,9 +27,7 @@ class Domain(AdminObject):
"""
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
help_text=ugettext_lazy("The domain name"))
quota = models.IntegerField(
help_text=ugettext_lazy("Default quota in MB applied to mailboxes")
)
quota = models.IntegerField()
enabled = models.BooleanField(
ugettext_lazy('enabled'),
help_text=ugettext_lazy("Check to activate this domain")
@@ -114,6 +114,17 @@ class Mailbox(AdminObject):
self.__mail_home = output.strip()
return self.__mail_home
@property
def alias_addresses(self):
"""Return all alias address of this mailbox.
:rtype: list of string
"""
aliases = []
for alias in self.alias_set.all():
aliases += [alias.full_address]
return aliases
def rename_dir(self, old_mail_home):
hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False)
if hm is None or hm == "no":
+2 -1
View File
@@ -85,7 +85,8 @@ class PermissionsTestCase(ModoTestCase):
self.user = User.objects.get(username='user@test.com')
self.values = dict(
username=self.user.username, role="DomainAdmins",
is_active=self.user.is_active, email="user@test.com"
is_active=self.user.is_active, email="user@test.com",
quota_act=True
)
def tearDown(self):
+17
View File
@@ -1,5 +1,6 @@
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin.models import (
Domain, Alias
@@ -66,6 +67,22 @@ class DomainTestCase(ModoTestCase):
self.assertIn(da.mailbox_set.all()[0], al.mboxes.all())
self.assertTrue(da.can_access(al))
def test_create_using_default_quota(self):
parameters.save_admin('DEFAULT_DOMAIN_QUOTA', 50, app='admin')
values = {
"name": "pouet.com", "create_dom_admin": "yes",
"dom_admin_username": "toto", "create_aliases": "yes",
"stepid": 'step2'
}
self.ajax_post(
reverse("modoboa.extensions.admin.views.domain.newdomain"),
values
)
dom = Domain.objects.get(name="pouet.com")
self.assertEqual(dom.quota, 50)
da = User.objects.get(username="toto@pouet.com")
self.assertEqual(da.mailbox_set.all()[0].quota, 50)
def test_modify(self):
"""Test the modification of a domain
+22 -5
View File
@@ -60,11 +60,14 @@ $(document).ready(function() {
interval: {{ interval }},
success_cb: function(data) {
var $link = $("#nbrequests");
var $maincounter = $("#alerts-counter");
if (data.requests > 0) {
$link.html(data.requests + " " + "{{ text }}");
$link.parent().removeClass('hidden');
$maincounter.html(data.requests);
$link.children("span").html(data.requests);
$maincounter.closest('div').removeClass('hidden');
} else {
$link.parent().addClass('hidden');
$maincounter.closest('div').addClass('hidden');
}
}
});
@@ -102,10 +105,24 @@ def display_requests(user):
url = reverse("modoboa.extensions.amavis.views.index")
url += "#listing/?viewrequests=1"
tpl = Template('<div class="btn-group {{ css }}"><a id="nbrequests" href="{{ url }}" class="btn btn-danger">{{ label }}</a></div>')
tpl = Template("""<ul class="nav pull-right {{ css }}">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-white icon-bell"></i> <span id="alerts-counter" class="label label-important">{{ nbrequests }}</span>
</a>
<ul class="dropdown-menu">
<li>
<a id="nbrequests" href="{{ url }}">
<span class="label label-important">{{ nbrequests }}</span> {{ label }}
</a>
</li>
</ul>
</li>
</ul>""")
css = "hidden" if nbrequests == 0 else ""
return [tpl.render(Context(dict(
label=_("%d pending requests" % nbrequests), url=url, css=css
label=_("Pending requests"), url=url, css=css,
nbrequests=nbrequests
)))]
+34
View File
@@ -8,9 +8,11 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from modoboa.lib import parameters
from modoboa.lib.exceptions import InternalError
from modoboa.lib.webutils import NavigationParameters
from modoboa.extensions.amavis.models import Users, Policy
def selfservice(ssfunc=None):
"""Decorator used to expose views to the 'self-service' feature
@@ -79,6 +81,38 @@ recipient=%s
return False
class QuarantineNavigationParameters(NavigationParameters):
"""
Specific NavigationParameters subclass for the quarantine.
"""
def __init__(self, request):
super(QuarantineNavigationParameters, self).__init__(
request, 'quarantine_navparams'
)
self.parameters += [('msgtype', None, False)]
def back_to_listing(self):
"""Return the current listing URL.
Looks into the user's session and the current request to build
the URL.
:return: a string
"""
url = "listing"
params = []
navparams = self.request.session[self.sessionkey]
if "page" in navparams:
params += ["page=%s" % navparams["page"]]
if "order" in navparams:
params += ["sort_order=%s" % navparams["order"]]
params += ["%s=%s" % (p[0], navparams[p[0]])
for p in self.parameters if p[0] in navparams]
if params:
url += "?%s" % ("&".join(params))
return url
def create_user_and_policy(name):
"""Create records.
+105 -66
View File
@@ -9,7 +9,7 @@ from modoboa.lib.webutils import static_url
from modoboa.lib.email_listing import MBconnector, EmailListing
from modoboa.lib.emailutils import Email
from modoboa.lib.dbutils import db_type
from modoboa.extensions.admin.models import Domain
from modoboa.extensions.admin.models import Domain, Alias
from .models import Quarantine, Msgrcpt
@@ -18,30 +18,60 @@ class Qtable(tables.Table):
styles = "table-condensed"
idkey = "mailid"
type = tables.Column(
"type", align="center", width="30px",
selection = tables.SelectionColumn(
"selection", safe=True, width='5px', header=None, sortable=False
)
rstatus = tables.ImgColumn("rstatus", width='25px')
from_ = tables.Column("from", label=ugettext_lazy("From"), limit=30)
subject = tables.Column("subject", label=ugettext_lazy("Subject"), limit=40)
time = tables.Column("date", label=ugettext_lazy("Date"))
to = tables.Column("to", label=ugettext_lazy("To"), sortable=False)
type = tables.Column(
"type", align="center", width="30px", sort_order="type", safe=True
)
rstatus = tables.ImgColumn(
"rstatus", width='25px', sortable=False
)
score = tables.Column(
"score", label=ugettext_lazy("Score"), limit=6,
sort_order="score", cssclass="openable"
)
to = tables.Column(
"to", label=ugettext_lazy("To"), sort_order="to", cssclass="openable"
)
from_ = tables.Column(
"from", label=ugettext_lazy("From"), limit=30,
sort_order="from", cssclass="openable"
)
subject = tables.Column(
"subject", label=ugettext_lazy("Subject"), limit=40,
sort_order="subject", cssclass="openable"
)
time = tables.Column(
"date", label=ugettext_lazy("Date"), sort_order="date",
cssclass="openable"
)
cols_order = [
'selection', 'type', 'rstatus', 'score', 'to', 'from_', 'subject', 'time'
]
cols_order = []
def parse_type(self, value):
color = 'important' if value in ['S', 'V'] else 'warning'
return '<span class="label label-%s">%s</span>' % (color, value)
def parse_date(self, value):
return datetime.fromtimestamp(value)
class SQLconnector(MBconnector):
orders = {
"from": "mail__from_addr",
order_translation_table = {
"type": "mail__msgrcpt__content",
"score": "mail__msgrcpt__bspam_level",
"date": "mail__time_num",
"subject": "mail__subject",
"date": "mail__time_num"
"from": "mail__from_addr",
"to": "mail__msgrcpt__rid__email"
}
def __init__(self, mail_ids=None, filter=None):
self.count = None
self.messages = None
self.mail_ids = mail_ids
self.filter = filter
@@ -52,35 +82,46 @@ class SQLconnector(MBconnector):
filter &= Q(mail__in=self.mail_ids)
if self.filter:
filter &= self.filter
self.messages = Quarantine.objects.filter(filter)
self.messages = Quarantine.objects.filter(filter).values(
"mail__from_addr",
"mail__msgrcpt__rid__email",
"mail__subject",
"mail__mail_id",
"mail__time_num",
"mail__msgrcpt__content",
"mail__msgrcpt__bspam_level",
"mail__msgrcpt__rs"
)
if "order" in kwargs:
totranslate = kwargs["order"][1:]
sign = kwargs["order"][:1]
if sign == " ":
sign = ""
order = sign + self.orders[totranslate]
self.messages = self.messages.order_by(order)
self.count = self.messages.count()
order = kwargs["order"]
sign = ""
if order[0] == "-":
sign = "-"
order = order[1:]
order = self.order_translation_table[order]
self.messages = self.messages.order_by(sign + order)
self.count = len(self.messages)
return self.count
def fetch(self, start=None, stop=None, **kwargs):
messages = self.messages[start - 1:stop]
emails = []
for qm in messages:
for rcpt in qm.mail.msgrcpt_set.all():
m = {"from": qm.mail.from_addr,
"to": rcpt.rid.email,
"subject": qm.mail.subject,
"mailid": qm.mail_id,
"date": qm.mail.time_num,
"type": rcpt.content}
if rcpt.rs == '':
m["class"] = "unseen"
elif rcpt.rs == 'R':
m["img_rstatus"] = static_url("pics/release.png")
elif rcpt.rs == 'p':
m["class"] = "pending"
emails.append(m)
for qm in self.messages[start - 1:stop]:
m = {"from": qm["mail__from_addr"],
"to": qm["mail__msgrcpt__rid__email"],
"subject": qm["mail__subject"],
"mailid": qm["mail__mail_id"],
"date": qm["mail__time_num"],
"type": qm["mail__msgrcpt__content"],
"score": qm["mail__msgrcpt__bspam_level"]}
rs = qm["mail__msgrcpt__rs"]
if rs == '':
m["class"] = "unseen"
elif rs == 'R':
m["img_rstatus"] = static_url("pics/release.png")
elif rs == 'p':
m["class"] = "pending"
emails.append(m)
return emails
@@ -98,22 +139,25 @@ class SQLWrapper(object):
"""
def get_mails(self, request, rcptfilter=None):
if request.GET.get("viewrequests", None) == "1":
q = Q(rs='p')
else:
q = ~Q(rs='D')
"""Retrieve all messages visible by a user.
Simple users can only see the messages
:rtype: QuerySet
"""
q = Q(rs='p') \
if request.GET.get("viewrequests", None) == "1" else ~Q(rs='D')
if request.user.group == 'SimpleUsers':
q &= Q(rid__email=request.user.email)
else:
if not request.user.is_superuser:
doms = Domain.objects.get_for_admin(request.user)
regexp = "(%s)" % '|'.join([dom.name for dom in doms])
doms_q = Q(rid__email__regex=regexp)
q &= doms_q
if rcptfilter is not None:
q &= Q(rid__email__contains=rcptfilter)
rcpts = [request.user.email] \
+ request.user.mailbox_set.all()[0].alias_addresses
q &= Q(rid__email__in=rcpts)
elif not request.user.is_superuser:
doms = Domain.objects.get_for_admin(request.user)
regexp = "(%s)" % '|'.join([dom.name for dom in doms])
doms_q = Q(rid__email__regex=regexp)
q &= doms_q
if rcptfilter is not None:
q &= Q(rid__email__contains=rcptfilter)
return Msgrcpt.objects.filter(q).values("mail_id")
def get_recipient_message(self, address, mailid):
@@ -153,24 +197,23 @@ class PgWrapper(SQLWrapper):
"""
def get_mails(self, request, rcptfilter=None):
if request.GET.get("viewrequests", None) == "1":
q = Q(rs='p')
else:
q = ~Q(rs='D')
q = Q(rs='p') \
if request.GET.get("viewrequests", None) == "1" else ~Q(rs='D')
where = ["U0.rid=maddr.id"]
if request.user.group == 'SimpleUsers':
where.append("convert_from(maddr.email, 'UTF8') = '%s'" % request.user.email)
return Msgrcpt.objects.filter(q).extra(
where=where, tables=['maddr']
)
if not request.user.is_superuser:
rcpts = [request.user.email] \
+ request.user.mailbox_set.all()[0].alias_addresses
where.append("convert_from(maddr.email, 'UTF8') IN (%s)" \
% (','.join(["'%s'" % rcpt for rcpt in rcpts])))
elif not request.user.is_superuser:
doms = Domain.objects.get_for_admin(request.user)
regexp = "(%s)" % '|'.join([dom.name for dom in doms])
where.append("convert_from(maddr.email, 'UTF8') ~ '%s'" % regexp)
if rcptfilter is not None:
where.append("convert_from(maddr.email, 'UTF8') LIKE '%%%s%%'" % rcptfilter)
return Msgrcpt.objects.filter(q).extra(where=where, tables=['maddr']).values("mail_id")
where.append("convert_from(maddr.email, 'UTF8') LIKE '%%%s%%'"
% rcptfilter)
return Msgrcpt.objects.filter(q)\
.extra(where=where, tables=['maddr']).values("mail_id")
def get_recipient_message(self, address, mailid):
qset = Msgrcpt.objects.filter(mail=mailid).extra(
@@ -229,10 +272,6 @@ class SQLlisting(EmailListing):
reset_wm_url = True
def __init__(self, user, msgs, filter, **kwargs):
if user.group == 'SimpleUsers':
Qtable.cols_order = ['type', 'rstatus', 'from_', 'subject', 'time']
else:
Qtable.cols_order = ['type', 'rstatus', 'to', 'from_', 'subject', 'time']
self.mbc = SQLconnector(msgs, filter)
super(SQLlisting, self).__init__(**kwargs)
self.show_listing_headers = True
@@ -8,6 +8,10 @@
z-index: 10;
}
#menubar input[type="checkbox"] {
margin: 0;
}
#listing {
position: fixed;
padding: 0 15px;
@@ -27,15 +31,32 @@
color: #0088cc;
}
#filters_bar {
/*padding-top: 5px;*/
padding-left: 15px;
}
thead tr {
position: relative;
z-index: 1;
}
td > input[type=checkbox] {
margin: 0;
}
td[name=type] span {
cursor: pointer;
}
td.unseen {
font-weight: bold;
}
td.pending {
td[class*=pending] {
font-style: italic;
}
td[class*=openable] {
cursor: pointer;
}
@@ -1,21 +1,42 @@
var Quarantine = function(options) {
this.initialize(options);
Listing.call(this, options);
};
Quarantine.prototype = {
constructor: Quarantine,
defaults: {
deflocation: "listing",
defcallback: "listing_cb"
deflocation: "listing"
},
initialize: function(options) {
this.options = $.extend({}, this.defaults, options);
Listing.prototype.initialize.call(this, options);
this.options = $.extend({}, this.defaults, this.options);
this.options.defcallback = $.proxy(this.listing_cb, this);
this.navobj = new History(this.options);
this.register_navcallbacks();
this.listen();
this.set_msgtype();
$(document).on('click', '#selectall', this.toggle_selection);
$("#searchfield").searchbar({navobj: this.navobj});
},
/*
* Activate the button corresponding to the current message type
* filter. If no message type is found, the 'All' button is
* selected.
*/
set_msgtype: function() {
var msgtype = this.navobj.getparam('msgtype');
$("button[name*=msgtype]").removeClass('active');
if (msgtype != undefined) {
$("button[name=msgtype_" + msgtype + "]").addClass('active');
} else {
$("button[name=msgtype_all]").addClass('active');
}
},
update_page: function(data) {
@@ -23,9 +44,6 @@ Quarantine.prototype = {
$("#menubar").html(data.menu);
$("#searchfield").searchbar({navobj: this.navobj});
}
if (data.navbar) {
$("#bottom-bar-right").html(data.navbar);
}
if (data.listing != undefined) {
$("#listing").html(data.listing);
$("#listing").css({
@@ -34,10 +52,11 @@ Quarantine.prototype = {
overflow: "auto"
});
}
this.update_listing(data);
},
listen: function() {
$(document).on("dblclick", "tbody>tr", $.proxy(this.viewmail_loader, this));
$(document).on("click", "td[class*=openable]", $.proxy(this.viewmail_loader, this));
$(document).on("click", "a[name=selectmsgs]", $.proxy(this.selectmsgs, this));
$(document).on("click", "a[name=release-multi]",
$.proxy(this.release_selection, this));
@@ -45,19 +64,16 @@ Quarantine.prototype = {
$.proxy(this.delete_selection, this));
$(document).on("click", "a[name=viewrequests]",
$.proxy(this.view_requests, this));
$(document).on("click", "#bottom-bar a", $.proxy(this.load_page, this));
$(document).on("click", "a[name=release]", $.proxy(this.release, this));
$(document).on("click", "a[name=delete]", $.proxy(this.delete, this));
$(document).on("click", "a[name=headers]", $.proxy(this.headers, this));
$(document).on("click", "td[name=type] span", $.proxy(this.filter_by_type, this));
$(document).on("click", "#filters button", $.proxy(this.filter_by_type, this));
},
load_page: function(e) {
e.preventDefault();
var $link = $(e.target).parent();
this.navobj.delparam("rcpt");
this.navobj.parse_string($link.attr("href")).update();
Listing.prototype.load_page.apply(this, arguments);
this.navobj.delparam("rcpt").update();
},
view_requests: function(e) {
@@ -77,20 +93,59 @@ Quarantine.prototype = {
this.navobj.update();
},
selectmsgs: function(e) {
e.preventDefault();
var type = $(e.target).attr("href");
if (type == "") {
/*
* Toggle message selection when the top checkbox's state is
* modified.
*
* If it is checked, all messages are selected. Otherwise, the
* current selection is resetted.
*/
toggle_selection: function(e) {
if (!$('#selectall').prop('checked')) {
$("#emails").htmltable("clear_selection");
return;
}
$("td[name*=selection]").each(function() {
var $input = $(this).children('input');
$input.prop('checked', true);
$('#emails').htmltable('select_row', $(this).parent());
});
},
/*
* Select all messages of a specific type.
*/
selectmsgs: function(e) {
e.preventDefault();
var type = get_target(e, 'a').attr("href");
var counter = 0;
$("td[name=type]").each(function() {
var $this = $(this);
if ($this.html().trim() == type) {
$("#emails").htmltable("select_row", $this.parent());
if ($this.find('span').html().trim() == type) {
var $input = $this.parent().children('td[name=selection]').children('input');
$input.prop('checked', true);
$('#emails').htmltable('select_row', $this.parent());
counter++;
}
});
if (counter) {
$("#selectall").prop('checked', true);
}
},
/*
* Filter listing by message type (spam, virus, etc.)
*/
filter_by_type: function(evt) {
var msgtype = $(evt.target).html().trim();
if (msgtype != 'All') {
this.navobj.setparam('msgtype', msgtype);
} else {
this.navobj.delparam('msgtype');
}
this.navobj.update();
},
_send_selection: function(e, name, message) {
@@ -138,7 +193,9 @@ Quarantine.prototype = {
}
$.ajax({
url: $link.attr("href"),
dataType: 'json'
dataType: 'json',
type: 'POST',
data: {rcpt: get_parameter_by_name($link.attr("href"), 'rcpt')}
}).done($.proxy(this.action_cb, this));
},
@@ -196,10 +253,30 @@ Quarantine.prototype = {
$.proxy(this.viewmail_cb, this));
},
activate_buttons: function($tr) {
$("a[name=release-multi]").removeClass('disabled');
$("a[name=delete-multi]").removeClass('disabled');
$("#selectall").prop('checked', true);
},
deactivate_buttons: function($tr) {
if (!this.htmltable || !this.htmltable.current_selection().length) {
$("a[name=release-multi]").addClass('disabled');
$("a[name=delete-multi]").addClass('disabled');
$("#selectall").prop('checked', false);
}
},
listing_cb: function(data) {
this.update_page(data);
$("#emails").htmltable();
this.navobj.delparam("rcpt").update();
this.set_msgtype();
$("#emails").htmltable({
tr_selected_event: this.activate_buttons,
tr_unselected_event: $.proxy(this.deactivate_buttons, this)
});
this.htmltable = $("#emails").data("htmltable");
this.deactivate_buttons();
},
viewmail_cb: function(data) {
@@ -215,4 +292,6 @@ Quarantine.prototype = {
$("body").notify("error", data.respmsg);
}
}
};
};
Quarantine.prototype = $.extend({}, Listing.prototype, Quarantine.prototype);
@@ -1,10 +1,13 @@
{% extends "fixed.html" %}{% load i18n %}
{% extends "fixed.html" %}{% load i18n amavis_tags %}
{% block pagetitle %}{% trans "Quarantine management" %}{% endblock %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}amavis/css/quarantine.css" />
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/jquery.sortable.css" />
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.htmltable.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.sortable.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/history.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/searchbar.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/listing.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}amavis/js/quarantine.js"></script>
<script type="text/javascript">
$(document).ready(function() {
@@ -16,6 +19,6 @@ $(document).ready(function() {
</script>
{% endblock %}
{% block fixed_content %}
<div id="menubar"></div>
<div id="menubar">{% quar_menu %}</div>
<div id="listing"></div>
{% endblock %}
@@ -0,0 +1,38 @@
{% load lib_tags i18n %}
<div class="btn-toolbar pull-left">
<div class="btn-group" title="{% trans 'Select' %}">
<a class="btn"><input type="checkbox" id="selectall" /></a>
<button class="btn dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a name="selectmsgs" href="S">{% trans "Spam" %}</a></li>
<li><a name="selectmsgs" href="V">{% trans "Virus" %}</a></li>
<li><a name="selectmsgs" href="H">{% trans "Bad Header" %}</a></li>
<li><a name="selectmsgs" href="M">{% trans "Bad MIME" %}</a></li>
<li><a name="selectmsgs" href="B">{% trans "Banned" %}</a></li>
</ul>
</div>
<div class="btn-group">
<a name="release-multi" href="{% url 'modoboa.extensions.amavis.views.process' %}" class="btn btn-success disabled">
<i class="icon-white icon-ok"></i> {% trans "Release" %}
</a>
</div>
<div class="btn-group">
<a name="delete-multi" href="{% url 'modoboa.extensions.amavis.views.process' %}" class="btn btn-danger disabled">
<i class="icon-white icon-trash"></i> {% trans "Delete" %}
</a>
</div>
{% include "common/email_searchbar.html" with extraopts=extraopts %}
</div>
<div id="filters_bar" class="btn-toolbar pull-right">
<span class="label label-title">{% trans "Display" %}</span>
<div id="filters" class="btn-group" data-toggle="buttons-radio">
<button name="msgtype_all" class="btn" title="{% trans 'All messages' %}">{% trans "All" %}</button>
<button name="msgtype_S" class="btn btn-danger" title="{% trans 'Spam only' %}">S</button>
<button name="msgtype_V" class="btn btn-danger" title="{% trans 'Virus only' %}">V</button>
<button name="msgtype_H" class="btn btn-warning" title="{% trans 'Bad Header only' %}">H</button>
<button name="msgtype_M" class="btn btn-warning" title="{% trans 'Bad MIME only' %}">M</button>
<button name="msgtype_B" class="btn btn-warning" title="{% trans 'Banned only' %}">B</button>
</div>
</div>
@@ -3,12 +3,13 @@ from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.conf import settings
from modoboa.lib.webutils import _render_to_string
register = template.Library()
@register.simple_tag
def viewm_menu(user, mail_id, rcpt):
def viewm_menu(mail_id, rcpt):
entries = [
{"name": "release",
"img": "icon-white icon-ok",
@@ -65,49 +66,13 @@ def viewm_menu_simple(user, mail_id, rcpt, secret_id=""):
@register.simple_tag
def quar_menu(user):
entries = [
{"name": "release-multi",
"url": reverse('modoboa.extensions.amavis.views.process'),
"img": "icon-white icon-ok",
"class": "btn-success",
"label": _("Release")},
{"name": "delete-multi",
"img": "icon-white icon-trash",
"class": "btn-danger",
"url": reverse('modoboa.extensions.amavis.views.process'),
"label": _("Delete")},
{"name": "select",
"url": "",
"label": _("Content Class"),
"menu": [
{"name": "selectmsgs",
"url": "",
"label": _("Nothing")},
{"name": "selectmsgs",
"url": "S",
"label": _("Spam")},
{"name": "selectmsgs",
"url": "V",
"label": _("Virus")},
{"name": "selectmsgs",
"url": "H",
"label": _("Bad header")},
{"name": "selectmsgs",
"url": "M",
"label": _("Bad MIME")}
]}
]
def quar_menu():
"""Render the quarantine listing menu.
if user.group != 'SimpleUsers':
extraopts = [{"name": "to", "label": _("To")}]
else:
extraopts = []
extracontent = render_to_string('common/email_searchbar.html', {
"STATIC_URL": settings.STATIC_URL,
"extraopts": extraopts
:rtype: str
:return: resulting HTML
"""
extraopts = [{"name": "to", "label": _("To")}]
return render_to_string('amavis/main_action_bar.html', {
'extraopts': extraopts
})
return render_to_string('common/buttons_list.html', dict(
entries=entries, extracontent=extracontent
))
+87 -103
View File
@@ -13,41 +13,19 @@ from modoboa.lib.exceptions import BadRequest
from modoboa.lib.webutils import (
getctx, ajax_response, render_to_json_response
)
from modoboa.lib.email_listing import parse_search_parameters
from modoboa.lib.templatetags.lib_tags import pagination_bar
from modoboa.extensions.admin.models import Mailbox, Domain
from modoboa.extensions.amavis.templatetags.amavis_tags import (
quar_menu, viewm_menu
)
from .lib import selfservice, AMrelease
from .lib import selfservice, AMrelease, QuarantineNavigationParameters
from .sql_listing import SQLlisting, SQLemail, get_wrapper
from .models import Msgrcpt
def __back_to_listing(request):
"""Return the current listing URL.
Looks into the user's session and the current request to build the
URL.
:param request: a ``Request`` object
:return: a string
"""
url = "listing"
params = []
if "page" in request.session:
params += ["page=%s" % request.session["page"]]
params += ["%s=%s" % (p, request.session[p])
for p in ["criteria", "pattern"] if p in request.session]
if params:
url += "?%s" % ("&".join(params))
return url
def empty_quarantine(request):
content = "<div class='alert alert-info'>%s</div>" % _("Empty quarantine")
ctx = getctx("ok", level=2, listing=content, navbar="",
menu=quar_menu(request.user))
ctx = getctx("ok", level=2, listing=content)
return render_to_json_response(ctx)
@@ -61,58 +39,56 @@ def _listing(request):
if not Domain.objects.get_for_admin(request.user).count():
return empty_quarantine(request)
order = request.GET.get("order", "-date")
if not "navparams" in request.session:
request.session["navparams"] = {}
request.session["navparams"]["order"] = order
parse_search_parameters(request)
if "pattern" in request.session:
criteria = request.session["criteria"]
navparams = QuarantineNavigationParameters(request)
navparams.store()
pattern = navparams.get('pattern', '')
if pattern:
criteria = navparams.get('criteria')
if criteria == "both":
criteria = "from_addr,subject,to"
for c in criteria.split(","):
if c == "from_addr":
nfilter = Q(mail__from_addr__contains=request.session["pattern"])
nfilter = Q(mail__from_addr__contains=pattern)
elif c == "subject":
nfilter = Q(mail__subject__contains=request.session["pattern"])
nfilter = Q(mail__subject__contains=pattern)
elif c == "to":
rcptfilter = request.session["pattern"]
rcptfilter = pattern
continue
else:
raise BadRequest("unsupported search criteria %s" % c)
flt = nfilter if flt is None else flt | nfilter
msgtype = navparams.get('msgtype', None)
if msgtype is not None:
nfilter = Q(mail__msgrcpt__content=msgtype)
flt = flt | nfilter if flt is not None else nfilter
msgs = get_wrapper().get_mails(request, rcptfilter)
if "page" in request.GET:
request.session["page"] = request.GET["page"]
pageid = int(request.session["page"])
else:
if "page" in request.session:
del request.session["page"]
pageid = 1
page = navparams.get('page')
lst = SQLlisting(
request.user, msgs, flt,
navparams=request.session["navparams"],
navparams=request.session["quarantine_navparams"],
elems_per_page=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE"))
)
page = lst.paginator.getpage(pageid)
page = lst.paginator.getpage(page)
if not page:
return empty_quarantine(request)
content = lst.fetch(request, page.id_start, page.id_stop)
navbar = lst.render_navbar(page, "listing/?")
ctx = getctx("ok", listing=content, navbar=navbar,
menu=quar_menu(request.user))
ctx = getctx(
"ok", listing=content, paginbar=pagination_bar(page), page=page.number
)
if request.session.get('location', 'listing') != 'listing':
ctx['menu'] = quar_menu()
request.session['location'] = 'listing'
return render_to_json_response(ctx)
@login_required
def index(request):
return render(request, "amavis/index.html", dict(
deflocation="listing/", defcallback="listing_cb", selection="quarantine"
deflocation="listing/?sort_order=-date", defcallback="listing_cb",
selection="quarantine"
))
@@ -160,23 +136,20 @@ def viewmail_selfservice(request, mail_id,
@selfservice(viewmail_selfservice)
def viewmail(request, mail_id):
if request.user.group != 'SimpleUsers':
rcpt = request.GET["rcpt"]
else:
rcpt = None
rcpt = request.GET["rcpt"]
if request.user.mailbox_set.count():
mb = Mailbox.objects.get(user=request.user)
if not rcpt or rcpt == mb.full_address:
msgrcpt = get_wrapper().get_recipient_message(mb.full_address, mail_id)
if rcpt in mb.alias_addresses:
msgrcpt = get_wrapper().get_recipient_message(rcpt, mail_id)
msgrcpt.rs = 'V'
msgrcpt.save()
content = Template("""
<iframe src="{{ url }}" id="mailcontent"></iframe>
""").render(Context({"url": reverse(getmailcontent, args=[mail_id])}))
menu = viewm_menu(request.user, mail_id, rcpt)
menu = viewm_menu(mail_id, rcpt)
ctx = getctx("ok", menu=menu, listing=content)
request.session['location'] = 'viewmail'
return render_to_json_response(ctx)
@@ -193,8 +166,8 @@ def viewheaders(request, mail_id):
def check_mail_id(request, mail_id):
if type(mail_id) in [str, unicode]:
if "rcpt" in request.GET:
mail_id = ["%s %s" % (request.GET["rcpt"], mail_id)]
if "rcpt" in request.POST:
mail_id = ["%s %s" % (request.POST["rcpt"], mail_id)]
else:
mail_id = [mail_id]
return mail_id
@@ -215,27 +188,29 @@ def delete_selfservice(request, mail_id):
@selfservice(delete_selfservice)
def delete(request, mail_id):
mail_id = check_mail_id(request, mail_id)
if request.user.group == 'SimpleUsers':
mb = Mailbox.objects.get(user=request.user)
msgrcpts = get_wrapper().get_recipient_messages(mb.full_address, mail_id)
#msgrcpts.update(rs='D')
for msgrcpt in msgrcpts:
msgrcpt.rs = 'D'
msgrcpt.save()
else:
wrapper = get_wrapper()
for mid in mail_id:
r, i = mid.split()
msgrcpt = wrapper.get_recipient_message(r, i)
msgrcpt.rs = 'D'
msgrcpt.save()
"""Delete message selection.
:param str mail_id: message unique identifier
"""
mail_id = check_mail_id(request, mail_id)
wrapper = get_wrapper()
mb = Mailbox.objects.get(user=request.user) \
if request.user.group == 'SimpleUsers' else None
for mid in mail_id:
r, i = mid.split()
if mb is not None and r != mb.full_address \
and not r in mb.alias_addresses:
continue
msgrcpt = wrapper.get_recipient_message(r, i)
msgrcpt.rs = 'D'
msgrcpt.save()
message = ungettext("%(count)d message deleted successfully",
"%(count)d messages deleted successfully",
len(mail_id)) % {"count": len(mail_id)}
return ajax_response(request, respmsg=message,
url=__back_to_listing(request))
return ajax_response(
request, respmsg=message,
url=QuarantineNavigationParameters(request).back_to_listing()
)
def release_selfservice(request, mail_id):
@@ -266,30 +241,37 @@ def release_selfservice(request, mail_id):
@selfservice(release_selfservice)
def release(request, mail_id):
"""Release message selection.
:param str mail_id: message unique identifier
"""
mail_id = check_mail_id(request, mail_id)
if request.user.group == 'SimpleUsers':
mb = Mailbox.objects.get(user=request.user)
msgrcpts = get_wrapper().get_recipient_messages(mb.full_address, mail_id)
if parameters.get_admin("USER_CAN_RELEASE") == "no":
# FIXME : can't use this syntax because extra SQL (using
# .extra() for postgres) is not propagated (the 'tables'
# parameter is lost somewhere...)
#
# msgrcpts.update(rs='p')
for msgrcpt in msgrcpts:
msgrcpt.rs = 'p'
msgrcpt.save()
message = ungettext("%(count)d request sent",
"%(count)d requests sent",
len(mail_id)) % {"count": len(mail_id)}
return ajax_response(request, "ok", respmsg=message,
url=__back_to_listing(request))
else:
msgrcpts = []
wrapper = get_wrapper()
for mid in mail_id:
r, i = mid.split()
msgrcpts += [wrapper.get_recipient_message(r, i)]
msgrcpts = []
wrapper = get_wrapper()
mb = Mailbox.objects.get(user=request.user) \
if request.user.group == 'SimpleUsers' else None
for mid in mail_id:
r, i = mid.split()
if mb is not None and r != mb.full_address \
and not r in mb.alias_addresses:
continue
msgrcpts += [wrapper.get_recipient_message(r, i)]
if mb is not None and parameters.get_admin("USER_CAN_RELEASE") == "no":
# FIXME : can't use this syntax because extra SQL (using
# .extra() for postgres) is not propagated (the 'tables'
# parameter is lost somewhere...)
#
# msgrcpts.update(rs='p')
for msgrcpt in msgrcpts:
msgrcpt.rs = 'p'
msgrcpt.save()
message = ungettext("%(count)d request sent",
"%(count)d requests sent",
len(mail_id)) % {"count": len(mail_id)}
return ajax_response(
request, "ok", respmsg=message,
url=QuarantineNavigationParameters(request).back_to_listing()
)
amr = AMrelease()
error = None
@@ -308,8 +290,10 @@ def release(request, mail_id):
len(mail_id)) % {"count": len(mail_id)}
else:
message = error
return ajax_response(request, "ko" if error else "ok", respmsg=message,
url=__back_to_listing(request))
return ajax_response(
request, "ko" if error else "ok", respmsg=message,
url=QuarantineNavigationParameters(request).back_to_listing()
)
@login_required
@@ -33,7 +33,7 @@ Best regards,
%(name)s
"""),
help_text=ugettext_lazy(
"Default content user when an auto-reply message is created "
"Default content used when an auto-reply message is created "
"automatically. The '%(name)s' macro will be replaced by the "
"user's full name."
),
@@ -86,6 +86,8 @@ def onMailboxDeleted(mailboxes):
@events.observe("MailboxModified")
def onMailboxModified(mailbox):
if not hasattr(mailbox, 'old_full_address'):
return
if mailbox.full_address == mailbox.old_full_address:
return
alias = Alias.objects.get(full_address=mailbox.old_full_address)
@@ -47,10 +47,12 @@ $(document).ready(function() {
<form class="form-inline">
<span id="custom-period">
<div class="input-prepend">
<span class="add-on"><i class="icon-indent-left"></i></span><input id="id_from" class="span10" name="date_from" value="{{ start }}" type="text" />
<span class="add-on"><i class="icon-indent-left"></i></span>
<input id="id_from" class="span10" name="date_from" value="{{ start }}" type="text" readonly="readonly" />
</div>
<div class="input-prepend">
<span class="add-on"><i class="icon-indent-right"></i></span><input id="id_to" class="span10" name="date_to" value="{{ end }}" type="text" />
<span class="add-on"><i class="icon-indent-right"></i></span>
<input id="id_to" class="span10" name="date_to" value="{{ end }}" type="text" readonly="readonly" />
</div>
<button class="btn" id="customsend">{% trans "Apply" %}</button>
</span>
+32 -23
View File
@@ -10,8 +10,8 @@ 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, parameters
from modoboa.lib.webutils import size2integer, NavigationParameters
from modoboa.lib.exceptions import InternalError
from modoboa.lib.webutils import size2integer
from modoboa.lib.email_listing import EmailListing
from modoboa.lib.emailutils import (
EmailAddress, Email, prepare_addresses, set_email_headers
@@ -26,22 +26,29 @@ class WMtable(tables.Table):
tableid = "emails"
styles = "table-condensed"
idkey = "imapid"
select = tables.ImgColumn(
"select", cssclass="draggable left", width="2%",
drag = tables.ImgColumn(
"drag", cssclass="draggable left", width="1%",
defvalue="%spics/grippy.png" % settings.STATIC_URL,
header="<input type='checkbox' name='toggleselect' id='toggleselect' />"
)
flags = tables.ImgColumn("flags", width="4%")
selection = tables.SelectionColumn(
"selection", safe=True, width='1%', header=None, sortable=False
)
flags = tables.ImgColumn("flags", width="3%")
withatts = tables.ImgColumn("withatts", width="2%")
subject = tables.Column(
"subject", label=ugettext_lazy("Subject"), width="50%", limit=60
"subject", label=ugettext_lazy("Subject"), width="50%", limit=60,
cssclass="openable"
)
from_ = tables.Column(
"from", width="20%", label=ugettext_lazy("From"), limit=30
"from", width="20%", label=ugettext_lazy("From"), limit=30,
cssclass="openable"
)
date = tables.Column(
"date", width="15%", label=ugettext_lazy("Date"), cssclass="openable"
)
date = tables.Column("date", width="15%", label=ugettext_lazy("Date"))
cols_order = ["select", "withatts", "flags", "subject", "from_", "date"]
cols_order = ["drag", "selection", "withatts", "flags", "subject", "from_", "date"]
def parse(self, header, value):
if value is None:
@@ -143,9 +150,10 @@ class ImapListing(EmailListing):
def __init__(self, user, password, **kwargs):
self.user = user
self.mbc = IMAPconnector(user=user.username, password=password)
if "pattern" in kwargs:
self.parse_search_parameters(kwargs["criteria"],
kwargs["pattern"])
if "pattern" in kwargs and kwargs["pattern"]:
self.parse_search_parameters(
kwargs["criteria"], kwargs["pattern"]
)
else:
self.mbc.criterions = []
@@ -605,17 +613,6 @@ def html2plaintext(content):
return plaintext
def get_current_url(request):
if not "folder" in request.session:
return ""
res = "%s?page=%s" % (request.session["folder"], request.session["page"])
for p in ["criteria", "pattern", "order"]:
if p in request.session.keys():
res += "&%s=%s" % (p, request.session[p])
return res
def create_mail_attachment(attdef):
"""Create the MIME part corresponding to the given attachment.
@@ -726,7 +723,7 @@ def send_mail(request, posturl=None):
password=request.session["password"]).push_mail(sentfolder, msg)
clean_attachments(request.session["compose_mail"]["attachments"])
del request.session["compose_mail"]
return True, dict(url=get_current_url(request))
return True, {}
listing = _render_to_string(request, "webmail/compose.html",
{"form": form, "noerrors": True,
@@ -756,3 +753,15 @@ class AttachmentUploadHandler(FileUploadHandler):
def file_complete(self, file_size):
return None
class WebmailNavigationParameters(NavigationParameters):
"""
Specific NavigationParameters subclass for the webmail.
"""
def __init__(self, request, defmailbox=None):
super(WebmailNavigationParameters, self).__init__(
request, 'webmail_navparams'
)
if defmailbox is not None:
self.parameters += [('mbox', defmailbox, False)]
@@ -228,4 +228,15 @@ div.expanded {
#folders2 li {
position: relative;
}
/*
* Table
*/
td > input[type=checkbox] {
margin: 0;
}
td[class*=openable] {
cursor: pointer;
}
@@ -76,7 +76,6 @@ Webmail.prototype = {
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() {
@@ -96,7 +95,7 @@ Webmail.prototype = {
$(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", "td[class*=openable]", $.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));
@@ -871,13 +870,11 @@ Webmail.prototype = {
},
delete_callback: function(data) {
var msg = (data.respmsg) ? data.respmsg : gettext("Message deleted");
this.go_back_to_listing();
if (this.get_current_mailbox() != this.options.trash) {
$("a[name=totrash]").removeClass("disabled");
}
$("body").notify("success", msg, 2000);
$("body").notify("success", data, 2000);
},
/*
@@ -1009,14 +1006,16 @@ Webmail.prototype = {
init_draggables: function() {
var plug = this;
$("td[name=select]").draggable({
$("td[name=drag]").draggable({
opacity: 0.8,
helper: function(e) {
var $this = $(this);
var $tr = $this.parent();
if (!plug.htmltable.is_selected($tr)) {
var $input = $tr.find('#selection');
plug.htmltable.select_row($tr);
$input.prop('checked', true);
}
var nmsgs = plug.htmltable.current_selection().length;
@@ -0,0 +1,3 @@
{% extends "common/buttons_list.html" %}{% load i18n %}
{% block extracontent %}{% include "common/email_searchbar.html" with extraopts=extraopts %}
{% endblock %}
@@ -114,13 +114,10 @@ def listmailbox_menu(selection, folder, user):
"label": _("Empty folder"),
"url": reverse(webmail.views.empty, args=[folder])}
]
searchbar = render_to_string('common/email_searchbar.html', {
"STATIC_URL": settings.STATIC_URL
return render_to_string('webmail/main_action_bar.html', {
'selection': selection, 'entries': entries, 'user': user, 'css': "nav",
'STATIC_URL': settings.STATIC_URL
})
return render_to_string('common/buttons_list.html', dict(
selection=selection, entries=entries, extracontent=searchbar,
user=user, css="nav"
))
@register.simple_tag
+13 -35
View File
@@ -23,7 +23,7 @@ from .lib import (
decode_payload, AttachmentUploadHandler,
save_attachment, ImapListing, EmailSignature,
clean_attachments, set_compose_session, send_mail,
ImapEmail, ReplyModifier, ForwardModifier
ImapEmail, WebmailNavigationParameters, ReplyModifier, ForwardModifier
)
from templatetags import webmail_tags
@@ -64,8 +64,9 @@ def move(request):
if not arg in request.GET:
raise BadRequest(_("Invalid request"))
mbc = get_imapconnector(request)
mbc.move(request.GET["msgset"], request.session["mbox"], request.GET["to"])
resp = listmailbox(request, request.session["mbox"], update_session=False)
navparams = WebmailNavigationParameters(request)
mbc.move(request.GET["msgset"], navparams.get('mbox'), request.GET["to"])
resp = listmailbox(request, navparams.get('mbox'), update_session=False)
return render_to_json_response(resp)
@@ -189,8 +190,7 @@ def editfolder(request, tplname="webmail/folder.html"):
res["newmb"] = form.cleaned_data["name"]
res["oldparent"] = oldparent
res["newparent"] = pf
if "mbox" in request.session:
del request.session["mbox"]
WebmailNavigationParameters(request).remove('mbox')
return render_to_json_response(res)
return render_to_json_response({'form_errors': form.errors}, status=400)
@@ -224,8 +224,7 @@ def delfolder(request):
mbc = IMAPconnector(user=request.user.username,
password=request.session["password"])
mbc.delete_folder(name)
if "mbox" in request.session:
del request.session["mbox"]
WebmailNavigationParameters(request).remove('mbox')
return ajax_response(request)
@@ -307,7 +306,7 @@ def render_mboxes_list(request, imapc):
:param imapc: an ``IMAPconnector` object
:return: a string
"""
curmbox = request.session.get("mbox", "INBOX")
curmbox = WebmailNavigationParameters(request).get("mbox", "INBOX")
return _render_to_string(request, "webmail/folders.html", {
"selected": curmbox,
"mboxes": imapc.getmboxes(request.user),
@@ -315,25 +314,6 @@ def render_mboxes_list(request, imapc):
})
def set_nav_params(request):
if not "navparams" in request.session:
request.session["navparams"] = {}
request.session["pageid"] = \
int(request.GET.get("page", 1))
if not request.GET.get("order", False):
if not "order" in request.session["navparams"]:
request.session["navparams"]["order"] = "-date"
else:
request.session["navparams"]["order"] = request.GET.get("order")
for p in ["pattern", "criteria"]:
if request.GET.get(p, False):
request.session["navparams"][p] = request.GET[p]
elif p in request.session["navparams"]:
del request.session["navparams"][p]
def listmailbox(request, defmailbox="INBOX", update_session=True):
"""Mailbox content listing
@@ -345,19 +325,18 @@ def listmailbox(request, defmailbox="INBOX", update_session=True):
:param defmailbox: the default mailbox (when not present inside request arguments)
:return: a dictionnary
"""
mbox = request.GET.get("mbox", defmailbox)
navparams = WebmailNavigationParameters(request, defmailbox)
if update_session:
set_nav_params(request)
request.session["mbox"] = mbox
navparams.store()
mbox = navparams.get('mbox')
lst = ImapListing(
request.user, request.session["password"],
baseurl="?action=listmailbox&mbox=%s&" % mbox,
folder=mbox,
elems_per_page=int(parameters.get_user(request.user, "MESSAGES_PER_PAGE")),
**request.session["navparams"]
**request.session["webmail_navparams"]
)
return lst.render(request, request.session["pageid"])
return lst.render(request, navparams.get('page'))
def render_compose(request, form, posturl, email=None, insert_signature=False):
@@ -524,7 +503,6 @@ def index(request):
"""
action = request.GET.get("action", None)
if action is not None:
if not action in globals():
raise UnknownAction
@@ -534,7 +512,7 @@ def index(request):
raise BadRequest(_("Invalid request"))
response = dict(selection="webmail")
curmbox = request.session.get("mbox", "INBOX")
curmbox = WebmailNavigationParameters(request).get("mbox", "INBOX")
if not request.is_ajax():
request.session["lastaction"] = None
imapc = get_imapconnector(request)
-13
View File
@@ -139,16 +139,3 @@ class EmailListing(object):
else:
listing = self.fetch(request, page.id_start, page.id_stop)
return dict(listing=listing, navbar=self.render_navbar(page, self.baseurl))
def parse_search_parameters(request):
if 'pattern' in request.GET:
request.session["pattern"] = re.escape(request.GET["pattern"])
if 'criteria' in request.GET:
request.session["criteria"] = request.GET["criteria"]
else:
request.session["criteria"] = ["from_addr"]
else:
for p in ["pattern", "criteria"]:
if p in request.session.keys():
del request.session[p]
+5 -2
View File
@@ -41,8 +41,11 @@ class Column(object):
col["value"] = row[self.name]
try:
limit = getattr(self, "limit")
if len(col["value"]) > limit:
col["value"] = col["value"][0:limit] + "..."
if limit:
# The value is not necessarily a string, so...
col["value"] = unicode(col["value"], "utf-8") if isinstance(col["value"], str) else unicode(col["value"])
if len(col["value"]) > limit:
col["value"] = col["value"][0:limit] + "..."
except AttributeError:
pass
try:
+52
View File
@@ -155,3 +155,55 @@ def topredirection(request):
infos = exts_pool.get_extension_infos(topredir)
path = infos["url"] if infos["url"] else infos["name"]
return HttpResponseRedirect(path)
class NavigationParameters(object):
"""
Just a simple object to manipulate navigation parameters.
"""
def __init__(self, request, sessionkey):
self.request = request
self.sessionkey = sessionkey
self.parameters = [('pattern', '', True),
('criteria', 'from_addr', False)]
def store(self):
"""Store navigation parameters into session.
"""
if not self.sessionkey in self.request.session:
self.request.session[self.sessionkey] = {}
navparams = self.request.session[self.sessionkey]
navparams["order"] = self.request.GET.get("sort_order", "-date")
navparams["page"] = int(self.request.GET.get("page", 1))
for param, defvalue, escape in self.parameters:
value = self.request.GET.get(param, defvalue)
if value is None:
if param in navparams:
del navparams[param]
continue
navparams[param] = re.escape(value) if escape else value
self.request.session.modified = True
def get(self, param, default_value=None):
"""Retrieve a navigation parameter.
Just a simple getter to avoid using the full key name to
access a parameter.
:param str param: parameter name
:param defaultvalue: default value if none is found
:return: parameter's value
"""
if not self.sessionkey in self.request.session:
return default_value
return self.request.session[self.sessionkey].get(param, default_value)
def remove(self, param):
"""Remove a navigation parameter from session.
:param str param: parameter name
"""
navparams = self.request.session[self.sessionkey]
if param in navparams:
del navparams[param]
File diff suppressed because it is too large Load Diff
+49 -49
View File
@@ -6,8 +6,8 @@
msgid ""
msgstr ""
"Project-Id-Version: modoboa-1.0.0\n"
"Report-Msgid-Bugs-To: miris@kunago.com\n"
"POT-Creation-Date: 2013-09-20 22:30+0200\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-12-03 10:32+0100\n"
"PO-Revision-Date: 2013-09-29 22:13+0100\n"
"Last-Translator: Miroslav Abrahám <miris@kunago.com>\n"
"Language-Team: CS <miris@kunago.com>\n"
@@ -18,97 +18,81 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Poedit 1.5.7\n"
#: admin/static/admin/js/admin.js:26
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr "Hledat"
#: admin/static/admin/js/admin.js:110
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr "Nemazat adresář domény"
#: admin/static/admin/js/admin.js:113
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr "Tento krok odstraní VŠECHNA data související s touto doménou."
#: admin/static/admin/js/admin.js:114
#: extensions/admin/static/admin/js/admin.js:174
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr ""
"Tento krok odstraní VŠECHNA data související s touto doménou, vyjma účtů."
#: admin/static/admin/js/admin.js:117
msgid "Delete this domain?"
msgstr "Smazat tuto doménu?"
#: admin/static/admin/js/admin.js:161
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr "Není určen správce domény"
#: admin/static/admin/js/admin.js:238
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr "Nemazat adresář schránky"
#: admin/static/admin/js/admin.js:243
msgid "Delete this account?"
msgstr "Smazat tento účet?"
#: admin/static/admin/js/admin.js:249
msgid "Delete this list?"
msgstr "Smazat tento seznam?"
#: admin/static/admin/js/admin.js:254
msgid "Delete this forward?"
msgstr "Smazat toto přeposílání?"
#: admin/static/admin/js/admin.js:259
msgid "Delete this alias?"
msgstr "Smazat tento alias?"
#: extensions/amavis/static/amavis/js/quarantine.js:126
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr "Odblokovat tento výběr?"
#: extensions/amavis/static/amavis/js/quarantine.js:130
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr "Smazat tento výběr?"
#: extensions/amavis/static/amavis/js/quarantine.js:148
#: extensions/amavis/static/amavis/js/selfservice.js:22
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr "Odblokovat tuto zprávu?"
#: extensions/amavis/static/amavis/js/quarantine.js:152
#: extensions/amavis/static/amavis/js/selfservice.js:26
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr "Smazat tuto zprávu?"
#: extensions/amavis/static/amavis/js/quarantine.js:169
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr "Zobrazit úplné záhlaví"
#: extensions/amavis/static/amavis/js/quarantine.js:174
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr "Skrýt záhlaví"
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Odstranit tento filtr?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:117
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Uložit změny?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:144
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Smazat sadu filtrů?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:161
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Aktivovat tuto sadu filtrů?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:169
#: extensions/sievefilters/static/sievefilters/js/lib.js:224
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr "aktivní"
@@ -116,19 +100,19 @@ msgstr "aktivní"
msgid "Search a domain"
msgstr "Vyhledat doménu"
#: extensions/webmail/static/webmail/js/webmail.js:485
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr "Odstranit vybranou schránku?"
#: extensions/webmail/static/webmail/js/webmail.js:499
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr "Schránka odstraněna"
#: extensions/webmail/static/webmail/js/webmail.js:823
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr "Zpráva odeslána"
#: extensions/webmail/static/webmail/js/webmail.js:856
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr "Zpráva smazána"
@@ -148,18 +132,34 @@ msgstr[0] "Přesunuji %s zprávu"
msgstr[1] "Přesunuji %s zprávy"
msgstr[2] "Přesunuji %s zpráv"
#: static/js/confirmation.js:48
#: static/js/confirmation.js:50
msgid "Warning"
msgstr "Upozornění"
#: static/js/confirmation.js:57
#: static/js/confirmation.js:59
msgid "Cancel"
msgstr "Zrušit"
#: static/js/confirmation.js:99 static/js/history.js:239
msgid "Internal Error"
#: static/js/global.js:314
#, fuzzy
msgid "Internal error"
msgstr "Interní chyba"
#: static/js/searchbar.js:20
msgid "Search..."
msgstr "Hledat..."
#~ msgid "Delete this domain?"
#~ msgstr "Smazat tuto doménu?"
#~ msgid "Delete this account?"
#~ msgstr "Smazat tento účet?"
#~ msgid "Delete this list?"
#~ msgstr "Smazat tento seznam?"
#~ msgid "Delete this forward?"
#~ msgstr "Smazat toto přeposílání?"
#~ msgid "Delete this alias?"
#~ msgstr "Smazat tento alias?"
File diff suppressed because it is too large Load Diff
+48 -50
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa-0.9\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-05-22 17:39+0200\n"
"PO-Revision-Date: 2013-05-24 22:38+0100\n"
"POT-Creation-Date: 2013-12-03 10:33+0100\n"
"PO-Revision-Date: 2013-12-08 23:34+0100\n"
"Last-Translator: Patrick Ben Koetter <p@sys4.de>\n"
"Language-Team: DE <p@state-of-mind.fr>\n"
"Language: de\n"
@@ -17,94 +17,78 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: admin/static/admin/js/admin.js:26
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr "Suche"
#: admin/static/admin/js/admin.js:110
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr "Verzeichnis der Domain nicht löschen"
#: admin/static/admin/js/admin.js:113
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr "Diese Aktion wird ALLE mit der Domain verbundenen Daten löschen."
#: admin/static/admin/js/admin.js:114
#: extensions/admin/static/admin/js/admin.js:174
msgid "This operation will remove all data associated to this domain, excepting accounts."
msgstr "Diese Aktion wird ALLE mit der Domain verbundenen Daten löschen - ausser Mailkonten."
#: admin/static/admin/js/admin.js:117
msgid "Delete this domain?"
msgstr "Diese Domain löschen?"
#: admin/static/admin/js/admin.js:161
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr "Domain-Administrator fehlt"
#: admin/static/admin/js/admin.js:238
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr "Verzeichnis der Mailbox nicht löschen"
#: admin/static/admin/js/admin.js:243
msgid "Delete this account?"
msgstr "Diese Konto löschen?"
#: admin/static/admin/js/admin.js:249
msgid "Delete this list?"
msgstr "Diese Liste löschen?"
#: admin/static/admin/js/admin.js:254
msgid "Delete this forward?"
msgstr "Diese Weiterleitung löschen?"
#: admin/static/admin/js/admin.js:259
msgid "Delete this alias?"
msgstr "Diesen Alias löschen?"
#: extensions/amavis/static/amavis/js/quarantine.js:126
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr "Diese Auswahl freilassen?"
#: extensions/amavis/static/amavis/js/quarantine.js:130
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr "Diese Auswahl löschen?"
#: extensions/amavis/static/amavis/js/quarantine.js:148
#: extensions/amavis/static/amavis/js/selfservice.js:22
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr "Diese Nachricht freilassen?"
#: extensions/amavis/static/amavis/js/quarantine.js:152
#: extensions/amavis/static/amavis/js/selfservice.js:26
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr "Diese Nachricht löschen?"
#: extensions/amavis/static/amavis/js/quarantine.js:169
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr "Vollansicht Header anzeigen"
#: extensions/amavis/static/amavis/js/quarantine.js:174
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr "Vollansicht Header ausblenden"
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr "'service'-Liste aktualisiert"
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Diesen Filter entfernen?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:117
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Änderungen speichern?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:144
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Diesen Filter entfernen?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:161
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Diesen Filter aktivieren?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:169
#: extensions/sievefilters/static/sievefilters/js/lib.js:224
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr "aktiv"
@@ -112,19 +96,19 @@ msgstr "aktiv"
msgid "Search a domain"
msgstr "Domain suchen"
#: extensions/webmail/static/webmail/js/webmail.js:485
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr "Ausgewählte Mailbox entfernen?"
#: extensions/webmail/static/webmail/js/webmail.js:499
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr "Mailbox entfernt"
#: extensions/webmail/static/webmail/js/webmail.js:823
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr "Nachricht gesendet"
#: extensions/webmail/static/webmail/js/webmail.js:856
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr "Nachricht gelöscht"
@@ -142,22 +126,36 @@ msgid_plural "Moving %s messages"
msgstr[0] "Verschiebe %s Nachricht"
msgstr[1] "Verschiebe %s Nachrichten"
#: static/js/confirmation.js:48
#: static/js/confirmation.js:50
msgid "Warning"
msgstr "Warnung"
#: static/js/confirmation.js:57
#: static/js/confirmation.js:59
msgid "Cancel"
msgstr "Abbrechen"
#: static/js/confirmation.js:99
#: static/js/history.js:239
msgid "Internal Error"
#: static/js/global.js:314
msgid "Internal error"
msgstr "Interner Fehler"
#: static/js/searchbar.js:20
msgid "Search..."
msgstr "Suchen..."
#~ msgid "Delete this domain?"
#~ msgstr "Diese Domain löschen?"
#~ msgid "Delete this account?"
#~ msgstr "Diese Konto löschen?"
#~ msgid "Delete this list?"
#~ msgstr "Diese Liste löschen?"
#~ msgid "Delete this forward?"
#~ msgstr "Diese Weiterleitung löschen?"
#~ msgid "Delete this alias?"
#~ msgstr "Diesen Alias löschen?"
#~ msgid "Loading..."
#~ msgstr "Lade..."
File diff suppressed because it is too large Load Diff
+32 -48
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-05-22 17:39+0200\n"
"POT-Creation-Date: 2013-12-03 10:32+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,96 +17,80 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: admin/static/admin/js/admin.js:26
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr ""
#: admin/static/admin/js/admin.js:110
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr ""
#: admin/static/admin/js/admin.js:113
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr ""
#: admin/static/admin/js/admin.js:114
#: extensions/admin/static/admin/js/admin.js:174
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr ""
#: admin/static/admin/js/admin.js:117
msgid "Delete this domain?"
msgstr ""
#: admin/static/admin/js/admin.js:161
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr ""
#: admin/static/admin/js/admin.js:238
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr ""
#: admin/static/admin/js/admin.js:243
msgid "Delete this account?"
msgstr ""
#: admin/static/admin/js/admin.js:249
msgid "Delete this list?"
msgstr ""
#: admin/static/admin/js/admin.js:254
msgid "Delete this forward?"
msgstr ""
#: admin/static/admin/js/admin.js:259
msgid "Delete this alias?"
msgstr ""
#: extensions/amavis/static/amavis/js/quarantine.js:126
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr ""
#: extensions/amavis/static/amavis/js/quarantine.js:130
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr ""
#: extensions/amavis/static/amavis/js/quarantine.js:148
#: extensions/amavis/static/amavis/js/selfservice.js:22
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr ""
#: extensions/amavis/static/amavis/js/quarantine.js:152
#: extensions/amavis/static/amavis/js/selfservice.js:26
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr ""
#: extensions/amavis/static/amavis/js/quarantine.js:169
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr ""
#: extensions/amavis/static/amavis/js/quarantine.js:174
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr ""
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:117
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:144
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:161
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:169
#: extensions/sievefilters/static/sievefilters/js/lib.js:224
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr ""
@@ -114,19 +98,19 @@ msgstr ""
msgid "Search a domain"
msgstr ""
#: extensions/webmail/static/webmail/js/webmail.js:485
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr ""
#: extensions/webmail/static/webmail/js/webmail.js:499
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr ""
#: extensions/webmail/static/webmail/js/webmail.js:823
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr ""
#: extensions/webmail/static/webmail/js/webmail.js:856
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr ""
@@ -144,16 +128,16 @@ msgid_plural "Moving %s messages"
msgstr[0] ""
msgstr[1] ""
#: static/js/confirmation.js:48
#: static/js/confirmation.js:50
msgid "Warning"
msgstr ""
#: static/js/confirmation.js:57
#: static/js/confirmation.js:59
msgid "Cancel"
msgstr ""
#: static/js/confirmation.js:99 static/js/history.js:239
msgid "Internal Error"
#: static/js/global.js:314
msgid "Internal error"
msgstr ""
#: static/js/searchbar.js:20
File diff suppressed because it is too large Load Diff
+56 -53
View File
@@ -1,114 +1,101 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# Pedro Gracia <lasarux@neuroomante.com>, 2011,2013
# Pedro Gracia <pedro.gracia@impulzia.com>, 2010, 2011
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: http://dev.modoboa.org/\n"
"POT-Creation-Date: 2013-05-22 17:39+0200\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-12-03 10:32+0100\n"
"PO-Revision-Date: 2013-06-03 16:32+0000\n"
"Last-Translator: Pedro Gracia <lasarux@neuroomante.com>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/modoboa/language/es/)\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/modoboa/language/"
"es/)\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:26
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr "Buscar"
#: admin/static/admin/js/admin.js:110
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr "No eleminar el directorio del dominio"
#: admin/static/admin/js/admin.js:113
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr "Esta operación eliminará TODOS los datos asociados a este dominio."
#: admin/static/admin/js/admin.js:114
#: extensions/admin/static/admin/js/admin.js:174
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Esta operación eliminará todos los datos asociados a este dominio, excepto las cuentas."
msgstr ""
"Esta operación eliminará todos los datos asociados a este dominio, excepto "
"las cuentas."
#: admin/static/admin/js/admin.js:117
msgid "Delete this domain?"
msgstr "¿Eliminar este dominio?"
#: admin/static/admin/js/admin.js:161
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr "No hay un administrador de dominio definido"
#: admin/static/admin/js/admin.js:238
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr "No eliminar el directorio del mailbox"
#: admin/static/admin/js/admin.js:243
msgid "Delete this account?"
msgstr "¿Eliminar esta cuenta?"
#: admin/static/admin/js/admin.js:249
msgid "Delete this list?"
msgstr "¿Eliminar esta lista?"
#: admin/static/admin/js/admin.js:254
msgid "Delete this forward?"
msgstr "¿Eliminar este reenvío?"
#: admin/static/admin/js/admin.js:259
msgid "Delete this alias?"
msgstr "¿Eliminar este alias?"
#: extensions/amavis/static/amavis/js/quarantine.js:126
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr "¿Publicar esta selección?"
#: extensions/amavis/static/amavis/js/quarantine.js:130
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr "¿Eliminar esta selección?"
#: extensions/amavis/static/amavis/js/quarantine.js:148
#: extensions/amavis/static/amavis/js/selfservice.js:22
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr "¿Publicar este mensaje?"
#: extensions/amavis/static/amavis/js/quarantine.js:152
#: extensions/amavis/static/amavis/js/selfservice.js:26
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr "¿Eliminar este mensaje?"
#: extensions/amavis/static/amavis/js/quarantine.js:169
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr "Ver todas las cabeceras"
#: extensions/amavis/static/amavis/js/quarantine.js:174
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr "Esconder todas las cabeceras"
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "¿Eliminar este filtro?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:117
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "¿Guardar cambios?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:144
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "¿Eliminar este conjunto de filtros?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:161
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "¿Activar este conjunto de filtros?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:169
#: extensions/sievefilters/static/sievefilters/js/lib.js:224
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr "activo"
@@ -116,19 +103,19 @@ msgstr "activo"
msgid "Search a domain"
msgstr "Buscar un dominio"
#: extensions/webmail/static/webmail/js/webmail.js:485
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr "¿Eliminar el mailbox seleccionado?"
#: extensions/webmail/static/webmail/js/webmail.js:499
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr "Mailbox eliminado"
#: extensions/webmail/static/webmail/js/webmail.js:823
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr "Mensaje enviado"
#: extensions/webmail/static/webmail/js/webmail.js:856
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr "Mensaje eliminado"
@@ -146,18 +133,34 @@ msgid_plural "Moving %s messages"
msgstr[0] "Moviendo %s mensaje"
msgstr[1] "Moviendo %s mensajes"
#: static/js/confirmation.js:48
#: static/js/confirmation.js:50
msgid "Warning"
msgstr "Aviso"
#: static/js/confirmation.js:57
#: static/js/confirmation.js:59
msgid "Cancel"
msgstr "Cancelar"
#: static/js/confirmation.js:99 static/js/history.js:239
msgid "Internal Error"
#: static/js/global.js:314
#, fuzzy
msgid "Internal error"
msgstr "Error interno"
#: static/js/searchbar.js:20
msgid "Search..."
msgstr "Buscar..."
#~ msgid "Delete this domain?"
#~ msgstr "¿Eliminar este dominio?"
#~ msgid "Delete this account?"
#~ msgstr "¿Eliminar esta cuenta?"
#~ msgid "Delete this list?"
#~ msgstr "¿Eliminar esta lista?"
#~ msgid "Delete this forward?"
#~ msgstr "¿Eliminar este reenvío?"
#~ msgid "Delete this alias?"
#~ msgstr "¿Eliminar este alias?"
File diff suppressed because it is too large Load Diff
+35 -50
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: modoboa-1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-05-22 17:39+0200\n"
"POT-Creation-Date: 2013-12-03 10:32+0100\n"
"PO-Revision-Date: 2011-01-07 10:00+0100\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: FR <tonio@ngyn.org>\n"
@@ -17,97 +17,82 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:26
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr "Chercher"
#: admin/static/admin/js/admin.js:110
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr "Ne pas supprimer le répertoire du domaine"
#: admin/static/admin/js/admin.js:113
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr "Cette opération supprimera TOUTES les données associées à ce domaine."
#: admin/static/admin/js/admin.js:114
#: extensions/admin/static/admin/js/admin.js:174
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Cette opération supprimera toutes les données associées à ce domaine "
"à l'excéption des comptes."
msgstr ""
"Cette opération supprimera toutes les données associées à ce domaine à "
"l'excéption des comptes."
#: admin/static/admin/js/admin.js:117
msgid "Delete this domain?"
msgstr "Supprimer ce domaine?"
#: admin/static/admin/js/admin.js:161
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr "Aucun administrateur de domaine défini"
#: admin/static/admin/js/admin.js:238
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr "Ne pas supprimer le répertoire de la boite aux lettres"
#: admin/static/admin/js/admin.js:243
msgid "Delete this account?"
msgstr "Supprimer ce compte?"
#: admin/static/admin/js/admin.js:249
msgid "Delete this list?"
msgstr "Supprimer cette liste?"
#: admin/static/admin/js/admin.js:254
msgid "Delete this forward?"
msgstr "Supprimer ce transfert?"
#: admin/static/admin/js/admin.js:259
msgid "Delete this alias?"
msgstr "Supprimer cet alias?"
#: extensions/amavis/static/amavis/js/quarantine.js:126
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr "Relâcher cette sélection?"
#: extensions/amavis/static/amavis/js/quarantine.js:130
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr "Supprimer cette sélection?"
#: extensions/amavis/static/amavis/js/quarantine.js:148
#: extensions/amavis/static/amavis/js/selfservice.js:22
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr "Relâcher cette message?"
#: extensions/amavis/static/amavis/js/quarantine.js:152
#: extensions/amavis/static/amavis/js/selfservice.js:26
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr "Supprimer ce message?"
#: extensions/amavis/static/amavis/js/quarantine.js:169
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr "Voir les entêtes complets"
#: extensions/amavis/static/amavis/js/quarantine.js:174
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr "Cacher les entêtes complets"
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr "Services mis à jour"
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Supprimer ce filtre?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:117
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Enregistrer les changements?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:144
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Supprimer ce jeu de filtres?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:161
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Activer ce jeu de filtres?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:169
#: extensions/sievefilters/static/sievefilters/js/lib.js:224
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr "actif"
@@ -115,19 +100,19 @@ msgstr "actif"
msgid "Search a domain"
msgstr "Checher un domaine"
#: extensions/webmail/static/webmail/js/webmail.js:485
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr "Supprimer la boite sélectionnée?"
#: extensions/webmail/static/webmail/js/webmail.js:499
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr "Boite aux lettres supprimées"
#: extensions/webmail/static/webmail/js/webmail.js:823
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr "Message envoyé"
#: extensions/webmail/static/webmail/js/webmail.js:856
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr "Message supprimé"
@@ -145,16 +130,16 @@ msgid_plural "Moving %s messages"
msgstr[0] "Déplacement de %s message"
msgstr[1] "Déplacement de %s messages"
#: static/js/confirmation.js:48
#: static/js/confirmation.js:50
msgid "Warning"
msgstr "Avertissement"
#: static/js/confirmation.js:57
#: static/js/confirmation.js:59
msgid "Cancel"
msgstr "Annuler"
#: static/js/confirmation.js:99 static/js/history.js:239
msgid "Internal Error"
#: static/js/global.js:314
msgid "Internal error"
msgstr "Erreur interne"
#: static/js/searchbar.js:20
File diff suppressed because it is too large Load Diff
+33 -28
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-09-26 23:44+0200\n"
"POT-Creation-Date: 2013-12-03 10:32+0100\n"
"PO-Revision-Date: 2013-09-27 00:07+0200\n"
"Last-Translator: Giancarlo Gaifas <lallo@artiemestieri.tn.it>\n"
"Language-Team: Italiano <LL@li.org>\n"
@@ -17,19 +17,19 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:26
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr "Ricerca"
#: admin/static/admin/js/admin.js:110
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr "Non eliminare la cartella dei domini"
#: admin/static/admin/js/admin.js:113
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr "Questa operazione eliminerà TUTTI i dati associati a questo dominio."
#: admin/static/admin/js/admin.js:114
#: extensions/admin/static/admin/js/admin.js:174
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
@@ -37,58 +37,62 @@ msgstr ""
"Questa operazione eliminerà tutti i dati associati a questo dominio tranne "
"gli account"
#: admin/static/admin/js/admin.js:161
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr "Non è stato definito un amministratore di dominio"
#: admin/static/admin/js/admin.js:238
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr "Non eliminare la cartella della casella posta"
#: extensions/amavis/static/amavis/js/quarantine.js:126
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr "Rilasciare questa selezione?"
#: extensions/amavis/static/amavis/js/quarantine.js:130
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr "Eliminare questa selezione?"
#: extensions/amavis/static/amavis/js/quarantine.js:148
#: extensions/amavis/static/amavis/js/selfservice.js:22
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr "Rilasciare questo messaggio?"
#: extensions/amavis/static/amavis/js/quarantine.js:152
#: extensions/amavis/static/amavis/js/selfservice.js:26
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr "Eliminare questo messaggio?"
#: extensions/amavis/static/amavis/js/quarantine.js:169
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr "Visualizza tutte le intestazioni"
#: extensions/amavis/static/amavis/js/quarantine.js:174
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr "Nascondi le intestazioni"
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Rimuovere questo filtro?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:117
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Salvare le modifiche?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:144
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Rimuovere il gruppo di filtri?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:161
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Attivare il gruppo di filtri?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:169
#: extensions/sievefilters/static/sievefilters/js/lib.js:224
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr "attivo"
@@ -96,30 +100,30 @@ msgstr "attivo"
msgid "Search a domain"
msgstr "Cerca un dominio"
#: extensions/webmail/static/webmail/js/webmail.js:476
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr "Rimuovere la casella di posta selezionata?"
#: extensions/webmail/static/webmail/js/webmail.js:490
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr "Casella di posta rimossa"
#: extensions/webmail/static/webmail/js/webmail.js:846
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr "Messaggio inviato"
#: extensions/webmail/static/webmail/js/webmail.js:878
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr "Messaggio eliminato"
#: extensions/webmail/static/webmail/js/webmail.js:1008
#: extensions/webmail/static/webmail/js/webmail.js:981
#, c-format
msgid "%s file"
msgid_plural "%s files"
msgstr[0] "%s file"
msgstr[1] "%s file"
#: extensions/webmail/static/webmail/js/webmail.js:1040
#: extensions/webmail/static/webmail/js/webmail.js:1013
#, c-format
msgid "Moving %s message"
msgid_plural "Moving %s messages"
@@ -134,8 +138,9 @@ msgstr "Attenzione"
msgid "Cancel"
msgstr "Elimina"
#: static/js/confirmation.js:101 static/js/history.js:235
msgid "Internal Error"
#: static/js/global.js:314
#, fuzzy
msgid "Internal error"
msgstr "Errore interno"
#: static/js/searchbar.js:20
File diff suppressed because it is too large Load Diff
+56 -53
View File
@@ -1,114 +1,101 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# mikec <miguelmclara@gmail.com>, 2012
# Sandra Ribeiro <sandrarib@gmail.com>, 2013
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: http://dev.modoboa.org/\n"
"POT-Creation-Date: 2013-05-22 17:39+0200\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-12-03 10:33+0100\n"
"PO-Revision-Date: 2013-06-03 17:44+0000\n"
"Last-Translator: Sandra Ribeiro <sandrarib@gmail.com>\n"
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/modoboa/language/pt_PT/)\n"
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/projects/p/"
"modoboa/language/pt_PT/)\n"
"Language: pt_PT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt_PT\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:26
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr "Procurar"
#: admin/static/admin/js/admin.js:110
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr "Não excluir o diretório do domínio"
#: admin/static/admin/js/admin.js:113
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr "Esta operação irá remover TODOS os dados associados a este domínio."
#: admin/static/admin/js/admin.js:114
#: extensions/admin/static/admin/js/admin.js:174
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Esta operação irá remover TODOS os dados associados a este domínio, excepto as contas."
msgstr ""
"Esta operação irá remover TODOS os dados associados a este domínio, excepto "
"as contas."
#: admin/static/admin/js/admin.js:117
msgid "Delete this domain?"
msgstr "Apagar este domínio?"
#: admin/static/admin/js/admin.js:161
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr "Não está definido nenhum administrador para o domínio"
#: admin/static/admin/js/admin.js:238
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr "Não excluir o diretório da caixa de correio"
#: admin/static/admin/js/admin.js:243
msgid "Delete this account?"
msgstr "Apagar esta conta?"
#: admin/static/admin/js/admin.js:249
msgid "Delete this list?"
msgstr "Apagar esta lista?"
#: admin/static/admin/js/admin.js:254
msgid "Delete this forward?"
msgstr "Apagar este reencaminhamento?"
#: admin/static/admin/js/admin.js:259
msgid "Delete this alias?"
msgstr "Apagar este alias?"
#: extensions/amavis/static/amavis/js/quarantine.js:126
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr "Libertar itens seleccionados?"
#: extensions/amavis/static/amavis/js/quarantine.js:130
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr "Eliminar itens seleccionados?"
#: extensions/amavis/static/amavis/js/quarantine.js:148
#: extensions/amavis/static/amavis/js/selfservice.js:22
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr "Libertar esta mensagem?"
#: extensions/amavis/static/amavis/js/quarantine.js:152
#: extensions/amavis/static/amavis/js/selfservice.js:26
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr "Eliminar esta mensagem?"
#: extensions/amavis/static/amavis/js/quarantine.js:169
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr "Ver cabeçalho completo"
#: extensions/amavis/static/amavis/js/quarantine.js:174
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr "Esconder cabeçalho completo"
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Remover este filtro?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:117
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Guardar alterações?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:144
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Limpar filtros definidos?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:161
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Activar estes filtros?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:169
#: extensions/sievefilters/static/sievefilters/js/lib.js:224
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr "activo"
@@ -116,19 +103,19 @@ msgstr "activo"
msgid "Search a domain"
msgstr "Pesquisar domínio"
#: extensions/webmail/static/webmail/js/webmail.js:485
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr "Remover a caixa de correio seleccionada"
#: extensions/webmail/static/webmail/js/webmail.js:499
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr "Caixa removida"
#: extensions/webmail/static/webmail/js/webmail.js:823
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr "Mensagem enviada"
#: extensions/webmail/static/webmail/js/webmail.js:856
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr "Mensagem apagada"
@@ -146,18 +133,34 @@ msgid_plural "Moving %s messages"
msgstr[0] "A mover %s mensagem"
msgstr[1] "A mover %s mensagens"
#: static/js/confirmation.js:48
#: static/js/confirmation.js:50
msgid "Warning"
msgstr "Aviso"
#: static/js/confirmation.js:57
#: static/js/confirmation.js:59
msgid "Cancel"
msgstr "Cancelar"
#: static/js/confirmation.js:99 static/js/history.js:239
msgid "Internal Error"
#: static/js/global.js:314
#, fuzzy
msgid "Internal error"
msgstr "Erro Interno"
#: static/js/searchbar.js:20
msgid "Search..."
msgstr "Pesquisar..."
#~ msgid "Delete this domain?"
#~ msgstr "Apagar este domínio?"
#~ msgid "Delete this account?"
#~ msgstr "Apagar esta conta?"
#~ msgid "Delete this list?"
#~ msgstr "Apagar esta lista?"
#~ msgid "Delete this forward?"
#~ msgstr "Apagar este reencaminhamento?"
#~ msgid "Delete this alias?"
#~ msgstr "Apagar este alias?"
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-12-03 10:32+0100\n"
"PO-Revision-Date: 2013-12-03 09:35+0000\n"
"Last-Translator: Antoine Nguyen <tonio@ngyn.org>\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/modoboa/language/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr "Поиск"
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr "Не удаляйте каталог домена"
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr "Эта операция удалит ВСЕ данные, связанные с этим доменом."
#: extensions/admin/static/admin/js/admin.js:174
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Эта операция удалит ВСЕ данные, связанные с этим доменом, за исключением аккаунтов."
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr "Не указан администратор домена"
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr "Не удаляйте каталог почтовых ящиков"
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr "Деблокировать отмеченные?"
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr "Удалить отмеченные?"
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr "Деблокировать сообщение?"
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr "Удалить сообщение?"
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr "Просмотр всех заголовков"
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr "Скрыть все заголовки"
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Удалить фильтр?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Сохранить изменения?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Удалить набор фильтров?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Активировать набор фильтров?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr "активно"
#: extensions/stats/static/stats/js/stats.js:37
msgid "Search a domain"
msgstr "Найти домен"
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr "Удалить выбранные почтовые ящики?"
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr "Почтовый ящик удален"
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr "Сообщение послано"
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr "Сообщение удалено"
#: extensions/webmail/static/webmail/js/webmail.js:981
#, c-format
msgid "%s file"
msgid_plural "%s files"
msgstr[0] "%s файл"
msgstr[1] "%s файла"
msgstr[2] "%s файлов"
#: extensions/webmail/static/webmail/js/webmail.js:1013
#, c-format
msgid "Moving %s message"
msgid_plural "Moving %s messages"
msgstr[0] "Перемещается %s сообщение"
msgstr[1] "Перемещается %s сообщения"
msgstr[2] "Перемещается %s сообщений"
#: static/js/confirmation.js:50
msgid "Warning"
msgstr "Внимание"
#: static/js/confirmation.js:59
msgid "Cancel"
msgstr "Отмена"
#: static/js/global.js:314
msgid "Internal error"
msgstr ""
#: static/js/searchbar.js:20
msgid "Search..."
msgstr "Поиск..."
File diff suppressed because it is too large Load Diff
+56 -53
View File
@@ -1,113 +1,100 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# ogg1980 <olle@dalnix.se>, 2013
msgid ""
msgstr ""
"Project-Id-Version: Modoboa\n"
"Report-Msgid-Bugs-To: http://dev.modoboa.org/\n"
"POT-Creation-Date: 2013-05-22 17:39+0200\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-12-03 10:33+0100\n"
"PO-Revision-Date: 2013-05-23 08:22+0000\n"
"Last-Translator: ogg1980 <olle@dalnix.se>\n"
"Language-Team: Swedish (http://www.transifex.com/projects/p/modoboa/language/sv/)\n"
"Language-Team: Swedish (http://www.transifex.com/projects/p/modoboa/language/"
"sv/)\n"
"Language: sv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: sv\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: admin/static/admin/js/admin.js:26
#: extensions/admin/static/admin/js/admin.js:32
msgid "Search"
msgstr "Sök"
#: admin/static/admin/js/admin.js:110
#: extensions/admin/static/admin/js/admin.js:170
msgid "Do not delete domain directory"
msgstr "Radera inte domän katalog"
#: admin/static/admin/js/admin.js:113
#: extensions/admin/static/admin/js/admin.js:173
msgid "This operation will remove ALL data associated to this domain."
msgstr "Denna åtgärd kommer radera ALL data associerad med den här domänen."
#: admin/static/admin/js/admin.js:114
#: extensions/admin/static/admin/js/admin.js:174
msgid ""
"This operation will remove all data associated to this domain, excepting "
"accounts."
msgstr "Den här operationen raderar all data som är associerad med denna domän, förutom konton."
msgstr ""
"Den här operationen raderar all data som är associerad med denna domän, "
"förutom konton."
#: admin/static/admin/js/admin.js:117
msgid "Delete this domain?"
msgstr "Radera denna domän?"
#: admin/static/admin/js/admin.js:161
#: extensions/admin/static/admin/js/admin.js:222
msgid "No domain administrator defined"
msgstr "Ingen domänadministratör definierad"
#: admin/static/admin/js/admin.js:238
#: extensions/admin/static/admin/js/admin.js:291
msgid "Do not delete mailbox directory"
msgstr "Radera inte brevlådans katalog"
#: admin/static/admin/js/admin.js:243
msgid "Delete this account?"
msgstr "Radera detta konto?"
#: admin/static/admin/js/admin.js:249
msgid "Delete this list?"
msgstr "Radera denna lista?"
#: admin/static/admin/js/admin.js:254
msgid "Delete this forward?"
msgstr "Radera denna vidarebefodring?"
#: admin/static/admin/js/admin.js:259
msgid "Delete this alias?"
msgstr "Radera detta alias?"
#: extensions/amavis/static/amavis/js/quarantine.js:126
#: extensions/amavis/static/amavis/js/quarantine.js:180
msgid "Release this selection?"
msgstr "Släpp markerat urval?"
#: extensions/amavis/static/amavis/js/quarantine.js:130
#: extensions/amavis/static/amavis/js/quarantine.js:184
msgid "Delete this selection?"
msgstr "Radera markerat urval?"
#: extensions/amavis/static/amavis/js/quarantine.js:148
#: extensions/amavis/static/amavis/js/selfservice.js:22
#: extensions/amavis/static/amavis/js/quarantine.js:203
#: extensions/amavis/static/amavis/js/selfservice.js:17
msgid "Release this message?"
msgstr "Släpp detta meddelande?"
#: extensions/amavis/static/amavis/js/quarantine.js:152
#: extensions/amavis/static/amavis/js/selfservice.js:26
#: extensions/amavis/static/amavis/js/quarantine.js:207
#: extensions/amavis/static/amavis/js/selfservice.js:21
msgid "Delete this message?"
msgstr "Radera detta meddelande?"
#: extensions/amavis/static/amavis/js/quarantine.js:169
#: extensions/amavis/static/amavis/js/quarantine.js:224
msgid "View full headers"
msgstr "Visa meddelandehuvud"
#: extensions/amavis/static/amavis/js/quarantine.js:174
#: extensions/amavis/static/amavis/js/quarantine.js:229
msgid "Hide full headers"
msgstr "Göm meddelandehuvud"
#: extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js:49
msgid "Services updated"
msgstr ""
#: extensions/sievefilters/static/sievefilters/js/lib.js:14
msgid "Remove this filter?"
msgstr "Radera detta filter?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:117
#: extensions/sievefilters/static/sievefilters/js/lib.js:107
msgid "Save changes?"
msgstr "Spara ändringar?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:144
#: extensions/sievefilters/static/sievefilters/js/lib.js:129
msgid "Remove filters set?"
msgstr "Radera filteruppsättning?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:161
#: extensions/sievefilters/static/sievefilters/js/lib.js:146
msgid "Activate this filters set?"
msgstr "Aktivera filteruppsättning?"
#: extensions/sievefilters/static/sievefilters/js/lib.js:169
#: extensions/sievefilters/static/sievefilters/js/lib.js:224
#: extensions/sievefilters/static/sievefilters/js/lib.js:154
#: extensions/sievefilters/static/sievefilters/js/lib.js:203
msgid "active"
msgstr "aktivera"
@@ -115,19 +102,19 @@ msgstr "aktivera"
msgid "Search a domain"
msgstr "Sök domän"
#: extensions/webmail/static/webmail/js/webmail.js:485
#: extensions/webmail/static/webmail/js/webmail.js:472
msgid "Remove the selected mailbox?"
msgstr "Radera markerad brevlåda?"
#: extensions/webmail/static/webmail/js/webmail.js:499
#: extensions/webmail/static/webmail/js/webmail.js:485
msgid "Mailbox removed"
msgstr "Brevlåda raderad"
#: extensions/webmail/static/webmail/js/webmail.js:823
#: extensions/webmail/static/webmail/js/webmail.js:727
msgid "Message sent"
msgstr "Meddelande sänt"
#: extensions/webmail/static/webmail/js/webmail.js:856
#: extensions/webmail/static/webmail/js/webmail.js:854
msgid "Message deleted"
msgstr "Meddelande raderat"
@@ -145,18 +132,34 @@ msgid_plural "Moving %s messages"
msgstr[0] "Flyttar %s meddelande"
msgstr[1] "Flyttar %s meddelanden"
#: static/js/confirmation.js:48
#: static/js/confirmation.js:50
msgid "Warning"
msgstr "Varning"
#: static/js/confirmation.js:57
#: static/js/confirmation.js:59
msgid "Cancel"
msgstr "Avbryt"
#: static/js/confirmation.js:99 static/js/history.js:239
msgid "Internal Error"
#: static/js/global.js:314
#, fuzzy
msgid "Internal error"
msgstr "Internt fel"
#: static/js/searchbar.js:20
msgid "Search..."
msgstr "Sök..."
#~ msgid "Delete this domain?"
#~ msgstr "Radera denna domän?"
#~ msgid "Delete this account?"
#~ msgstr "Radera detta konto?"
#~ msgid "Delete this list?"
#~ msgstr "Radera denna lista?"
#~ msgid "Delete this forward?"
#~ msgstr "Radera denna vidarebefodring?"
#~ msgid "Delete this alias?"
#~ msgstr "Radera detta alias?"
+10
View File
@@ -116,3 +116,13 @@ body {
.table-fixed {
table-layout: fixed;
}
.menubar-text {
display: inline;
vertical-align: middle;
}
.label-title {
vertical-align: middle;
margin: 0 10px;
}
+13
View File
@@ -233,6 +233,19 @@ function parse_qs(raw) {
return res;
}
/*
* Extract a specific URL parameter using its name.
*
* Ref: http://stackoverflow.com/questions/901115/get-query-string-values-in-javascript
*/
function get_parameter_by_name(url, name) {
var name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(url);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
/*
* Return the target associated to an event object.
*/
+59 -11
View File
@@ -10,6 +10,7 @@
this.$element = $(element);
this.options = $.extend({}, $.fn.htmltable.defaults, options);
this.shift_pressed = false;
this.ctrl_pressed = false;
this.last_selection = null;
this.prev_dir = null;
this.listen();
@@ -25,29 +26,50 @@
},
listen: function() {
$(document)
.off("click", "tbody>tr")
.on("click", "tbody>tr",
.off("change", "tbody input[type=checkbox]")
.on("change", "tbody input[type=checkbox]",
$.proxy(this.toggle_select, this));
$(document).on("keydown", "body", $.proxy(this.keydown, this));
$(document).on("keyup", "body", $.proxy(this.keyup, this));
},
/**
* Effective change of a table row's state.
*/
_toggle_select: function($tr) {
this.last_selection = $tr;
if ($tr.hasClass(this.options.tr_selected_class)) {
$tr.children('td[name=selection]').children('input').prop('checked', false);
$tr.removeClass(this.options.tr_selected_class);
if (this.options.tr_unselected_event != undefined) {
this.options.tr_unselected_event($tr);
}
} else {
$tr.children('td[name=selection]').children('input').prop('checked', true);
$tr.addClass(this.options.tr_selected_class);
if (this.options.tr_selected_event != undefined) {
this.options.tr_selected_event($tr);
}
}
},
/**
* Change the selection state of a given table row.
*
* Holding the shift key between two selections let users
* select all rows between those two locations.
*
* Holding the ctrl key between two selections let users make
* an additive selection.
*/
toggle_select: function(e) {
e.preventDefault();
var $tr = $(e.target).parent();
var $tr = $(e.target).parents('tr');
if (this.shift_pressed && this.last_selection) {
var start = this.last_selection;
this.clear_selection();
this.last_selection = start;
var itfunc = ($tr.index() >= this.last_selection.index())
? "next" : "prev";
var $curtr = null;
@@ -55,7 +77,7 @@
if (this.prev_dir && itfunc != this.prev_dir) {
$curtr = this.last_selection;
} else {
$curtr = this.last_selection[itfunc]("tr");
$curtr = this.last_selection;
}
for (; $curtr.length; $curtr = $curtr[itfunc]("tr")) {
this._toggle_select($curtr);
@@ -65,30 +87,50 @@
}
this.prev_dir = itfunc;
return;
} else if (!this.ctrl_pressed && (!this.last_selection || !this.last_selection.is($tr))) {
this.clear_selection();
}
this.last_selection = $tr;
this._toggle_select($tr);
},
/**
* Check if $row is selected or not.
*/
is_selected: function($row) {
return $row.hasClass(this.options.tr_selected_class);
},
/**
* Return the current selection. (jQuery object)
*/
current_selection: function() {
return $("tr[class*=" + this.options.tr_selected_class + "]");
},
/*
* Cancel the current selection
/**
* Cancel the current selection.
*/
clear_selection: function() {
this.last_selection = null;
this.current_selection().removeClass(this.options.tr_selected_class);
$("tbody input[type=checkbox]").prop('checked', false);
if (this.options.tr_unselected_event != undefined) {
this.options.tr_unselected_event();
}
},
/*
* Manually select a table row
/**
* Manually select a table row.
*/
select_row: function($row) {
if (!this.last_selection) {
this.last_selection = $row;
}
$row.addClass(this.options.tr_selected_class);
if (this.options.tr_selected_event != undefined) {
this.options.tr_selected_event($row);
}
},
keydown: function(e) {
@@ -97,6 +139,9 @@
e.preventDefault();
this.shift_pressed = true;
break;
case 17:
this.ctrl_pressed = true;
break;
}
},
@@ -106,6 +151,9 @@
e.preventDefault();
this.shift_pressed = false;
break;
case 17:
this.ctrl_pressed = false;
break;
}
}
};
+1 -1
View File
@@ -30,7 +30,7 @@ Listing.prototype = {
e.preventDefault();
});
}
if (data.page != this.navobj.getparam("page")) {
if (data.page && data.page != this.navobj.getparam("page")) {
this.navobj.setparam("page", data.page).update(false, true);
}
var $sortables = $(this.options.sortable_selector);
+3 -2
View File
@@ -1,11 +1,12 @@
{% load lib_tags %}
<div class="btn-toolbar {{ extraclasses }}">{% for entry in entries %}
<div class="btn-group">{% if entry.menu %}
<a class="btn dropdown-toggle" data-toggle="dropdown">{% if entry.img %}<i class="{{ entry.img}}"></i>{% endif %}{{ entry.label }}<span class="caret"></span></a>
<a class="btn dropdown-toggle" data-toggle="dropdown">{% if entry.img %}<i class="{{ entry.img}}"></i>{% endif %}{{ entry.label }} <span class="caret"></span></a>
<ul class="dropdown-menu">{% for sentry in entry.menu %}
<li>{% render_link sentry %}</li>
{% endfor %}</ul>
{% else %}{% render_link entry "btn" %}{% endif %}
</div>
{% endfor %}{{ extracontent }}</div>
{% endfor %}{% block extracontent %}{% endblock %}
</div>
+1 -1
View File
@@ -1,4 +1,4 @@
django
django>=1.5,<1.6
django-reversion>=1.8.0
dj-database-url
south