diff --git a/modoboa/admin/forms.py b/modoboa/admin/forms.py index 452764949..2d26e8c90 100644 --- a/modoboa/admin/forms.py +++ b/modoboa/admin/forms.py @@ -79,6 +79,9 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm): hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False) if hm == "yes": if self.oldname is not None and d.name != self.oldname: + for q in Quota.objects.filter(username__contains="@%s" % self.oldname): + q.username = q.username.replace('@%s' % self.oldname, '@%s' % d.name) + q.save() old_mail_homes = dict((mb.id, mb.mail_home) for mb in d.mailbox_set.all()) d.save() if old_mail_homes is not None: @@ -571,6 +574,9 @@ class AccountFormMail(forms.Form, DynamicForm): old_mail_home = self.mb.mail_home self.mb.domain = domain self.mb.address = locpart + q = Quota.objects.get(username=self.mb.full_address) + q.username = self.mb.full_address + q.save() self.mb.set_quota(self.cleaned_data["quota"], user.has_perm("admin.add_domain")) self.mb.save() if old_mail_home is not None: diff --git a/modoboa/admin/migrations/0023_auto__add_quota.py b/modoboa/admin/migrations/0023_auto__add_quota.py new file mode 100644 index 000000000..51de52372 --- /dev/null +++ b/modoboa/admin/migrations/0023_auto__add_quota.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Quota' + db.create_table('admin_quota', ( + ('username', self.gf('django.db.models.fields.EmailField')(max_length=75, primary_key=True)), + ('bytes', self.gf('django.db.models.fields.IntegerField')(default=0)), + ('messages', self.gf('django.db.models.fields.IntegerField')(default=0)), + )) + db.send_create_signal('admin', ['Quota']) + + + def backwards(self, orm): + # Deleting model 'Quota' + db.delete_table('admin_quota') + + + models = { + 'admin.alias': { + 'Meta': {'ordering': "['domain__name', 'address']", 'unique_together': "(('address', 'domain'),)", 'object_name': 'Alias'}, + 'address': ('django.db.models.fields.CharField', [], {'max_length': '254'}), + 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'extmboxes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mboxes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['admin.Mailbox']", 'symmetrical': 'False'}) + }, + 'admin.domain': { + 'Meta': {'ordering': "['name']", 'object_name': 'Domain'}, + 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'quota': ('django.db.models.fields.IntegerField', [], {}) + }, + 'admin.domainalias': { + 'Meta': {'object_name': 'DomainAlias'}, + 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}) + }, + 'admin.extension': { + 'Meta': {'object_name': 'Extension'}, + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) + }, + 'admin.mailbox': { + 'Meta': {'object_name': 'Mailbox'}, + 'address': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'quota': ('django.db.models.fields.IntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'admin.objectaccess': { + 'Meta': {'unique_together': "(('user', 'content_type', 'object_id'),)", 'object_name': 'ObjectAccess'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'admin.objectdates': { + 'Meta': {'object_name': 'ObjectDates'}, + 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'admin.quota': { + 'Meta': {'object_name': 'Quota'}, + 'bytes': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'messages': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'username': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'primary_key': 'True'}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['admin'] \ No newline at end of file diff --git a/modoboa/admin/migrations/0024_create_quotas.py b/modoboa/admin/migrations/0024_create_quotas.py new file mode 100644 index 000000000..874b776f4 --- /dev/null +++ b/modoboa/admin/migrations/0024_create_quotas.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + for mb in orm["admin.Mailbox"].objects.all(): + orm["admin.Quota"].objects.create(username="%s@%s" % (mb.address, mb.domain.name)) + + def backwards(self, orm): + "Write your backwards methods here." + orm["admin.Quota"].objects.all().delete() + + models = { + 'admin.alias': { + 'Meta': {'ordering': "['domain__name', 'address']", 'unique_together': "(('address', 'domain'),)", 'object_name': 'Alias'}, + 'address': ('django.db.models.fields.CharField', [], {'max_length': '254'}), + 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'extmboxes': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mboxes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['admin.Mailbox']", 'symmetrical': 'False'}) + }, + 'admin.domain': { + 'Meta': {'ordering': "['name']", 'object_name': 'Domain'}, + 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'quota': ('django.db.models.fields.IntegerField', [], {}) + }, + 'admin.domainalias': { + 'Meta': {'object_name': 'DomainAlias'}, + 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}) + }, + 'admin.extension': { + 'Meta': {'object_name': 'Extension'}, + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) + }, + 'admin.mailbox': { + 'Meta': {'object_name': 'Mailbox'}, + 'address': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'dates': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.ObjectDates']"}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'quota': ('django.db.models.fields.IntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'admin.objectaccess': { + 'Meta': {'unique_together': "(('user', 'content_type', 'object_id'),)", 'object_name': 'ObjectAccess'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'admin.objectdates': { + 'Meta': {'object_name': 'ObjectDates'}, + 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'admin.quota': { + 'Meta': {'object_name': 'Quota'}, + 'bytes': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'messages': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'username': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'primary_key': 'True'}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['admin'] + symmetrical = True diff --git a/modoboa/admin/models.py b/modoboa/admin/models.py index caa8075a6..4659e3a46 100644 --- a/modoboa/admin/models.py +++ b/modoboa/admin/models.py @@ -723,6 +723,10 @@ class Mailbox(DatesAware): else: self.quota = value + def get_cur_quota(self): + q = Quota.objects.get(username=self.full_address) + return q.bytes / (self.quota * 1048576) * 100 + def save_from_user(self, localpart, domain, user, quota=None, owner=None): """Simple save method called for automatic creations @@ -736,7 +740,11 @@ class Mailbox(DatesAware): self.set_quota(quota, True if (owner and owner.has_perm("admin.add_domain")) else False) super(Mailbox, self).save() + Quota.objects.create(username=self.full_address) + def delete(self, keepdir=False): + q = Quota.objects.get(username=self.full_address) + q.delete() super(Mailbox, self).delete() if not keepdir: self.delete_dir() @@ -761,6 +769,13 @@ def mailbox_deleted_handler(sender, **kwargs): if alias.mboxes.count() == 0: alias.delete() + +class Quota(models.Model): + username = models.EmailField(primary_key=True) + bytes = models.IntegerField(default=0) + messages = models.IntegerField(default=0) + + class Alias(DatesAware): address = models.CharField( ugettext_lazy('address'), max_length=254, diff --git a/modoboa/admin/static/admin/css/admin.css b/modoboa/admin/static/admin/css/admin.css index 7a48f9cda..a28919dd9 100644 --- a/modoboa/admin/static/admin/css/admin.css +++ b/modoboa/admin/static/admin/css/admin.css @@ -18,6 +18,10 @@ table td[name="actions"] { text-align: right; } +table .progress { + margin-bottom: 0 !important; +} + .search { width: 85%; -webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px; diff --git a/modoboa/admin/static/admin/js/admin.js b/modoboa/admin/static/admin/js/admin.js index 859de7edf..3db113d36 100644 --- a/modoboa/admin/static/admin/js/admin.js +++ b/modoboa/admin/static/admin/js/admin.js @@ -185,6 +185,17 @@ Identities.prototype = { } }, + listen: function() { + Admin.prototype.listen.call(this); + $(document).on("click", "a.ajaxlink", $.proxy(this.page_loader, this)); + }, + + page_loader: function(e) { + var $link = get_target(e); + e.preventDefault(); + this.navobj.baseurl($link.attr("href")).update(); + }, + list_cb: function(data) { Admin.prototype.list_cb.call(this, data); var deloptions = {}; diff --git a/modoboa/admin/templates/admin/quotas.html b/modoboa/admin/templates/admin/quotas.html new file mode 100644 index 000000000..77f8170b2 --- /dev/null +++ b/modoboa/admin/templates/admin/quotas.html @@ -0,0 +1,19 @@ +{% load i18n libextras %} + + + + + + + + {% for mbox in mboxes %} + + + + {% endfor %} + +
{% trans "Address" %}{% trans "Current quota" %}{% trans "Actions" %}
{{ mbox.full_address }} +
+
+
+
diff --git a/modoboa/admin/templatetags/admin_extras.py b/modoboa/admin/templatetags/admin_extras.py index 625b2c791..752eb84e4 100644 --- a/modoboa/admin/templatetags/admin_extras.py +++ b/modoboa/admin/templatetags/admin_extras.py @@ -102,6 +102,16 @@ def domains_menu(selection, user): @register.simple_tag def identities_menu(user): entries = [ + {"name": "identities", + "label": _("List identities"), + "img": "icon-user", + "class": "ajaxlink", + "url": "list/"}, + {"name": "quotas", + "label": _("List quotas"), + "img": "icon-hdd", + "class": "ajaxlink", + "url": "quotas/"}, {"name" : "newaccount", "label" : _("Add account"), "img" : "icon-plus", diff --git a/modoboa/admin/urls.py b/modoboa/admin/urls.py index 5b2e6ce70..50f6582a2 100644 --- a/modoboa/admin/urls.py +++ b/modoboa/admin/urls.py @@ -23,6 +23,7 @@ urlpatterns = patterns( (r'^identities/$', 'identities'), (r'^identities/list/$', '_identities'), + (r'^identities/quotas/$', 'list_quotas'), (r'^identities/import/$', 'import_identities'), (r'^identities/export/$', 'export_identities'), diff --git a/modoboa/admin/views.py b/modoboa/admin/views.py index 84f9ac32e..99a808b88 100644 --- a/modoboa/admin/views.py +++ b/modoboa/admin/views.py @@ -340,6 +340,19 @@ def mboxes_list(request): mboxes = request.user.get_mailboxes() return ajax_simple_response([mb.full_address for mb in mboxes]) + +@login_required +@permission_required("admin.add_mailbox") +def list_quotas(request, tplname="admin/quotas.html"): + mboxes = request.user.get_mailboxes() + return ajax_simple_response({ + "status": "ok", + "table": _render_to_string(request, tplname, { + "mboxes": mboxes + }) + }) + + @login_required @permission_required("auth.add_user") @transaction.commit_on_success