Revision 37d59b27

b/snf-astakos-app/astakos/im/forms.py
843 843

  
844 844
    def clean(self):
845 845
        userid = self.data.get('user', None)
846
        policies = self.resource_policies
846 847
        self.user = None
847 848
        if userid:
848 849
            try:
......
865 866
            if name.endswith('_uplimit'):
866 867
                subs = name.split('_uplimit')
867 868
                prefix, suffix = subs
868
                resource = Resource.objects.get(name=prefix)
869

  
869
                try:
870
                    resource = Resource.objects.get(name=prefix)
871
                except Resource.DoesNotExist:
872
                    raise forms.ValidationError("Resource %s does not exist" %
873
                                                resource.name)
870 874
                # keep only resource limits for selected resource groups
871 875
                if self.data.get(
872 876
                    'is_selected_%s' % resource.group, "0"
873 877
                 ) == "1":
878
                    if not resource.allow_in_projects:
879
                        raise forms.ValidationError("Invalid resource %s" %
880
                                                    resource.name)
874 881
                    d = model_to_dict(resource)
875 882
                    if uplimit:
876 883
                        d.update(dict(resource=prefix, uplimit=uplimit))
......
879 886
                    append(d)
880 887

  
881 888
        ordered_keys = presentation.RESOURCES['resources_order']
882
        policies = sorted(policies, key=lambda r:ordered_keys.index(r['str_repr']))
889
        def resource_order(r):
890
            if r['str_repr'] in ordered_keys:
891
                return ordered_keys.index(r['str_repr'])
892
            else:
893
                return -1
894

  
895
        policies = sorted(policies, key=resource_order)
883 896
        return policies
884 897

  
885 898
    def save(self, commit=True):
b/snf-astakos-app/astakos/im/models.py
36 36
import logging
37 37
import json
38 38
import math
39
import copy
40 39

  
41 40
from time import asctime
42 41
from datetime import datetime, timedelta
......
65 64
from django.core.validators import email_re
66 65
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
67 66

  
67
from synnefo.lib.utils import dict_merge
68

  
68 69
from astakos.im.settings import (
69 70
    DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
70 71
    AUTH_TOKEN_DURATION, EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
......
103 104
inf = float('inf')
104 105

  
105 106

  
106
def dict_merge(a, b):
107
    """
108
    http://www.xormedia.com/recursively-merge-dictionaries-in-python/
109
    """
110
    if not isinstance(b, dict):
111
        return b
112
    result = copy.deepcopy(a)
113
    for k, v in b.iteritems():
114
        if k in result and isinstance(result[k], dict):
115
                result[k] = dict_merge(result[k], v)
116
        else:
117
            result[k] = copy.deepcopy(v)
118
    return result
119

  
120

  
121 107
class Service(models.Model):
122 108
    name = models.CharField(_('Name'), max_length=255, unique=True,
123 109
                            db_index=True)
......
188 174

  
189 175

  
190 176
_presentation_data = {}
177

  
178

  
191 179
def get_presentation(resource):
192 180
    global _presentation_data
193 181
    resource_presentation = _presentation_data.get(resource, {})
......
197 185
        _presentation_data[resource] = resource_presentation
198 186
    return resource_presentation
199 187

  
188

  
200 189
class Resource(models.Model):
201 190
    name = models.CharField(_('Name'), max_length=255, unique=True)
202 191
    desc = models.TextField(_('Description'), null=True)
b/snf-astakos-app/astakos/im/presentation.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
from astakos.im import settings
35
from synnefo.lib.utils import dict_merge
35 36

  
36 37
RESOURCES = {
37 38
    'groups': {
......
132 133
            'verbose_name': 'Private Network',
133 134
            'group': 'network'
134 135

  
135
        }
136
        },
137
        'astakos.pending_app': {
138
            'help_text': ('Pending project applications limit'),
139
            'help_text_input_each': ('Total pending project applications user '
140
                                     'is allowed to create'),
141
            'is_abbreviation': False,
142
            'report_desc': 'Pending Project Applications',
143
            'placeholder': 'eg. 2',
144
            'verbose_name': 'pending project application',
145
            'group': 'accounts'
146

  
147
        },
136 148
    },
137
    'groups_order': ['storage', 'compute', 'network'],
149
    'groups_order': ['storage', 'compute', 'network', 'accounts'],
138 150
    'resources_order': ['pithos.diskspace',
139 151
                        'cyclades.disk',
140 152
                        'cyclades.cpu',
141 153
                        'cyclades.ram',
142 154
                        'cyclades.vm',
143
                        'cyclades.network.private'
144
                        ]
155
                        'cyclades.network.private',
156
                        'astakos.pending_app'
157
                        ],
158
    'exclude_from_usage': []
145 159
}
146 160

  
161
# extend from settings
162
RESOURCES = dict_merge(RESOURCES, settings.RESOURCES_META)
163

  
147 164

  
148 165
def service_defaults(service_name):
149 166
    """
b/snf-astakos-app/astakos/im/settings.py
236 236
# Whether or not to display projects in astakos menu
237 237
PROJECTS_VISIBLE = getattr(settings, 'ASTAKOS_PROJECTS_VISIBLE', False)
238 238

  
239
# A way to extend the settings presentation metadata
239
# A way to extend the services presentation metadata
240 240
SERVICES_META = getattr(settings, 'ASTAKOS_SERVICES_META', {})
241

  
242
# A way to extend the resources presentation metadata
243
RESOURCES_META = getattr(settings, 'ASTAKOS_RESOURCES_META', {})
b/snf-astakos-app/astakos/im/static/im/js/usage.js
101 101
    this.container.append(this.el.main);
102 102
    var ul = this.container.find("ul");
103 103
    this.el.list = this.render('quotas', {
104
      'resources':this.resources_ordered
104
      'resources': this.resources_ordered
105 105
    });
106 106
    ul.append(this.el.list);
107 107
  },
......
125 125
      })
126 126
    });
127 127
      
128
    resources_ordered = _.map(ordered, function(rk) { return resources[rk] });
128
    resources_ordered = _.filter(_.map(ordered, 
129
                                       function(rk) { 
130
                                         return resources[rk] 
131
                                       }), 
132
                                 function(i) { return i});
129 133
    this.resources = resources;
130 134
    this.resources_ordered = resources_ordered;
131 135

  
......
137 141
    var self = this;
138 142
    _.each(this.quotas, function(value, key) {
139 143
      var usage = self.getUsage(key);
144
      if (!usage) { return }
140 145
      var el = self.$().find("li[data-resource='"+key+"']");
141 146
      self.updateResourceElement(el, usage);
142 147
    })
......
158 163
  getUsage: function(resource_name) {
159 164
    var resource = this.quotas[resource_name];
160 165
    var resource_meta = this.resources[resource_name];
166
    if (!resource_meta) { return }
161 167
    var value, limit, percentage; 
162 168
    
163 169
    limit = resource.limit;
......
169 175
    if (value > limit) {
170 176
      percentage = 100;
171 177
    }
172

  
178
  
173 179
    if (resource_meta.unit == 'bytes') {
174 180
      value = humanize.filesize(value);
175 181
      limit = humanize.filesize(limit);
......
198 204
    _.each(this.quotas, function(v, k) {
199 205
      var r = self.resources[k];
200 206
      var usage = self.getUsage(k);
207
      if (!usage) { return }
201 208
      r.usage = usage;
202 209
      self.resources[k].usage = usage;
210
      if (!self.resources_ordered[r.index]) { return }
203 211
      self.resources_ordered[r.index].usage = usage;
204 212
    });
205 213
  },
b/snf-astakos-app/astakos/im/tests.py
1206 1206
                                                      api_url="/astakos/api/")
1207 1207
        self.resource = Resource.objects.create(name="astakos.pending_app",
1208 1208
                                                uplimit=0,
1209
                                                allow_in_projects=False,
1209 1210
                                                service=self.astakos_service)
1210 1211

  
1211 1212
        # custom service resources
......
1241 1242
        self.assertRedirects(r, reverse('project_add'))
1242 1243

  
1243 1244
    @im_settings(PROJECT_ADMINS=['uuid1'])
1245
    def test_allow_in_project(self):
1246
        dfrom = datetime.now()
1247
        dto = datetime.now() + timedelta(days=30)
1248

  
1249
        # astakos.pending_uplimit allow_in_project flag is False
1250
        # we shouldn't be able to create a project application using this
1251
        # resource.
1252
        application_data = {
1253
            'name': 'project.synnefo.org',
1254
            'homepage': 'https://www.synnefo.org',
1255
            'start_date': dfrom.strftime("%Y-%m-%d"),
1256
            'end_date': dto.strftime("%Y-%m-%d"),
1257
            'member_join_policy': 2,
1258
            'member_leave_policy': 1,
1259
            'service1.resource_uplimit': 100,
1260
            'is_selected_service1.resource': "1",
1261
            'astakos.pending_app_uplimit': 100,
1262
            'is_selected_accounts': "1",
1263
            'user': self.user.pk
1264
        }
1265
        form = forms.ProjectApplicationForm(data=application_data)
1266
        # form is invalid
1267
        self.assertEqual(form.is_valid(), False)
1268

  
1269
        del application_data['astakos.pending_app_uplimit']
1270
        del application_data['is_selected_accounts']
1271
        form = forms.ProjectApplicationForm(data=application_data)
1272
        self.assertEqual(form.is_valid(), True)
1273

  
1274
    @im_settings(PROJECT_ADMINS=['uuid1'])
1244 1275
    def test_applications(self):
1245 1276
        # let user have 2 pending applications
1246 1277
        self.user.add_resource_policy('astakos.pending_app', 2)
......
1261 1292
            'member_join_policy': 2,
1262 1293
            'member_leave_policy': 1,
1263 1294
            'service1.resource_uplimit': 100,
1264
            'is_selected_service1.resource': 1,
1295
            'is_selected_service1.resource': "1",
1265 1296
            'user': self.user.pk
1266 1297
        }
1267 1298
        r = self.user_client.post(post_url, data=application_data, follow=True)
b/snf-astakos-app/astakos/im/views.py
863 863
@valid_astakos_user_required
864 864
def resource_usage(request):
865 865

  
866
    resources_meta = presentation.RESOURCES
867

  
866 868
    current_usage = quotas.get_user_quotas(request.user)
867 869
    current_usage = json.dumps(current_usage['system'])
868
    resource_catalog, resource_groups = _resources_catalog(request)
870
    resource_catalog, resource_groups = _resources_catalog(for_usage=True)
871
    if resource_catalog is False:
872
        # on fail resource_groups contains the result object
873
        result = resource_groups
874
        messages.error(request, 'Unable to retrieve system resources: %s' %
875
                       result.reason)
876

  
869 877
    resource_catalog = json.dumps(resource_catalog)
870 878
    resource_groups = json.dumps(resource_groups)
871
    resources_order = json.dumps(presentation.RESOURCES.get('resources_order'))
879
    resources_order = json.dumps(resources_meta.get('resources_order'))
872 880

  
873 881
    return render_response('im/resource_usage.html',
874 882
                           context_instance=get_context(request),
......
1016 1024
        return response
1017 1025

  
1018 1026

  
1019
def _resources_catalog(request):
1027
def _resources_catalog(for_project=False, for_usage=False):
1020 1028
    """
1021 1029
    `resource_catalog` contains a list of tuples. Each tuple contains the group
1022 1030
    key the resource is assigned to and resources list of dicts that contain
......
1024 1032
    `resource_groups` contains information about the groups
1025 1033
    """
1026 1034
    # presentation data
1027
    resource_groups = presentation.RESOURCES.get('groups', {})
1035
    resources_meta = presentation.RESOURCES
1036
    resource_groups = resources_meta.get('groups', {})
1028 1037
    resource_catalog = ()
1029 1038
    resource_keys = []
1030 1039

  
1031 1040
    # resources in database
1032 1041
    result = callpoint.list_resources()
1033 1042
    if not result.is_success:
1034
        messages.error(request, 'Unable to retrieve system resources: %s' %
1035
                                result.reason)
1043
        return False, result
1036 1044
    else:
1037 1045
        # initialize resource_catalog to contain all group/resource information
1038 1046
        for r in result.data:
......
1044 1052
                                       result.data)] for g in resource_groups]
1045 1053

  
1046 1054
    # order groups, also include unknown groups
1047
    groups_order = presentation.RESOURCES.get('groups_order')
1055
    groups_order = resources_meta.get('groups_order')
1048 1056
    for g in resource_groups.keys():
1049 1057
        if not g in groups_order:
1050 1058
            groups_order.append(g)
1051 1059

  
1052 1060
    # order resources, also include unknown resources
1053
    resources_order = presentation.RESOURCES.get('resources_order')
1061
    resources_order = resources_meta.get('resources_order')
1054 1062
    for r in resource_keys:
1055 1063
        if not r in resources_order:
1056 1064
            resources_order.append(r)
......
1069 1077
    # sort resources
1070 1078
    def resourceindex(r):
1071 1079
        return resources_order.index(r['str_repr'])
1080

  
1072 1081
    for index, group in enumerate(resource_catalog):
1073 1082
        resource_catalog[index][1] = sorted(resource_catalog[index][1],
1074 1083
                                            key=resourceindex)
......
1078 1087
                if g[0] == group[0]:
1079 1088
                    resource_groups.pop(gindex)
1080 1089

  
1090
    # filter out resources which user cannot request in a project application
1091
    exclude = resources_meta.get('exclude_from_usage', [])
1092
    for group_index, group_resources in enumerate(list(resource_catalog)):
1093
        group, resources = group_resources
1094
        for index, resource in list(enumerate(resources)):
1095
            if for_project and not resource.get('allow_in_projects'):
1096
                resources.remove(resource)
1097
            if resource.get('str_repr') in exclude and for_usage:
1098
                resources.remove(resource)
1099

  
1100
    # cleanup empty groups
1101
    for group_index, group_resources in enumerate(list(resource_catalog)):
1102
        group, resources = group_resources
1103
        if len(resources) == 0:
1104
            resource_catalog.pop(group_index)
1105
            resource_groups.pop(group)
1106

  
1107

  
1081 1108
    return resource_catalog, resource_groups
1082 1109

  
1083 1110

  
......
1098 1125
                      "end_date", "comments"]
1099 1126
    membership_fields = ["member_join_policy", "member_leave_policy",
1100 1127
                         "limit_on_members_number"]
1101
    resource_catalog, resource_groups = _resources_catalog(request)
1128
    resource_catalog, resource_groups = _resources_catalog(for_project=True)
1129
    if resource_catalog is False:
1130
        # on fail resource_groups contains the result object
1131
        result = resource_groups
1132
        messages.error(request, 'Unable to retrieve system resources: %s' %
1133
                       result.reason)
1102 1134
    extra_context = {
1103 1135
        'resource_catalog': resource_catalog,
1104 1136
        'resource_groups': resource_groups,
......
1205 1237
                      "end_date", "comments"]
1206 1238
    membership_fields = ["member_join_policy", "member_leave_policy",
1207 1239
                         "limit_on_members_number"]
1208
    resource_catalog, resource_groups = _resources_catalog(request)
1240
    resource_catalog, resource_groups = _resources_catalog(for_project=True)
1241
    if resource_catalog is False:
1242
        # on fail resource_groups contains the result object
1243
        result = resource_groups
1244
        messages.error(request, 'Unable to retrieve system resources: %s' %
1245
                       result.reason)
1209 1246
    extra_context = {
1210 1247
        'resource_catalog': resource_catalog,
1211 1248
        'resource_groups': resource_groups,

Also available in: Unified diff