From 618a9b8c11fc3de2629ea735768565d407d86c39 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 19 May 2014 22:28:25 +0200 Subject: [PATCH] add separate models for mail addresses, domains and mailboxes --- gnuviechadmin/managemails/admin.py | 139 +++++++++++++++++- .../managemails/migrations/0001_initial.py | 94 +++++++++++- .../0002_auto__chg_field_mailbox_password.py | 58 ++++++++ gnuviechadmin/managemails/models.py | 59 +++++++- requirements/base.txt | 1 + 5 files changed, 343 insertions(+), 8 deletions(-) create mode 100644 gnuviechadmin/managemails/migrations/0002_auto__chg_field_mailbox_password.py diff --git a/gnuviechadmin/managemails/admin.py b/gnuviechadmin/managemails/admin.py index 8c38f3f..6aef221 100644 --- a/gnuviechadmin/managemails/admin.py +++ b/gnuviechadmin/managemails/admin.py @@ -1,3 +1,140 @@ +from django.utils.html import format_html from django.contrib import admin +from django import forms +from django.forms.util import flatatt +from django.utils.translation import ugettext as _ -# Register your models here. +from .models import ( + MailDomain, + Mailbox, + MailAddress +) + +PASSWORD_MISMATCH_ERROR = _("Passwords don't match") + + +class ReadOnlyPasswordHashWidget(forms.Widget): + def render(self, name, value, attrs): + final_attrs = self.build_attrs(attrs) + summary = format_html("{0}: {1} ", + _('Hash'), value) + return format_html("{1}", flatatt(final_attrs), summary) + + +class ReadOnlyPasswordHashField(forms.Field): + widget = ReadOnlyPasswordHashWidget + + def __init__(self, *args, **kwargs): + kwargs.setdefault("required", False) + super(ReadOnlyPasswordHashField, self).__init__(*args, **kwargs) + + def bound_data(self, data, initial): + return initial + + def _has_changed(self, initial, data): + return False + + +class MailboxCreationForm(forms.ModelForm): + """ + A form for creating mailboxes. + + """ + password1 = forms.CharField(label=_('Password'), + widget=forms.PasswordInput) + password2 = forms.CharField(label=_('Password (again)'), + widget=forms.PasswordInput) + + class Meta: + model = Mailbox + fields = ('username', 'domain') + + def clean_password2(self): + """ + Check that the two password entries match. + + """ + password1 = self.cleaned_data.get('password1') + password2 = self.cleaned_data.get('password2') + if password1 and password2 and password1 != password2: + raise forms.ValidationError(PASSWORD_MISMATCH_ERROR) + return password2 + + def save(self, commit=True): + """ + Save the provided password in hashed format. + + """ + mailbox = super(MailboxCreationForm, self).save(commit=False) + mailbox.set_password(self.cleaned_data['password1']) + mailbox.uid = 0 + mailbox.gid = 0 + if commit: + mailbox.save() + return mailbox + + +class MailboxChangeForm(forms.ModelForm): + """ + A form for updating mailboxes. + + """ + password = ReadOnlyPasswordHashField() + + class Meta: + model = Mailbox + fields = ('username', 'domain', 'password', 'home', 'uid', 'gid', + 'active') + + def clean_password(self): + return self.initial['password'] + + +class MailboxAdmin(admin.ModelAdmin): + """ + Custom admin page for mailboxes. + + """ + form = MailboxChangeForm + add_form = MailboxCreationForm + + list_display = ('username', 'domain', 'active') + list_filter = ('active',) + fieldsets = ( + (None, { + 'fields': ('username', 'domain', 'password', 'active')}), + (_('System'), { + 'fields': ('home', 'uid', 'gid')}), + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('username', 'domain', 'password1', 'password2')}), + ) + search_fields = ('username', 'domain') + ordering = ('username', 'domain') + filter_horizontal = () + + def get_fieldsets(self, request, obj=None): + if not obj: + return self.add_fieldsets + return super(MailboxAdmin, self).get_fieldsets(request, obj) + + def get_form(self, request, obj=None, **kwargs): + """ + Use special form during mailbox creation. + + """ + defaults = {} + if obj is None: + defaults.update({ + 'form': self.add_form, + 'fields': admin.util.flatten_fieldsets(self.add_fieldsets), + }) + defaults.update(kwargs) + return super(MailboxAdmin, self).get_form(request, obj, **defaults) + + +admin.site.register(MailDomain) +admin.site.register(Mailbox, MailboxAdmin) +admin.site.register(MailAddress) diff --git a/gnuviechadmin/managemails/migrations/0001_initial.py b/gnuviechadmin/managemails/migrations/0001_initial.py index 50429b6..f170f2c 100644 --- a/gnuviechadmin/managemails/migrations/0001_initial.py +++ b/gnuviechadmin/managemails/migrations/0001_initial.py @@ -8,11 +8,18 @@ from django.db import models class Migration(SchemaMigration): def forwards(self, orm): + # Adding model 'MailDomain' + db.create_table(u'managemails_maildomain', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('domain', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)), + )) + db.send_create_signal(u'managemails', ['MailDomain']) + # Adding model 'Mailbox' db.create_table(u'managemails_mailbox', ( (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('username', self.gf('django.db.models.fields.CharField')(max_length=128)), - ('domain', self.gf('django.db.models.fields.CharField')(max_length=128)), + ('username', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)), + ('domain', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['managemails.MailDomain'])), ('password', self.gf('django.db.models.fields.CharField')(max_length=64)), ('home', self.gf('django.db.models.fields.CharField')(max_length=255)), ('uid', self.gf('django.db.models.fields.PositiveSmallIntegerField')()), @@ -21,23 +28,102 @@ class Migration(SchemaMigration): )) db.send_create_signal(u'managemails', ['Mailbox']) + # Adding model 'MailAddress' + db.create_table(u'managemails_mailaddress', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('localpart', self.gf('django.db.models.fields.CharField')(max_length=128)), + ('domain', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['managemails.MailDomain'])), + ('active', self.gf('django.db.models.fields.BooleanField')(default=True)), + )) + db.send_create_signal(u'managemails', ['MailAddress']) + + # Adding unique constraint on 'MailAddress', fields ['localpart', 'domain'] + db.create_unique(u'managemails_mailaddress', ['localpart', 'domain_id']) + + # Adding model 'MailAddressMailbox' + db.create_table(u'managemails_mailaddressmailbox', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('mailaddress', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['managemails.MailAddress'])), + ('mailbox', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['managemails.Mailbox'])), + )) + db.send_create_signal(u'managemails', ['MailAddressMailbox']) + + # Adding unique constraint on 'MailAddressMailbox', fields ['mailaddress', 'mailbox'] + db.create_unique(u'managemails_mailaddressmailbox', ['mailaddress_id', 'mailbox_id']) + + # Adding model 'MailAddressForward' + db.create_table(u'managemails_mailaddressforward', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('mailaddress', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['managemails.MailAddress'])), + ('target', self.gf('django.db.models.fields.EmailField')(max_length=254)), + )) + db.send_create_signal(u'managemails', ['MailAddressForward']) + + # Adding unique constraint on 'MailAddressForward', fields ['mailaddress', 'target'] + db.create_unique(u'managemails_mailaddressforward', ['mailaddress_id', 'target']) + def backwards(self, orm): + # Removing unique constraint on 'MailAddressForward', fields ['mailaddress', 'target'] + db.delete_unique(u'managemails_mailaddressforward', ['mailaddress_id', 'target']) + + # Removing unique constraint on 'MailAddressMailbox', fields ['mailaddress', 'mailbox'] + db.delete_unique(u'managemails_mailaddressmailbox', ['mailaddress_id', 'mailbox_id']) + + # Removing unique constraint on 'MailAddress', fields ['localpart', 'domain'] + db.delete_unique(u'managemails_mailaddress', ['localpart', 'domain_id']) + + # Deleting model 'MailDomain' + db.delete_table(u'managemails_maildomain') + # Deleting model 'Mailbox' db.delete_table(u'managemails_mailbox') + # Deleting model 'MailAddress' + db.delete_table(u'managemails_mailaddress') + + # Deleting model 'MailAddressMailbox' + db.delete_table(u'managemails_mailaddressmailbox') + + # Deleting model 'MailAddressForward' + db.delete_table(u'managemails_mailaddressforward') + models = { + u'managemails.mailaddress': { + 'Meta': {'unique_together': "(('localpart', 'domain'),)", 'object_name': 'MailAddress'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.MailDomain']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'localpart': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + u'managemails.mailaddressforward': { + 'Meta': {'unique_together': "(('mailaddress', 'target'),)", 'object_name': 'MailAddressForward'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mailaddress': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.MailAddress']"}), + 'target': ('django.db.models.fields.EmailField', [], {'max_length': '254'}) + }, + u'managemails.mailaddressmailbox': { + 'Meta': {'unique_together': "(('mailaddress', 'mailbox'),)", 'object_name': 'MailAddressMailbox'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mailaddress': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.MailAddress']"}), + 'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.Mailbox']"}) + }, u'managemails.mailbox': { 'Meta': {'object_name': 'Mailbox'}, 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.MailDomain']"}), 'gid': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), 'home': ('django.db.models.fields.CharField', [], {'max_length': '255'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 'uid': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), - 'username': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}) + }, + u'managemails.maildomain': { + 'Meta': {'object_name': 'MailDomain'}, + 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) } } diff --git a/gnuviechadmin/managemails/migrations/0002_auto__chg_field_mailbox_password.py b/gnuviechadmin/managemails/migrations/0002_auto__chg_field_mailbox_password.py new file mode 100644 index 0000000..deaf3cd --- /dev/null +++ b/gnuviechadmin/managemails/migrations/0002_auto__chg_field_mailbox_password.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'Mailbox.password' + db.alter_column(u'managemails_mailbox', 'password', self.gf('django.db.models.fields.CharField')(max_length=255)) + + def backwards(self, orm): + + # Changing field 'Mailbox.password' + db.alter_column(u'managemails_mailbox', 'password', self.gf('django.db.models.fields.CharField')(max_length=64)) + + models = { + u'managemails.mailaddress': { + 'Meta': {'unique_together': "(('localpart', 'domain'),)", 'object_name': 'MailAddress'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.MailDomain']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'localpart': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + u'managemails.mailaddressforward': { + 'Meta': {'unique_together': "(('mailaddress', 'target'),)", 'object_name': 'MailAddressForward'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mailaddress': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.MailAddress']"}), + 'target': ('django.db.models.fields.EmailField', [], {'max_length': '254'}) + }, + u'managemails.mailaddressmailbox': { + 'Meta': {'unique_together': "(('mailaddress', 'mailbox'),)", 'object_name': 'MailAddressMailbox'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mailaddress': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.MailAddress']"}), + 'mailbox': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.Mailbox']"}) + }, + u'managemails.mailbox': { + 'Meta': {'object_name': 'Mailbox'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['managemails.MailDomain']"}), + 'gid': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'home': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'uid': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}) + }, + u'managemails.maildomain': { + 'Meta': {'object_name': 'MailDomain'}, + 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['managemails'] \ No newline at end of file diff --git a/gnuviechadmin/managemails/models.py b/gnuviechadmin/managemails/models.py index 6e5e9bb..6cf7a81 100644 --- a/gnuviechadmin/managemails/models.py +++ b/gnuviechadmin/managemails/models.py @@ -1,11 +1,64 @@ from django.db import models +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext as _ +from passlib.hash import sha512_crypt + + +@python_2_unicode_compatible +class MailDomain(models.Model): + domain = models.CharField(max_length=128, unique=True) + + class Meta: + verbose_name = _('Mail domain') + verbose_name_plural = _('Mail domains') + + def __str__(self): + return self.domain class Mailbox(models.Model): - username = models.CharField(max_length=128) - domain = models.CharField(max_length=128) - password = models.CharField(max_length=64) + username = models.CharField(max_length=128, unique=True) + domain = models.ForeignKey(MailDomain) + password = models.CharField(max_length=255) home = models.CharField(max_length=255) uid = models.PositiveSmallIntegerField() gid = models.PositiveSmallIntegerField() active = models.BooleanField(default=True) + + class Meta: + verbose_name = _('Mailbox') + verbose_name_plural = _('Mailboxes') + + def set_password(self, password): + self.password = sha512_crypt.encrypt(password) + + +@python_2_unicode_compatible +class MailAddress(models.Model): + localpart = models.CharField(max_length=128) + domain = models.ForeignKey(MailDomain) + active = models.BooleanField(default=True) + + class Meta: + unique_together = ('localpart', 'domain') + verbose_name = _('Mail address') + verbose_name_plural = _('Mail addresses') + + def __str__(self): + return "{0}@{1}".format(self.localpart, self.domain) + + +class MailAddressMailbox(models.Model): + mailaddress = models.ForeignKey(MailAddress) + mailbox = models.ForeignKey(Mailbox) + + class Meta: + unique_together = ('mailaddress', 'mailbox') + + +class MailAddressForward(models.Model): + mailaddress = models.ForeignKey(MailAddress) + target = models.EmailField(max_length=254) + + class Meta: + unique_together = ('mailaddress', 'target') diff --git a/requirements/base.txt b/requirements/base.txt index 51d537e..2cedcab 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,3 +5,4 @@ django-model-utils==2.0.3 logutils==0.3.3 South==0.8.4 psycopg2==2.5.3 +passlib==1.6.2