Revision c7c0ec58

b/snf-astakos-app/astakos/im/functions.py
54 54
from datetime import datetime
55 55
from functools import wraps
56 56

  
57
import astakos.im.settings as astakos_settings
57 58
from astakos.im.settings import (
58 59
    DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
59 60
    VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
......
67 68
from astakos.im.notifications import build_notification, NotificationError
68 69
from astakos.im.models import (
69 70
    AstakosUser, Invitation, ProjectMembership, ProjectApplication, Project,
71
    UserSetting,
70 72
    PendingMembershipError, get_resource_names, new_chain)
71 73
from astakos.im.project_notif import (
72 74
    membership_change_notify, membership_enroll_notify,
......
694 696
            m = _(astakos_messages.NOT_ALLOWED)
695 697
            raise PermissionDenied(m)
696 698

  
699
    reached, limit = reached_pending_application_limit(request_user.id, precursor)
700
    if reached:
701
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
702
        raise PermissionDenied(m)
703

  
697 704
    application = ProjectApplication(**kw)
698 705

  
699 706
    if precursor is None:
......
808 815
            raise Http404
809 816
        else:
810 817
            return None, application
818

  
819

  
820
def get_user_setting(user_id, key):
821
    try:
822
        setting = UserSetting.objects.get(
823
            user=user_id, setting=key)
824
        return setting.value
825
    except UserSetting.DoesNotExist:
826
        return getattr(astakos_settings, key)
827

  
828

  
829
def set_user_setting(user_id, key, value):
830
    try:
831
        setting = UserSetting.objects.get_for_update(
832
            user=user_id, setting=key)
833
    except UserSetting.DoesNotExist:
834
        setting = UserSetting(user_id=user_id, setting=key)
835
    setting.value = value
836
    setting.save()
837

  
838

  
839
def unset_user_setting(user_id, key):
840
    UserSetting.objects.filter(user=user_id, setting=key).delete()
841

  
842

  
843
PENDING_APPLICATION_LIMIT_SETTING = 'PENDING_APPLICATION_LIMIT'
844

  
845
def get_pending_application_limit(user_id):
846
    key = PENDING_APPLICATION_LIMIT_SETTING
847
    return get_user_setting(user_id, key)
848

  
849

  
850
def set_pending_application_limit(user_id, value):
851
    key = PENDING_APPLICATION_LIMIT_SETTING
852
    return set_user_setting(user_id, key, value)
853

  
854

  
855
def unset_pending_application_limit(user_id):
856
    key = PENDING_APPLICATION_LIMIT_SETTING
857
    return unset_user_setting(user_id, key)
858

  
859

  
860
def _reached_pending_application_limit(user_id):
861
    limit = get_pending_application_limit(user_id)
862

  
863
    PENDING = ProjectApplication.PENDING
864
    pending = ProjectApplication.objects.filter(
865
        applicant__id=user_id, state=PENDING).count()
866

  
867
    return pending >= limit, limit
868

  
869

  
870
def reached_pending_application_limit(user_id, precursor=None):
871
    reached, limit = _reached_pending_application_limit(user_id)
872

  
873
    if precursor is None:
874
        return reached, limit
875

  
876
    chain = precursor.chain
877
    objs = ProjectApplication.objects
878
    q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
879
    has_pending = q.exists()
880

  
881
    if not has_pending:
882
        return reached, limit
883

  
884
    return False, limit
b/snf-astakos-app/astakos/im/management/commands/user-show.py
71 71
            for resource, limits in quotas.iteritems():
72 72
                showable_quotas[resource] = limits.capacity
73 73

  
74
            settings_dict = {}
75
            settings = user.settings()
76
            for setting in settings:
77
                settings_dict[setting.setting] = setting.value
78

  
74 79
            kv = OrderedDict(
75 80
                [
76 81
                    ('id', user.id),
......
97 102
                    ('email_verified', user.email_verified),
98 103
                    ('username', user.username),
99 104
                    ('activation_sent_date', user.activation_sent),
100
                    ('resources', showable_quotas),
101 105
                ])
102 106

  
107
            if settings_dict:
108
                kv['settings'] = settings_dict
109

  
110
            kv['resources'] = showable_quotas
111

  
103 112
            if get_latest_terms():
104 113
                has_signed_terms = user.signed_terms
105 114
                kv['has_signed_terms'] = has_signed_terms
b/snf-astakos-app/astakos/im/management/commands/user-update.py
34 34
from optparse import make_option
35 35
from datetime import datetime
36 36

  
37
from django.utils.translation import ugettext as _
37 38
from django.core.management.base import BaseCommand, CommandError
38 39
from django.contrib.auth.models import Group
39 40
from django.core.exceptions import ValidationError
40 41

  
41 42
from astakos.im.models import AstakosUser
42
from astakos.im.functions import activate, deactivate
43
from astakos.im.functions import (activate, deactivate,
44
                                  set_pending_application_limit,
45
                                  unset_pending_application_limit)
43 46
from ._common import remove_user_permission, add_user_permission
44 47

  
45 48

  
......
102 105
        make_option('--delete-permission',
103 106
                    dest='delete-permission',
104 107
                    help="Delete user permission"),
108
        make_option('--set-max-pending',
109
                    dest='pending',
110
                    metavar='INT',
111
                    help=("Set limit on user's maximum pending "
112
                          "project applications")),
113
        make_option('--unset-max-pending',
114
                    dest='unset_pending',
115
                    action='store_true',
116
                    default=False,
117
                    help=("Restore default limit of user's maximum pending "
118
                          "project applications")),
105 119
    )
106 120

  
107 121
    def handle(self, *args, **options):
......
109 123
            raise CommandError("Please provide a user ID")
110 124

  
111 125
        if args[0].isdigit():
112
            user = AstakosUser.objects.get(id=int(args[0]))
126
            user_id = int(args[0])
127
            user = AstakosUser.objects.get(id=user_id)
113 128
        else:
114 129
            raise CommandError("Invalid ID")
115 130

  
......
202 217

  
203 218
        if password:
204 219
            self.stdout.write('User\'s new password: %s\n' % password)
220

  
221
        pending = options.get('pending')
222
        if pending:
223
            try:
224
                pending = int(pending)
225
            except ValueError as e:
226
                m = _("Expected integer argument")
227
                raise CommandError(m)
228
            else:
229
                set_pending_application_limit(user_id, pending)
230

  
231
        unset_pending = options.get('unset_pending')
232
        if unset_pending:
233
            unset_pending_application_limit(user_id)
b/snf-astakos-app/astakos/im/messages.py
194 194
APPLICATION_CANNOT_CANCEL               =   "Cannot cancel application %s in state '%s'"
195 195
APPLICATION_CANCELLED                   =   "Your project request has been cancelled."
196 196

  
197
REACHED_PENDING_APPLICATION_LIMIT = ("You have reached the maximum number "
198
                                     "of pending project applications: %s.")
199

  
200
PENDING_APPLICATION_LIMIT_ADD = \
201
    ("You are not allowed to create a new project "
202
     "because you have reached the maximum [%s] for "
203
     "pending project applications. "
204
     "Consider cancelling any unnecessary ones.")
205

  
206
PENDING_APPLICATION_LIMIT_MODIFY = \
207
    ("You are not allowed to modify this project "
208
     "because you have reached the maximum [%s] for "
209
     "pending project applications. "
210
     "Consider cancelling any unnecessary ones.")
197 211

  
198 212
# Auth providers messages
199 213
AUTH_PROVIDER_NOT_ACTIVE                     =   "'%(provider)s' is disabled."
b/snf-astakos-app/astakos/im/migrations/0023_auto__add_usersetting__add_unique_usersetting_user_setting.py
1
# encoding: utf-8
2
import datetime
3
from south.db import db
4
from south.v2 import SchemaMigration
5
from django.db import models
6

  
7
class Migration(SchemaMigration):
8

  
9
    def forwards(self, orm):
10
        
11
        # Adding model 'UserSetting'
12
        db.create_table('im_usersetting', (
13
            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14
            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['im.AstakosUser'])),
15
            ('setting', self.gf('django.db.models.fields.CharField')(max_length=255)),
16
            ('value', self.gf('django.db.models.fields.IntegerField')()),
17
        ))
18
        db.send_create_signal('im', ['UserSetting'])
19

  
20
        # Adding unique constraint on 'UserSetting', fields ['user', 'setting']
21
        db.create_unique('im_usersetting', ['user_id', 'setting'])
22

  
23

  
24
    def backwards(self, orm):
25
        
26
        # Removing unique constraint on 'UserSetting', fields ['user', 'setting']
27
        db.delete_unique('im_usersetting', ['user_id', 'setting'])
28

  
29
        # Deleting model 'UserSetting'
30
        db.delete_table('im_usersetting')
31

  
32

  
33
    models = {
34
        'auth.group': {
35
            'Meta': {'object_name': 'Group'},
36
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
37
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
38
            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
39
        },
40
        'auth.permission': {
41
            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
42
            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
43
            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
44
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
45
            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
46
        },
47
        'auth.user': {
48
            'Meta': {'object_name': 'User'},
49
            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
50
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
51
            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
52
            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
53
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
54
            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
55
            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
56
            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
57
            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
58
            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
59
            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
60
            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
61
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
62
        },
63
        'contenttypes.contenttype': {
64
            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
65
            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
66
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67
            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
68
            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
69
        },
70
        'im.additionalmail': {
71
            'Meta': {'object_name': 'AdditionalMail'},
72
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
73
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
75
        },
76
        'im.approvalterms': {
77
            'Meta': {'object_name': 'ApprovalTerms'},
78
            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
79
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
80
            'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
81
        },
82
        'im.astakosuser': {
83
            'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
84
            'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
85
            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
86
            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
87
            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
88
            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
89
            'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
90
            'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
91
            'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
92
            'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
93
            'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
94
            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
95
            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
96
            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
97
            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
98
            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
99
            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
100
            'updated': ('django.db.models.fields.DateTimeField', [], {}),
101
            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
102
            'uuid': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'})
103
        },
104
        'im.astakosuserauthprovider': {
105
            'Meta': {'ordering': "('module', 'created')", 'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'},
106
            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
107
            'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
108
            'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}),
109
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
110
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
111
            'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
112
            'info_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
113
            'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}),
114
            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"})
115
        },
116
        'im.astakosuserquota': {
117
            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
118
            'capacity': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'max_digits': '38', 'decimal_places': '0'}),
119
            'export_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
120
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
121
            'import_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
122
            'quantity': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '0', 'max_digits': '38', 'decimal_places': '0'}),
123
            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
124
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
125
        },
126
        'im.chain': {
127
            'Meta': {'object_name': 'Chain'},
128
            'chain': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
129
        },
130
        'im.emailchange': {
131
            'Meta': {'object_name': 'EmailChange'},
132
            'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
133
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
134
            'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
135
            'requested_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
136
            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchanges'", 'unique': 'True', 'to': "orm['im.AstakosUser']"})
137
        },
138
        'im.invitation': {
139
            'Meta': {'object_name': 'Invitation'},
140
            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
141
            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
142
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
143
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
144
            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
145
            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
146
            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
147
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
148
        },
149
        'im.pendingthirdpartyuser': {
150
            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'},
151
            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
152
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
153
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
154
            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
155
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
156
            'info': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
157
            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
158
            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
159
            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
160
            'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
161
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
162
        },
163
        'im.project': {
164
            'Meta': {'object_name': 'Project'},
165
            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}),
166
            'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
167
            'deactivation_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
168
            'deactivation_reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
169
            'id': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'chained_project'", 'unique': 'True', 'primary_key': 'True', 'db_column': "'id'", 'to': "orm['im.Chain']"}),
170
            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
171
            'is_modified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
172
            'last_approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
173
            'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}),
174
            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True', 'null': 'True', 'db_index': 'True'}),
175
            'state': ('django.db.models.fields.IntegerField', [], {'default': '1', 'db_index': 'True'})
176
        },
177
        'im.projectapplication': {
178
            'Meta': {'unique_together': "(('chain', 'id'),)", 'object_name': 'ProjectApplication'},
179
            'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_applied'", 'to': "orm['im.AstakosUser']"}),
180
            'chain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'chained_apps'", 'db_column': "'chain'", 'to': "orm['im.Chain']"}),
181
            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
182
            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
183
            'end_date': ('django.db.models.fields.DateTimeField', [], {}),
184
            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True'}),
185
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
186
            'issue_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
187
            'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
188
            'member_join_policy': ('django.db.models.fields.IntegerField', [], {}),
189
            'member_leave_policy': ('django.db.models.fields.IntegerField', [], {}),
190
            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
191
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_owned'", 'to': "orm['im.AstakosUser']"}),
192
            'precursor_application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectApplication']", 'null': 'True', 'blank': 'True'}),
193
            'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}),
194
            'response_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
195
            'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
196
            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
197
        },
198
        'im.projectmembership': {
199
            'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'},
200
            'acceptance_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'db_index': 'True'}),
201
            'application': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'null': 'True', 'to': "orm['im.ProjectApplication']"}),
202
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
203
            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
204
            'is_pending': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
205
            'leave_request_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
206
            'pending_application': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pending_memberships'", 'null': 'True', 'to': "orm['im.ProjectApplication']"}),
207
            'pending_serial': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'db_index': 'True'}),
208
            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
209
            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}),
210
            'request_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
211
            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
212
        },
213
        'im.projectmembershiphistory': {
214
            'Meta': {'object_name': 'ProjectMembershipHistory'},
215
            'date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
216
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
217
            'person': ('django.db.models.fields.BigIntegerField', [], {}),
218
            'project': ('django.db.models.fields.BigIntegerField', [], {}),
219
            'reason': ('django.db.models.fields.IntegerField', [], {}),
220
            'serial': ('django.db.models.fields.BigIntegerField', [], {})
221
        },
222
        'im.projectresourcegrant': {
223
            'Meta': {'unique_together': "(('resource', 'project_application'),)", 'object_name': 'ProjectResourceGrant'},
224
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
225
            'member_capacity': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
226
            'member_export_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
227
            'member_import_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
228
            'project_application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectApplication']", 'null': 'True'}),
229
            'project_capacity': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
230
            'project_export_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
231
            'project_import_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
232
            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
233
        },
234
        'im.resource': {
235
            'Meta': {'unique_together': "(('service', 'name'),)", 'object_name': 'Resource'},
236
            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
237
            'group': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
238
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
239
            'meta': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.ResourceMetadata']", 'symmetrical': 'False'}),
240
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
241
            'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Service']"}),
242
            'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
243
            'uplimit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '0', 'max_digits': '38', 'decimal_places': '0'})
244
        },
245
        'im.resourcemetadata': {
246
            'Meta': {'object_name': 'ResourceMetadata'},
247
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
248
            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
249
            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
250
        },
251
        'im.serial': {
252
            'Meta': {'object_name': 'Serial'},
253
            'serial': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
254
        },
255
        'im.service': {
256
            'Meta': {'ordering': "('order',)", 'object_name': 'Service'},
257
            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
258
            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
259
            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
260
            'icon': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
261
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
262
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
263
            'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
264
            'url': ('django.db.models.fields.FilePathField', [], {'max_length': '100'})
265
        },
266
        'im.sessioncatalog': {
267
            'Meta': {'object_name': 'SessionCatalog'},
268
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
269
            'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
270
            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"})
271
        },
272
        'im.usersetting': {
273
            'Meta': {'unique_together': "(('user', 'setting'),)", 'object_name': 'UserSetting'},
274
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275
            'setting': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
276
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
277
            'value': ('django.db.models.fields.IntegerField', [], {})
278
        }
279
    }
280

  
281
    complete_apps = ['im']
b/snf-astakos-app/astakos/im/models.py
837 837
            return False
838 838
        return True
839 839

  
840
    def settings(self):
841
        return UserSetting.objects.filter(user=self)
842

  
840 843

  
841 844
class AstakosUserAuthProviderManager(models.Manager):
842 845

  
......
1195 1198
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1196 1199

  
1197 1200

  
1201
class UserSetting(models.Model):
1202
    user = models.ForeignKey(AstakosUser)
1203
    setting = models.CharField(max_length=255)
1204
    value = models.IntegerField()
1205

  
1206
    objects = ForUpdateManager()
1207

  
1208
    class Meta:
1209
        unique_together = ("user", "setting")
1210

  
1211

  
1198 1212
### PROJECTS ###
1199 1213
################
1200 1214

  
b/snf-astakos-app/astakos/im/settings.py
340 340
# Users that can approve or deny project applications from the web.
341 341
PROJECT_ADMINS = getattr(settings, 'ASTAKOS_PROJECT_ADMINS', set())
342 342

  
343
# Maximum pending project applications per applicant.
344
# This is to reduce the volume of applications
345
# in case users abuse the mechanism.
346
PENDING_APPLICATION_LIMIT = getattr(settings,
347
                                    'ASTAKOS_PENDING_APPLICATION_LIMIT', 1)
348

  
343 349
# OAuth2 Twitter credentials.
344 350
TWITTER_TOKEN = getattr(settings, 'ASTAKOS_TWITTER_TOKEN', '')
345 351
TWITTER_SECRET = getattr(settings, 'ASTAKOS_TWITTER_SECRET', '')
b/snf-astakos-app/astakos/im/views.py
99 99
    invite,
100 100
    send_activation as send_activation_func,
101 101
    SendNotificationError,
102
    reached_pending_application_limit,
102 103
    accept_membership, reject_membership, remove_membership, cancel_membership,
103 104
    leave_project, join_project, enroll_member, can_join_request, can_leave_request,
104 105
    get_related_project_id, get_by_chain_or_404,
......
1045 1046
@signed_terms_required
1046 1047
@login_required
1047 1048
def project_add(request):
1049

  
1050
    user = request.user
1051
    reached, limit = reached_pending_application_limit(user.id)
1052
    if reached:
1053
        m = _(astakos_messages.PENDING_APPLICATION_LIMIT_ADD) % limit
1054
        messages.error(request, m)
1055
        next = reverse('astakos.im.views.project_list')
1056
        next = restrict_next(next, domain=COOKIE_DOMAIN)
1057
        return redirect(next)
1058

  
1048 1059
    resource_groups = RESOURCES_PRESENTATION_DATA.get('groups', {})
1049 1060
    resource_catalog = ()
1050 1061
    result = callpoint.list_resources()
......
1157 1168
        m = _(astakos_messages.NOT_ALLOWED)
1158 1169
        raise PermissionDenied(m)
1159 1170

  
1171
    reached, limit = reached_pending_application_limit(user.id, app)
1172
    if reached:
1173
        m = _(astakos_messages.PENDING_APPLICATION_LIMIT_MODIFY) % limit
1174
        messages.error(request, m)
1175
        next = reverse('astakos.im.views.project_list')
1176
        next = restrict_next(next, domain=COOKIE_DOMAIN)
1177
        return redirect(next)
1178

  
1160 1179
    resource_groups = RESOURCES_PRESENTATION_DATA.get('groups', {})
1161 1180
    resource_catalog = ()
1162 1181
    result = callpoint.list_resources()
b/snf-astakos-app/conf/20-snf-astakos-app-settings.conf
283 283
# UUIDs of users that can approve or deny project applications from the web.
284 284
# ASTAKOS_PROJECT_ADMINS = set() # e.g. set(['01234567-89ab-cdef-0123-456789abcdef'])
285 285

  
286
# Maximum pending project applications per applicant.
287
# This is to reduce the volume of applications
288
# in case users abuse the mechanism.
289
#ASTAKOS_PENDING_APPLICATION_LIMIT = 1
290

  
286 291
# OAuth2 Twitter credentials.
287 292
#ASTAKOS_TWITTER_KEY = ''
288 293
#ASTAKOS_TWITTER_SECRET = ''

Also available in: Unified diff