Models modification for better integration with third party softwares

(ie. Postfix and others).
Some bugfixes.
Added documentation.
This commit is contained in:
Antoine Nguyen
2009-03-26 18:20:19 +01:00
parent d234b308b7
commit 6c67aa7093
11 changed files with 203 additions and 120 deletions
+15 -4
View File
@@ -36,19 +36,28 @@ http://<youraddress>:8000/mailng/admin/.
Tips to link Mailng with postfix
--------------------------------
First, define following maps on your server::
First, define following maps on your server (this should work with
Postfix versions >= 2.2)::
# /etc/postfix/sql-domains.cf
user = <user>
password = <password>
dbname = <database>
query = SELECT name FROM domains WHERE name='%s' AND enabled='1'
query = SELECT name FROM admin_domain WHERE name='%s' AND enabled='1'
# /etc/postfix/sql-mailboxes.cf
user = <user>
password = <password>
dbname = <database>
query = SELECT path FROM mailboxes WHERE address = '%s' AND enabled = '1'
query = SELECT path FROM admin_mailbox WHERE full_address = '%s' AND enabled = '1'
# /etc/postfix/sql-aliases.cf
user = <user>
password = <password>
dbname = <database>
query = SELECT t2.full_address FROM admin_alias AS t1, admin_mailbox AS t2
WHERE t1.full_address='%s' AND t1.mbox=t2.id
AND t1.enabled='1'
Then, use the following configuration in your /etc/postfix/main.cf file
(this is one possible configuration)::
@@ -65,6 +74,8 @@ Then, use the following configuration in your /etc/postfix/main.cf file
relay_domains = mysql:/etc/postfix/sql-domains.cf
virtual_mailbox_domains = mysql:/etc/postfix/sql-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/sql-mailboxes.cf
virtual_alias_domains =
virtual_alias_maps = mysql:/etc/postfix/sql-aliases.cf
# Stuff after
NB: Mailng only supports maildir format for now. Others will surely be
added soon.
+3 -1
View File
@@ -6,13 +6,14 @@ class DomainForm(forms.ModelForm):
model = Domain
class MailboxForm(forms.ModelForm):
quota = forms.IntegerField(label="Quota", required=False)
password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
password2 = forms.CharField(label="Confirmation", widget=forms.PasswordInput)
enabled = forms.BooleanField(label="Enabled", required=False)
class Meta:
model = Mailbox
fields = ('name', 'address', 'quota')
fields = ('name', 'address')
def clean_password2(self):
if self.cleaned_data["password1"] != self.cleaned_data["password2"]:
@@ -31,4 +32,5 @@ class AliasForm(forms.ModelForm):
class Meta:
model = Alias
fields = ('address', 'mbox', 'enabled')
+60 -4
View File
@@ -1,30 +1,86 @@
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
from django.conf import settings
from mailng.lib import exec_pipe
import os
class Domain(models.Model):
name = models.CharField(max_length=100)
quota = models.IntegerField()
enabled = models.BooleanField()
def create_dir(self):
code, output = exec_pipe("sudo -u %s mkdir %s/%s" \
% (settings.VIRTUAL_UID, settings.STORAGE_PATH,
self.name))
if code:
os.system("echo '%s' >> /tmp/vmail.log" % output)
return False
return True
def rename_dir(self, newname):
code, output = exec_pipe("sudo -u %s mv %s/%s %s/%s" \
% (settings.VIRTUAL_UID,
settings.STORAGE_PATH, self.name,
settings.STORAGE_PATH, newname))
if code:
os.system("echo '%s' >> /tmp/vmail.log" % output)
return False
return True
def delete_dir(self):
code, output = exec_pipe("sudo -u %s rm -r %s/%s" \
% (settings.VIRTUAL_UID, settings.STORAGE_PATH,
self.name))
if code:
return False
return True
class Mailbox(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
# password = models.CharField(max_length=100)
full_address = models.CharField(max_length=150)
quota = models.IntegerField()
# enabled = models.BooleanField()
uid = models.IntegerField()
gid = models.IntegerField()
path = models.CharField(max_length=200)
domain = models.ForeignKey(Domain)
user = models.ForeignKey(User)
path = models.CharField(max_length=255)
def __str__(self):
return "%s" % (self.address)
def create_dir(self, domain):
path = "%s/%s/%s" % (settings.STORAGE_PATH, domain.name, self.address)
code, output = exec_pipe("sudo -u %s mkdir -p %s/.maildir" \
% (settings.VIRTUAL_UID, path))
if code:
return False
self.path = "%s/.maildir/" % path
return True
def rename_dir(self, domain, newaddress):
path = "%s/%s" % (settings.STORAGE_PATH, domain)
code, output = exec_pipe("sudo -u %s mv %s/%s %s/%s" \
% (settings.VIRTUAL_UID,
path, self.address, path, newaddress))
if code:
os.system("echo '%s' >> /tmp/vmail.log" % output)
return False
self.path = "%s/%s/.maildir/" % (path, newaddress)
return True
def delete_dir(self):
code, output = exec_pipe("sudo -u %s rm -r %s/%s/%s" \
% (settings.VIRTUAL_UID, settings.STORAGE_PATH,
self.domain.name, self.name))
if code:
return False
return True
class Alias(models.Model):
address = models.CharField(max_length=100)
full_address = models.CharField(max_length=150)
mbox = models.ForeignKey(Mailbox)
enabled = models.BooleanField()
+86 -41
View File
@@ -8,7 +8,6 @@ from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from mailng import admin
from mailng.lib.fsops import *
from mailng.admin.models import Domain, Mailbox, Alias
from forms import MailboxForm, DomainForm, AliasForm
@@ -41,7 +40,8 @@ def newdomain(request):
if Domain.objects.filter(name=request.POST["name"]):
error = "Domain with this name already defined"
else:
if create_domain(request.POST["name"]):
domain = form.save(commit=False)
if domain.create_dir():
form.save()
ctx = _ctx_ok(reverse(admin.views.domains))
return HttpResponse(simplejson.dumps(ctx),
@@ -62,12 +62,20 @@ def editdomain(request, dom_id):
domain = Domain.objects.get(pk=dom_id)
if request.method == "POST":
form = DomainForm(request.POST, instance=domain)
error = None
if form.is_valid():
form.save()
ctx = _ctx_ok(reverse(admin.views.domains))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
if domain.name != request.POST["name"]:
if Domain.objects.filter(name=request.POST["name"]):
error = "Domain with this name already defined"
else:
if not domain.rename_dir(request.POST["name"]):
error = "Failed to rename domain, check permissions"
if not error:
form.save()
ctx = _ctx_ok(reverse(admin.views.domains))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
ctx = _ctx_ko("admin/editdomain.html", {
"domain" : domain, "form" : form
"domain" : domain, "form" : form, "error" : error
})
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@@ -79,7 +87,7 @@ def editdomain(request, dom_id):
@login_required
def deldomain(request, dom_id):
domain = Domain.objects.get(pk=dom_id)
destroy_domain(domain.name)
domain.delete_dir()
domain.delete()
return HttpResponseRedirect('/mailng/admin/')
@@ -102,8 +110,8 @@ def newmailbox(request, dom_id=None):
domain=dom_id):
error = "Mailbox with this address already exists"
else:
if create_mailbox(domain.name, request.POST["address"]):
mb = form.save(commit=False)
mb = form.save(commit=False)
if mb.create_dir(domain):
user = User()
user.username = user.email = "%s@%s" % (mb.address, domain.name)
user.set_password(request.POST["password1"])
@@ -111,13 +119,16 @@ def newmailbox(request, dom_id=None):
and True or False
fname, lname = mb.name.split()
user.first_name = fname
user.last_name = lname
user.save()
mb.user = user
mb.uid = mb.gid = 500
mb.domain = domain
mb.quota = request.POST["quota"]
if not mb.quota:
mb.quota = domain.quota
mb.full_address = "%s@%s" % (mb.address, domain.name)
mb.save()
ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[domain.id]))
return HttpResponse(simplejson.dumps(ctx),
@@ -138,22 +149,39 @@ def editmailbox(request, dom_id, mbox_id=None):
mb = Mailbox.objects.get(pk=mbox_id)
if request.method == "POST":
form = MailboxForm(request.POST, instance=mb)
error = None
if form.is_valid():
if request.POST["enabled"] != mb.user.is_active \
or request.POST["password1"] != mb.user.password:
mb.user.is_active = request.POST["enabled"]
mb.user.password = request.POST["password1"]
mb.user.save()
form.save()
ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[dom_id]))
else:
ctx = _ctx_ko("admin/editmailbox.html", {
"mbox" : mb, "form" : form
})
json = simplejson.dumps(ctx)
return HttpResponse(json, mimetype="application/json")
if mb.address != request.POST["address"]:
if Mailbox.objects.filter(address=request.POST["address"],
domain=dom_id):
error = "Mailbox with this address already exists"
else:
if not mb.rename_dir(mb.domain.name, request.POST["address"]):
error = "Failed to rename mailbox, check permissions"
if not error:
mb = form.save(commit=False)
enabled = request.POST.has_key("enabled") \
and request.POST["enabled"] or False
if enabled != mb.user.is_active \
or request.POST["password1"] != mb.user.password:
mb.user.is_active = enabled
mb.user.password = request.POST["password1"]
mb.user.save()
mb.quota = request.POST["quota"]
if not mb.quota:
mb.quota = mb.domain.quota
mb.full_address = "%s@%s" % (mb.address, mb.domain.name)
mb.save()
ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[dom_id]))
return HttpResponse(simplejson.dumps(ctx),
mimetype="application/json")
ctx = _ctx_ko("admin/editmailbox.html", {
"mbox" : mb, "form" : form, "error" : error
})
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
form = MailboxForm(instance=mb)
form.fields['quota'].initial = mb.quota
form.fields['enabled'].initial = mb.user.is_active
form.fields['password1'].initial = mb.user.password
form.fields['password2'].initial = mb.user.password
@@ -164,7 +192,8 @@ def editmailbox(request, dom_id, mbox_id=None):
@login_required
def delmailbox(request, dom_id, mbox_id=None):
mb = Mailbox.objects.get(pk=mbox_id)
destroy_mailbox(mb.domain.name, mb.address)
mb.delete_dir()
mb.user.delete()
mb.delete()
return HttpResponseRedirect(reverse(admin.views.mailboxes,
args=[dom_id]))
@@ -188,15 +217,22 @@ def newalias(request, dom_id, mbox_id):
mbox = Mailbox.objects.get(pk=mbox_id)
if request.method == "POST":
form = AliasForm(request.POST)
error = None
if form.is_valid():
form.save()
ctx = _ctx_ok(reverse(admin.views.aliases, args=[mbox.domain.id]))
else:
ctx = _ctx_ko("admin/newalias.html", {
"mbox" : mbox, "form" : form
})
json = simplejson.dumps(ctx)
return HttpResponse(json, mimetype="application/json")
if Alias.objects.filter(address=request.POST["address"],
mbox=mbox_id):
error = "Alias with this address already exists"
else:
alias = form.save(commit=False)
alias.full_address = "%s@%s" % (alias.address, mbox.domain.name)
alias.save()
ctx = _ctx_ok(reverse(admin.views.aliases, args=[mbox.domain.id]))
return HttpResponse(simplejson.dumps(ctx),
mimetype="application/json")
ctx = _ctx_ko("admin/newalias.html", {
"mbox" : mbox, "form" : form, "error" : error
})
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
form = AliasForm({"mbox" : mbox_id}, domain=mbox.domain)
return render_to_response('admin/newalias.html', {
@@ -208,15 +244,24 @@ def editalias(request, dom_id, alias_id):
alias = Alias.objects.get(pk=alias_id)
if request.method == "POST":
form = AliasForm(request.POST, instance=alias)
error = None
if form.is_valid():
form.save()
ctx = _ctx_ok(reverse(admin.views.aliases, args=[dom_id]))
else:
ctx = _ctx_ko("admin/editalias.html", {
"alias" : alias, "form" : form
})
json = simplejson.dumps(ctx)
return HttpResponse(json, mimetype="application/json")
if alias.address != request.POST["address"] \
and Alias.objects.filter(address=request.POST["address"],
mbox=alias.mbox.id):
error = "Alias with this address already exists"
else:
alias = form.save(commit=False)
alias.full_address = "%s@%s" % (alias.address,
alias.mbox.domain.name)
alias.save()
ctx = _ctx_ok(reverse(admin.views.aliases, args=[dom_id]))
return HttpResponse(simplejson.dumps(ctx),
mimetype="application/json")
ctx = _ctx_ko("admin/editalias.html", {
"alias" : alias, "form" : form, "error" : error
})
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
form = AliasForm(instance=alias)
return _render(request, 'admin/editalias.html', {
"form" : form, "alias" : alias
+34
View File
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
import popen2
import os
import time
def exec_pipe(cmd):
"""Exécute une commande et récupère la sortie générée
Le module popen2 est utilisé pour l'exécution de la commande. Un
objet Popen4 est créé car il regroupe la sortie standard et la sortie
d'erreur dans le même fd.
De plus, popen2 est le seul à pouvoir capturer retourner le code de
sortie de la commande.
cmd -- commande à exécuter
Retourne un couple (code de sortie, texte de sortie)
"""
child = popen2.Popen4(cmd)
child.tochild.close()
while True:
ret = child.poll()
if ret != -1:
break
time.sleep(0.001)
output = child.fromchild.read()
if not ret:
code = 0
else:
if os.WIFEXITED(ret):
code = os.WEXITSTATUS(ret), output
else:
code = -1
return code, output
-69
View File
@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
import popen2
import os
import time
from django.conf import settings
def exec_pipe(cmd):
"""Exécute une commande et récupère la sortie générée
Le module popen2 est utilisé pour l'exécution de la commande. Un
objet Popen4 est créé car il regroupe la sortie standard et la sortie
d'erreur dans le même fd.
De plus, popen2 est le seul à pouvoir capturer retourner le code de
sortie de la commande.
cmd -- commande à exécuter
Retourne un couple (code de sortie, texte de sortie)
"""
child = popen2.Popen4(cmd)
child.tochild.close()
while True:
ret = child.poll()
if ret != -1:
break
time.sleep(0.001)
output = child.fromchild.read()
if not ret:
code = 0
else:
if os.WIFEXITED(ret):
code = os.WEXITSTATUS(ret), output
else:
code = -1
return code, output
def create_domain(name):
code, output = exec_pipe("sudo -u %s mkdir %s/%s" \
% (settings.VIRTUAL_UID, settings.STORAGE_PATH,
name))
if code:
os.system("echo '%s' >> /tmp/vmail.log" % output)
return False
return True
def destroy_domain(name):
code, output = exec_pipe("sudo -u %s rm -r %s/%s" \
% (settings.VIRTUAL_UID, settings.STORAGE_PATH,
name))
if code:
return False
return True
def create_mailbox(domain, name):
code, output = exec_pipe("sudo -u %s mkdir -p %s/%s/%s/.maildir" \
% (settings.VIRTUAL_UID, settings.STORAGE_PATH,
domain, name))
if code:
return False
return True
def destroy_mailbox(domain, name):
code, output = exec_pipe("sudo -u %s rm -r %s/%s/%s" \
% (settings.VIRTUAL_UID, settings.STORAGE_PATH,
domain, name))
if code:
return False
return True
+1 -1
View File
@@ -23,6 +23,7 @@
rel="{handler:'iframe',size:{x:300,y:190}}">
<img src="/static/pics/edit.png" border="0" title="Edit alias"/>
</a>
{% url admin.views.delalias domain.id alias.id as del_url %}
<a href="{{ del_url }}" name="deleteAlias">
<img src="/static/pics/remove.png" border="0" title="Delete alias" />
</a>
@@ -30,7 +31,6 @@
<td>{{ alias.address }}@{{ alias.mbox.domain.name }}</td>
<td>{{ alias.mbox.address }}@{{ alias.mbox.domain.name }}</td>
<td>{{ alias.enabled|yesno }}</td
{% url admin.views.delalias domain.id alias.id as del_url %}
</tr>
{% endfor %}
</table>
+1
View File
@@ -4,6 +4,7 @@
{% endblock %}
{% block content %}
<div class="title"><h1>Alias {{ alias.address }}</h1></div>
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.editalias alias.mbox.domain.id alias.id as edit_url %}
<form action="{{ edit_url }}" method="POST" id="aliasform" name="aliasform">
{% include "admin/genericform.html" %}
+1
View File
@@ -5,6 +5,7 @@
{% block content %}
<div class="title"><h1>Domain {{ domain.name }}</h1></div>
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.editdomain domain.id as dom_url %}
<form action="{{ dom_url }}" method="post" name="domform" id="domform">
{% include "admin/genericform.html" %}
+1
View File
@@ -5,6 +5,7 @@
{% block content %}
<div class="title"><h1>Mailbox {{ mbox.name }}</h1></div>
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.editmailbox mbox.domain_id mbox.id as box_url %}
<form action="{{ box_url }}" method="POST" id="mboxform" name="mboxform">
{% include "admin/genericform.html" %}
+1
View File
@@ -4,6 +4,7 @@
{% endblock %}
{% block content %}
<div class="title"><h1>New alias</h1></div>
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.newalias mbox.domain_id mbox.id as box_url %}
<form action="{{ box_url }}" method="POST" id="aliasform" name="aliasform">
{% include "admin/genericform.html" %}