Better settings to control manual learning.

see #622
This commit is contained in:
Antoine Nguyen
2014-10-25 17:14:33 +02:00
parent ac00c8e195
commit 56f48ed6b8
7 changed files with 152 additions and 59 deletions
+4 -3
View File
@@ -712,10 +712,11 @@ Raised when an existing mailbox is modified.
*Callback prototype*::
def callback(newmailbox, oldmailbox): pass
def callback(mailbox): pass
* ``newmailbox`` is a ``Mailbox`` instance containing the new values
* ``oldmailbox`` is a ``Mailbox`` instance containing the old values
* ``mailbox`` is the ``Mailbox`` modified instance. It contains a
``old_full_address`` extra field to check if the address was
modified.
PasswordChange
==============
+27 -12
View File
@@ -74,12 +74,22 @@ class ParametersForm(AdminParametersForm):
help_text=_("The e-mail address used to send notitications")
)
lsep = SeparatorField(label=_("Learning from messages"))
lsep = SeparatorField(label=_("Manual learning"))
amavis_is_local = YesNoField(
label=_("Is amavis local?"),
manual_learning = YesNoField(
label=_("Enable manual learning"),
initial="yes",
help_text=_("Tell if amavis is running on the same server than modoboa")
help_text=_(
"Allow users to manually train Spamassassin about spam and non-spam"
)
)
sa_is_local = YesNoField(
label=_("Is Spamassassin local?"),
initial="yes",
help_text=_(
"Tell if spamassassin is running on the same server than modoboa"
)
)
default_user = forms.CharField(
@@ -90,12 +100,6 @@ class ParametersForm(AdminParametersForm):
)
)
per_user_bayesian_filter = YesNoField(
label=_("Enable per-user bayesian filters?"),
initial="no",
help_text=_("Allow users to personally train spamassassin")
)
spamd_address = forms.CharField(
label=_("Spamd address"),
initial="127.0.0.1",
@@ -108,12 +112,23 @@ class ParametersForm(AdminParametersForm):
help_text=_("The TCP port spamd is listening on")
)
user_manual_learning = YesNoField(
label=_("Enable per-user manual learning"),
initial="no",
help_text=_("Allow users to personally tell Spamassassin about "
"spam and non-spam")
)
visibility_rules = {
"am_pdp_host": "am_pdp_mode=inet",
"am_pdp_port": "am_pdp_mode=inet",
"am_pdp_socket": "am_pdp_mode=unix",
"spamd_address": "amavis_is_local=no",
"spamd_port": "amavis_is_local=no",
"sa_is_local": "manual_learning=yes",
"default_user": "manual_learning=yes",
"spamd_address": "sa_is_local=no",
"spamd_port": "sa_is_local=no",
"user_manual_learning": "manual_learning=yes"
}
+42 -5
View File
@@ -8,6 +8,8 @@ from modoboa.extensions.amavis.lib import (
create_user_and_policy, update_user_and_policy, delete_user_and_policy,
create_user_and_use_policy, delete_user
)
from .lib import manual_learning_enabled
from .models import Users
@events.observe("UserMenuDisplay")
@@ -54,18 +56,53 @@ def on_domain_alias_deleted(domainaliases):
delete_user("@{0}".format(domainalias.name))
@events.observe("MailboxCreated")
def on_mailbox_created(user, mailbox):
"""Create amavis records for the new mailbox."""
create_user_and_policy(mailbox.full_address)
@events.observe("MailboxModified")
def on_mailbox_modified(mailbox):
"""Update amavis records if address has changed."""
if parameters.get_admin("MANUAL_LEARNING") == "no" or \
mailbox.full_address == mailbox.old_full_address:
return
user = Users.objects.select_related.get(email=mailbox.old_full_address)
full_address = mailbox.full_address
user.email = full_address
user.policy.policy_name = full_address[:32]
user.policy.sa_username = full_address
user.policy.save()
user.save()
@events.observe("MailboxDeleted")
def on_mailbox_deleted(mailbox):
"""Delete amavis records related to this mailbox."""
"""Clean amavis database when a mailbox is removed."""
if parameters.get_admin("MANUAL_LEARNING") == "no":
return
delete_user_and_policy(mailbox.full_address)
@events.observe("MailboxAliasCreated")
def on_mailboxalias_created(user, alias):
"""Create amavis record for the new alias.
FIXME: how to deal with distibution lists ?
"""
if not manual_learning_enabled(user) or alias.type != "alias":
return
policy = Policy.objects.get(
policy_name=alias.mboxes.all()[0].full_address
)
email = alias.full_address
Users.objects.create(email=email, policy=policy, fullname=email)
@events.observe("MailboxAliasDeleted")
def on_mailboxalias_deleted(alias):
"""Clean amavis database when an alias is removed."""
if parameters.get_admin("MANUAL_LEARNING") == "no":
return
if Users.objects.exists(email=alias.fullname):
Users.objects.delete(email=alias.fullname)
@events.observe("GetStaticContent")
def extra_static_content(caller, user):
if user.group == "SimpleUsers":
+40 -10
View File
@@ -88,20 +88,20 @@ class SpamassassinClient(object):
"""A stupid spamassassin client."""
def __init__(self, username=None):
def __init__(self, user):
"""Constructor."""
self._amavis_is_local = parameters.get_admin("AMAVIS_IS_LOCAL")
if username is None:
self._username = parameters.get_admin("DEFAULT_USER")
self._sa_is_local = parameters.get_admin("SA_IS_LOCAL")
if user.group == "SimpleUsers":
user_manual_learning = parameters.get_admin("USER_MANUAL_LEARNING")
if user_manual_learning == "yes":
self._username = user.email
else:
self._username = username
self._username = parameters.get_admin("DEFAULT_USER")
setup_manual_learning(user)
self.error = None
if self._amavis_is_local == "yes":
if self._sa_is_local == "yes":
self._learn_cmd = "sa-learn --{0} --no-sync -u {1}"
self._learn_cmd_kwargs = {}
# self._learn_cmd_kwargs = {
# "sudo_user": parameters.get_admin("AMAVIS_SYSTEM_USER")
# }
self._expected_exit_codes = [0]
else:
self._learn_cmd = "spamc -d {0} -p {1}".format(
@@ -131,7 +131,7 @@ class SpamassassinClient(object):
def done(self):
"""Call this method at the end of the processing."""
if self._amavis_is_local:
if self._sa_is_local:
exec_cmd("sa-learn -u {0} --sync".format(self._username),
**self._learn_cmd_kwargs)
@@ -177,11 +177,13 @@ def create_user_and_policy(name, priority=7):
an identifier.
:param str name: name
:return: the new ``Policy`` object
"""
policy = Policy.objects.create(policy_name=name[:32])
Users.objects.create(
email=name, fullname=name, priority=priority, policy=policy
)
return policy
def create_user_and_use_policy(name, policy_name, priority=7):
@@ -234,3 +236,31 @@ def delete_user(name):
Users.objects.get(email=name).delete()
except Users.DoesNotExist:
pass
def manual_learning_enabled(user):
"""Check if manual learning is enabled or not.
Also check for :kw:`user` if necessary.
:return: True if learning is enabled, False otherwise.
"""
manual_learning = parameters.get_admin("MANUAL_LEARNING") == "yes"
if manual_learning and user.group == "SimpleUsers":
manual_learning = parameters.get_admin("USER_MANUAL_LEARNING") == "yes"
return manual_learning
def setup_manual_learning(user):
"""Setup manual learning if necessary.
:return: True if learning has been setup, False otherwise
"""
result = False
if user.group == "SimpleUsers":
if not Policy.objects.exists(email=user.email):
policy = create_user_and_policy(user.email)
for alias in user.mailbox_set.all()[0].alias_addresses:
create_user_and_use_policy(alias.full_address, policy)
result = True
return result
@@ -23,7 +23,8 @@
<i class="icon-white icon-trash"></i> {% trans "Delete" %}
</a>
</div>
<div class="btn-group" title="{% trans 'More actions' %}">
{% if manual_learning %}
<div class="btn-group" title="{% trans 'Manual learning' %}">
<button class="btn dropdown-toggle" data-toggle="dropdown">
<i class="icon-cog"></i> <span class="caret"></span>
</button>
@@ -32,6 +33,7 @@
<li><a name="mark-as-ham-multi" href="{% url 'modoboa.extensions.amavis.views.process' %}">{% trans "Mark as non-spam" %}</a></li>
</ul>
</div>
{% endif %}
{% include "common/email_searchbar.html" with extraopts=extraopts %}
</div>
<div id="filters_bar" class="btn-toolbar pull-right">
@@ -1,13 +1,20 @@
"""
Amavis frontend template tags.
"""
from django import template
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from ..lib import manual_learning_enabled
register = template.Library()
@register.simple_tag
def viewm_menu(mail_id, rcpt):
def viewm_menu(user, mail_id, rcpt):
"""Menu displayed within the viewmail action."""
entries = [
{"name": "release",
"img": "icon-white icon-ok",
@@ -21,20 +28,6 @@ def viewm_menu(mail_id, rcpt):
"url": reverse('modoboa.extensions.amavis.views.delete', args=[mail_id])
+ ("?rcpt=%s" % rcpt if rcpt else ""),
"label": _("Delete")},
{"name": "process",
"img": "icon-cog",
"menu": [
{"name": "mark-as-spam",
"label": _("Mark as spam"),
"url": reverse(
"modoboa.extensions.amavis.views.mark_as_spam", args=[mail_id]
) + ("?rcpt=%s" % rcpt if rcpt else "")},
{"name": "mark-as-ham",
"label": _("Mark as non-spam"),
"url": reverse(
"modoboa.extensions.amavis.views.mark_as_ham", args=[mail_id]
) + ("?rcpt=%s" % rcpt if rcpt else "")}
]},
{"name": "headers",
"url": reverse(
'modoboa.extensions.amavis.views.viewheaders', args=[mail_id]
@@ -42,6 +35,24 @@ def viewm_menu(mail_id, rcpt):
"label": _("View full headers")},
]
if manual_learning_enabled(user):
entries.insert(1, {
"name": "process",
"img": "icon-cog",
"menu": [
{"name": "mark-as-spam",
"label": _("Mark as spam"),
"url": reverse(
"modoboa.extensions.amavis.views.mark_as_spam", args=[mail_id]
) + ("?rcpt=%s" % rcpt if rcpt else "")},
{"name": "mark-as-ham",
"label": _("Mark as non-spam"),
"url": reverse(
"modoboa.extensions.amavis.views.mark_as_ham", args=[mail_id]
) + ("?rcpt=%s" % rcpt if rcpt else "")}
]
})
menu = render_to_string('common/buttons_list.html',
{"entries": entries, "extraclasses": "pull-left"})
@@ -80,13 +91,14 @@ def viewm_menu_simple(user, mail_id, rcpt, secret_id=""):
@register.simple_tag
def quar_menu():
def quar_menu(user):
"""Render the quarantine listing menu.
: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("amavis/main_action_bar.html", {
"extraopts": extraopts,
"manual_learning": manual_learning_enabled(user)
})
+6 -10
View File
@@ -19,7 +19,7 @@ from modoboa.extensions.amavis.templatetags.amavis_tags import (
)
from .lib import (
selfservice, AMrelease, QuarantineNavigationParameters,
SpamassassinClient
SpamassassinClient, manual_learning_enabled
)
from .models import Msgrcpt
from .sql_connector import get_connector
@@ -57,7 +57,7 @@ def _listing(request):
"ok", listing=content, paginbar=pagination_bar(page), page=page.number
)
if request.session.get('location', 'listing') != 'listing':
ctx['menu'] = quar_menu()
ctx['menu'] = quar_menu(request.user)
request.session['location'] = 'listing'
return render_to_json_response(ctx)
@@ -115,7 +115,7 @@ def viewmail(request, mail_id):
content = Template("""
<iframe src="{{ url }}" id="mailcontent"></iframe>
""").render(Context({"url": reverse(getmailcontent, args=[mail_id])}))
menu = viewm_menu(mail_id, rcpt)
menu = viewm_menu(request.user, mail_id, rcpt)
ctx = getctx("ok", menu=menu, listing=content)
request.session['location'] = 'viewmail'
return render_to_json_response(ctx)
@@ -263,15 +263,11 @@ def mark_messages(request, selection, mtype):
:param str selection: message unique identifier
:param str mtype: type of marking (spam or ham)
"""
if not manual_learning_enabled(request.user):
return render_to_json_response({"status": "ok"})
selection = check_mail_id(request, selection)
connector = get_connector()
per_user_bayesian_filter = parameters.get_admin("PER_USER_BAYESIAN_FILTER")
if per_user_bayesian_filter == "yes" \
and request.user.group == "SimpleUsers":
username = request.user.email
else:
username = None
saclient = SpamassassinClient(username)
saclient = SpamassassinClient(request.user)
for item in selection:
rcpt, mail_id = item.split()
content = "".join(