Revision 8fb8d0cf snf-astakos-app/astakos/im/models.py

b/snf-astakos-app/astakos/im/models.py
148 148
            else:
149 149
                metadata[component.name] = d
150 150

  
151

  
152 151
        def component_by_order(s):
153 152
            return s[1].get('order')
154 153

  
......
276 275
            return '%ss' % self.display_name
277 276
        return self.display_name
278 277

  
278

  
279 279
def get_resource_names():
280 280
    _RESOURCE_NAMES = []
281 281
    resources = Resource.objects.select_related('service').all()
......
320 320
        Returns a uuid to username mapping for the uuids appearing in l.
321 321
        If l is None returns the mapping for all existing users.
322 322
        """
323
        q = self.filter(uuid__in=l) if l != None else self
323
        q = self.filter(uuid__in=l) if l is not None else self
324 324
        return dict(q.values_list('uuid', 'username'))
325 325

  
326 326
    def displayname_catalog(self, l=None):
......
331 331
        if l is not None:
332 332
            lmap = dict((x.lower(), x) for x in l)
333 333
            q = self.filter(username__in=lmap.keys())
334
            values = ((lmap[n], u) for n, u in q.values_list('username', 'uuid'))
334
            values = ((lmap[n], u)
335
                      for n, u in q.values_list('username', 'uuid'))
335 336
        else:
336 337
            q = self
337 338
            values = self.values_list('username', 'uuid')
338 339
        return dict(values)
339 340

  
340 341

  
341

  
342 342
class AstakosUser(User):
343 343
    """
344 344
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
345 345
    """
346
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
347
                                   null=True)
346
    affiliation = models.CharField(_('Affiliation'), max_length=255,
347
                                   blank=True, null=True)
348 348

  
349 349
    #for invitations
350 350
    user_level = astakos_settings.DEFAULT_USER_LEVEL
351 351
    level = models.IntegerField(_('Inviter level'), default=user_level)
352 352
    invitations = models.IntegerField(
353
        _('Invitations left'), default=astakos_settings.INVITATIONS_PER_LEVEL.get(user_level, 0))
354

  
355
    auth_token = models.CharField(_('Authentication Token'),
356
                                  max_length=64,
357
                                  unique=True,
358
                                  null=True,
359
                                  blank=True,
360
                                  help_text = _('Renew your authentication '
361
                                                'token. Make sure to set the new '
362
                                                'token in any client you may be '
363
                                                'using, to preserve its '
364
                                                'functionality.'))
353
        _('Invitations left'),
354
        default=astakos_settings.INVITATIONS_PER_LEVEL.get(user_level, 0))
355

  
356
    auth_token = models.CharField(
357
        _('Authentication Token'),
358
        max_length=64,
359
        unique=True,
360
        null=True,
361
        blank=True,
362
        help_text=_('Renew your authentication '
363
                    'token. Make sure to set the new '
364
                    'token in any client you may be '
365
                    'using, to preserve its '
366
                    'functionality.'))
365 367
    auth_token_created = models.DateTimeField(_('Token creation date'),
366 368
                                              null=True)
367 369
    auth_token_expires = models.DateTimeField(
......
428 430
        Resource, null=True, through='AstakosUserQuota')
429 431

  
430 432
    disturbed_quota = models.BooleanField(_('Needs quotaholder syncing'),
431
                                           default=False, db_index=True)
433
                                          default=False, db_index=True)
432 434

  
433 435
    objects = AstakosUserManager()
434 436
    forupdate = ForUpdateManager()
......
463 465
        if self.has_perm(pname):
464 466
            return
465 467
        p, created = Permission.objects.get_or_create(
466
                                    codename=pname,
467
                                    name=pname.capitalize(),
468
                                    content_type=get_content_type())
468
            codename=pname,
469
            name=pname.capitalize(),
470
            content_type=get_content_type())
469 471
        self.user_permissions.add(p)
470 472

  
471 473
    def remove_permission(self, pname):
......
545 547
        self.auth_token = new_token
546 548
        self.auth_token_created = datetime.now()
547 549
        self.auth_token_expires = self.auth_token_created + \
548
                                  timedelta(hours=astakos_settings.AUTH_TOKEN_DURATION)
550
            timedelta(hours=astakos_settings.AUTH_TOKEN_DURATION)
549 551
        if flush_sessions:
550 552
            self.flush_sessions(current_key)
551 553
        msg = 'Token renewed for %s' % self.log_display
......
605 607
                msg += " (manually accepted)"
606 608
            else:
607 609
                msg += " (accepted policy: %s)" % \
608
                        self.accepted_policy
610
                    self.accepted_policy
609 611
        return msg
610 612

  
611 613
    @property
......
730 732

  
731 733
    def get_activation_url(self, nxt=False):
732 734
        url = "%s?auth=%s" % (reverse('astakos.im.views.activate'),
733
                                 quote(self.verification_code))
735
                              quote(self.verification_code))
734 736
        if nxt:
735 737
            url += "&next=%s" % quote(nxt)
736 738
        return url
737 739

  
738 740
    def get_password_reset_url(self, token_generator=default_token_generator):
739 741
        return reverse('astakos.im.views.target.local.password_reset_confirm',
740
                          kwargs={'uidb36':int_to_base36(self.id),
741
                                  'token':token_generator.make_token(self)})
742
                       kwargs={'uidb36': int_to_base36(self.id),
743
                               'token': token_generator.make_token(self)})
742 744

  
743 745
    def get_inactive_message(self, provider_module, identifier=None):
744 746
        provider = self.get_auth_provider(provider_module, identifier)
......
758 760
            message = msg_pending
759 761
            url = self.get_resend_activation_url()
760 762
            msg_extra = msg_pending_help + \
761
                        u' ' + \
762
                        '<a href="%s">%s?</a>' % (url, msg_resend)
763
                u' ' + \
764
                '<a href="%s">%s?</a>' % (url, msg_resend)
763 765
        else:
764 766
            if not self.moderated:
765 767
                message = msg_pending_mod
......
930 932
    """
931 933
    Available user authentication methods.
932 934
    """
933
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
934
                                   null=True, default=None)
935
    affiliation = models.CharField(_('Affiliation'), max_length=255,
936
                                   blank=True, null=True, default=None)
935 937
    user = models.ForeignKey(AstakosUser, related_name='auth_providers')
936 938
    module = models.CharField(_('Provider'), max_length=255, blank=False,
937
                                default='local')
939
                              default='local')
938 940
    identifier = models.CharField(_('Third-party identifier'),
939
                                              max_length=255, null=True,
940
                                              blank=True)
941
                                  max_length=255, null=True,
942
                                  blank=True)
941 943
    active = models.BooleanField(default=True)
942 944
    auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
943
                                   default='astakos')
945
                                    default='astakos')
944 946
    info_data = models.TextField(default="", null=True, blank=True)
945 947
    created = models.DateTimeField('Creation date', auto_now_add=True)
946 948

  
......
959 961
        except Exception, e:
960 962
            self.info = {}
961 963

  
962
        for key,value in self.info.iteritems():
964
        for key, value in self.info.iteritems():
963 965
            setattr(self, 'info_%s' % key, value)
964 966

  
965 967
    @property
......
977 979

  
978 980
        extra_data['instance'] = self
979 981
        return auth.get_provider(self.module, self.user,
980
                                           self.identifier, **extra_data)
982
                                 self.identifier, **extra_data)
981 983

  
982 984
    def __repr__(self):
983
        return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier)
985
        return '<AstakosUserAuthProvider %s:%s>' % (
986
            self.module, self.identifier)
984 987

  
985 988
    def __unicode__(self):
986 989
        if self.identifier:
......
1055 1058
    code = models.BigIntegerField(_('Invitation code'), db_index=True)
1056 1059
    is_consumed = models.BooleanField(_('Consumed?'), default=False)
1057 1060
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
1058
    consumed = models.DateTimeField(_('Consumption date'), null=True, blank=True)
1061
    consumed = models.DateTimeField(_('Consumption date'),
1062
                                    null=True, blank=True)
1059 1063

  
1060 1064
    def __init__(self, *args, **kwargs):
1061 1065
        super(Invitation, self).__init__(*args, **kwargs)
......
1099 1103
                raise EmailChange.DoesNotExist
1100 1104
            # is there an active user with this address?
1101 1105
            try:
1102
                AstakosUser.objects.get(email__iexact=email_change.new_email_address)
1106
                AstakosUser.objects.get(
1107
                    email__iexact=email_change.new_email_address)
1103 1108
            except AstakosUser.DoesNotExist:
1104 1109
                pass
1105 1110
            else:
......
1135 1140

  
1136 1141
    def get_url(self):
1137 1142
        return reverse('email_change_confirm',
1138
                      kwargs={'activation_key': self.activation_key})
1143
                       kwargs={'activation_key': self.activation_key})
1139 1144

  
1140 1145
    def activation_key_expired(self):
1141
        expiration_date = timedelta(days=astakos_settings.EMAILCHANGE_ACTIVATION_DAYS)
1146
        expiration_date = timedelta(
1147
            days=astakos_settings.EMAILCHANGE_ACTIVATION_DAYS)
1142 1148
        return self.requested_at + expiration_date < datetime.now()
1143 1149

  
1144 1150

  
......
1173 1179
    """
1174 1180
    Model for registring successful third party user authentications
1175 1181
    """
1176
    third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
1182
    third_party_identifier = models.CharField(
1183
        _('Third-party identifier'), max_length=255, null=True, blank=True)
1177 1184
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
1178 1185
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
1179 1186
    first_name = models.CharField(_('first name'), max_length=30, blank=True,
......
1182 1189
                                 null=True)
1183 1190
    affiliation = models.CharField('Affiliation', max_length=255, blank=True,
1184 1191
                                   null=True)
1185
    username = models.CharField(_('username'), max_length=30, unique=True,
1186
                                help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
1192
    username = models.CharField(
1193
        _('username'), max_length=30, unique=True,
1194
        help_text=_("Required. 30 characters or fewer. "
1195
                    "Letters, numbers and @/./+/-/_ characters"))
1187 1196
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
1188 1197
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
1189 1198
    info = models.TextField(default="", null=True, blank=True)
......
1211 1220

  
1212 1221
    @property
1213 1222
    def realname(self):
1214
        return '%s %s' %(self.first_name, self.last_name)
1223
        return '%s %s' % (self.first_name, self.last_name)
1215 1224

  
1216 1225
    @realname.setter
1217 1226
    def realname(self, value):
......
1226 1235
        if not self.id:
1227 1236
            # set username
1228 1237
            while not self.username:
1229
                username =  uuid.uuid4().hex[:30]
1238
                username = uuid.uuid4().hex[:30]
1230 1239
                try:
1231
                    AstakosUser.objects.get(username = username)
1240
                    AstakosUser.objects.get(username=username)
1232 1241
                except AstakosUser.DoesNotExist, e:
1233 1242
                    self.username = username
1234 1243
        super(PendingThirdPartyUser, self).save(**kwargs)
......
1239 1248
        self.token = default_token_generator.make_token(self)
1240 1249

  
1241 1250
    def existing_user(self):
1242
        return AstakosUser.objects.filter(auth_providers__module=self.provider,
1243
                                         auth_providers__identifier=self.third_party_identifier)
1251
        return AstakosUser.objects.filter(
1252
            auth_providers__module=self.provider,
1253
            auth_providers__identifier=self.third_party_identifier)
1244 1254

  
1245 1255
    def get_provider(self, user):
1246 1256
        params = {
......
1250 1260
        return auth.get_provider(self.provider, user,
1251 1261
                                 self.third_party_identifier, **params)
1252 1262

  
1263

  
1253 1264
class SessionCatalog(models.Model):
1254 1265
    session_key = models.CharField(_('session key'), max_length=40)
1255 1266
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
......
1274 1285
    def search_by_name(self, *search_strings):
1275 1286
        projects = Project.objects.search_by_name(*search_strings)
1276 1287
        chains = [p.id for p in projects]
1277
        apps  = ProjectApplication.objects.search_by_name(*search_strings)
1288
        apps = ProjectApplication.objects.search_by_name(*search_strings)
1278 1289
        apps = (app for app in apps if app.is_latest())
1279 1290
        app_chains = [app.chain for app in apps if app.chain not in chains]
1280 1291
        return chains + app_chains
......
1309 1320

  
1310 1321

  
1311 1322
class Chain(models.Model):
1312
    chain  =   models.AutoField(primary_key=True)
1323
    chain = models.AutoField(primary_key=True)
1313 1324

  
1314 1325
    def __str__(self):
1315 1326
        return "%s" % (self.chain,)
1316 1327

  
1317 1328
    objects = ChainManager()
1318 1329

  
1319
    PENDING            = 0
1320
    DENIED             = 3
1321
    DISMISSED          = 4
1322
    CANCELLED          = 5
1330
    PENDING = 0
1331
    DENIED = 3
1332
    DISMISSED = 4
1333
    CANCELLED = 5
1323 1334

  
1324
    APPROVED           = 10
1325
    APPROVED_PENDING   = 11
1326
    SUSPENDED          = 12
1327
    SUSPENDED_PENDING  = 13
1328
    TERMINATED         = 14
1335
    APPROVED = 10
1336
    APPROVED_PENDING = 11
1337
    SUSPENDED = 12
1338
    SUSPENDED_PENDING = 13
1339
    TERMINATED = 14
1329 1340
    TERMINATED_PENDING = 15
1330 1341

  
1331 1342
    PENDING_STATES = [PENDING,
......
1353 1364
                   TERMINATED]
1354 1365

  
1355 1366
    STATE_DISPLAY = {
1356
        PENDING            : _("Pending"),
1357
        DENIED             : _("Denied"),
1358
        DISMISSED          : _("Dismissed"),
1359
        CANCELLED          : _("Cancelled"),
1360
        APPROVED           : _("Active"),
1361
        APPROVED_PENDING   : _("Active - Pending"),
1362
        SUSPENDED          : _("Suspended"),
1363
        SUSPENDED_PENDING  : _("Suspended - Pending"),
1364
        TERMINATED         : _("Terminated"),
1365
        TERMINATED_PENDING : _("Terminated - Pending"),
1366
        }
1367

  
1367
        PENDING:             _("Pending"),
1368
        DENIED:              _("Denied"),
1369
        DISMISSED:           _("Dismissed"),
1370
        CANCELLED:           _("Cancelled"),
1371
        APPROVED:            _("Active"),
1372
        APPROVED_PENDING:    _("Active - Pending"),
1373
        SUSPENDED:           _("Suspended"),
1374
        SUSPENDED_PENDING:   _("Suspended - Pending"),
1375
        TERMINATED:          _("Terminated"),
1376
        TERMINATED_PENDING:  _("Terminated - Pending"),
1377
    }
1368 1378

  
1369 1379
    @classmethod
1370 1380
    def _chain_state(cls, project_state, app_state):
......
1420 1430

  
1421 1431
    def user_visible_by_chain(self, flt):
1422 1432
        model = self.model
1423
        pending = self.filter(model.Q_PENDING | model.Q_DENIED).values_list('chain')
1433
        pending = self.filter(
1434
            model.Q_PENDING | model.Q_DENIED).values_list('chain')
1424 1435
        approved = self.filter(model.Q_APPROVED).values_list('chain')
1425 1436
        by_chain = dict(pending.annotate(models.Max('id')))
1426 1437
        by_chain.update(approved.annotate(models.Max('id')))
......
1434 1445
            participates_filters = Q()
1435 1446
        else:
1436 1447
            participates_filters = Q(owner=user) | Q(applicant=user) | \
1437
                                   Q(project__projectmembership__person=user)
1448
                Q(project__projectmembership__person=user)
1438 1449

  
1439
        return self.user_visible_by_chain(participates_filters).order_by('issue_date').distinct()
1450
        return self.user_visible_by_chain(
1451
            participates_filters).order_by('issue_date').distinct()
1440 1452

  
1441 1453
    def search_by_name(self, *search_strings):
1442 1454
        q = Q()
......
1452 1464

  
1453 1465

  
1454 1466
class ProjectApplication(models.Model):
1455
    applicant               =   models.ForeignKey(
1456
                                    AstakosUser,
1457
                                    related_name='projects_applied',
1458
                                    db_index=True)
1459

  
1460
    PENDING     =    0
1461
    APPROVED    =    1
1462
    REPLACED    =    2
1463
    DENIED      =    3
1464
    DISMISSED   =    4
1465
    CANCELLED   =    5
1466

  
1467
    state                   =   models.IntegerField(default=PENDING,
1468
                                                    db_index=True)
1469

  
1470
    owner                   =   models.ForeignKey(
1471
                                    AstakosUser,
1472
                                    related_name='projects_owned',
1473
                                    db_index=True)
1474

  
1475
    chain                   =   models.ForeignKey(Chain,
1476
                                                  related_name='chained_apps',
1477
                                                  db_column='chain')
1478
    precursor_application   =   models.ForeignKey('ProjectApplication',
1479
                                                  null=True,
1480
                                                  blank=True)
1481

  
1482
    name                    =   models.CharField(max_length=80)
1483
    homepage                =   models.URLField(max_length=255, null=True,
1484
                                                verify_exists=False)
1485
    description             =   models.TextField(null=True, blank=True)
1486
    start_date              =   models.DateTimeField(null=True, blank=True)
1487
    end_date                =   models.DateTimeField()
1488
    member_join_policy      =   models.IntegerField()
1489
    member_leave_policy     =   models.IntegerField()
1490
    limit_on_members_number =   models.PositiveIntegerField(null=True)
1491
    resource_grants         =   models.ManyToManyField(
1492
                                    Resource,
1493
                                    null=True,
1494
                                    blank=True,
1495
                                    through='ProjectResourceGrant')
1496
    comments                =   models.TextField(null=True, blank=True)
1497
    issue_date              =   models.DateTimeField(auto_now_add=True)
1498
    response_date           =   models.DateTimeField(null=True, blank=True)
1499
    response                =   models.TextField(null=True, blank=True)
1500

  
1501
    objects                 =   ProjectApplicationManager()
1467
    applicant = models.ForeignKey(
1468
        AstakosUser,
1469
        related_name='projects_applied',
1470
        db_index=True)
1471

  
1472
    PENDING = 0
1473
    APPROVED = 1
1474
    REPLACED = 2
1475
    DENIED = 3
1476
    DISMISSED = 4
1477
    CANCELLED = 5
1478

  
1479
    state = models.IntegerField(default=PENDING,
1480
                                db_index=True)
1481
    owner = models.ForeignKey(
1482
        AstakosUser,
1483
        related_name='projects_owned',
1484
        db_index=True)
1485
    chain = models.ForeignKey(Chain,
1486
                              related_name='chained_apps',
1487
                              db_column='chain')
1488
    precursor_application = models.ForeignKey('ProjectApplication',
1489
                                              null=True,
1490
                                              blank=True)
1491
    name = models.CharField(max_length=80)
1492
    homepage = models.URLField(max_length=255, null=True,
1493
                               verify_exists=False)
1494
    description = models.TextField(null=True, blank=True)
1495
    start_date = models.DateTimeField(null=True, blank=True)
1496
    end_date = models.DateTimeField()
1497
    member_join_policy = models.IntegerField()
1498
    member_leave_policy = models.IntegerField()
1499
    limit_on_members_number = models.PositiveIntegerField(null=True)
1500
    resource_grants = models.ManyToManyField(
1501
        Resource,
1502
        null=True,
1503
        blank=True,
1504
        through='ProjectResourceGrant')
1505
    comments = models.TextField(null=True, blank=True)
1506
    issue_date = models.DateTimeField(auto_now_add=True)
1507
    response_date = models.DateTimeField(null=True, blank=True)
1508
    response = models.TextField(null=True, blank=True)
1509

  
1510
    objects = ProjectApplicationManager()
1502 1511

  
1503 1512
    # Compiled queries
1504
    Q_PENDING  = Q(state=PENDING)
1513
    Q_PENDING = Q(state=PENDING)
1505 1514
    Q_APPROVED = Q(state=APPROVED)
1506
    Q_DENIED   = Q(state=DENIED)
1515
    Q_DENIED = Q(state=DENIED)
1507 1516

  
1508 1517
    class Meta:
1509 1518
        unique_together = ("chain", "id")
......
1513 1522

  
1514 1523
    # TODO: Move to a more suitable place
1515 1524
    APPLICATION_STATE_DISPLAY = {
1516
        PENDING  : _('Pending review'),
1517
        APPROVED : _('Approved'),
1518
        REPLACED : _('Replaced'),
1519
        DENIED   : _('Denied'),
1525
        PENDING:   _('Pending review'),
1526
        APPROVED:  _('Approved'),
1527
        REPLACED:  _('Replaced'),
1528
        DENIED:    _('Denied'),
1520 1529
        DISMISSED: _('Dismissed'),
1521 1530
        CANCELLED: _('Cancelled')
1522 1531
    }
......
1631 1640
    def cancel(self):
1632 1641
        if not self.can_cancel():
1633 1642
            m = _("cannot cancel: application '%s' in state '%s'") % (
1634
                    self.id, self.state)
1643
                self.id, self.state)
1635 1644
            raise AssertionError(m)
1636 1645

  
1637 1646
        self.state = self.CANCELLED
......
1643 1652
    def dismiss(self):
1644 1653
        if not self.can_dismiss():
1645 1654
            m = _("cannot dismiss: application '%s' in state '%s'") % (
1646
                    self.id, self.state)
1655
                self.id, self.state)
1647 1656
            raise AssertionError(m)
1648 1657

  
1649 1658
        self.state = self.DISMISSED
......
1655 1664
    def deny(self, reason):
1656 1665
        if not self.can_deny():
1657 1666
            m = _("cannot deny: application '%s' in state '%s'") % (
1658
                    self.id, self.state)
1667
                self.id, self.state)
1659 1668
            raise AssertionError(m)
1660 1669

  
1661 1670
        self.state = self.DENIED
......
1669 1678
    def approve(self, reason):
1670 1679
        if not self.can_approve():
1671 1680
            m = _("cannot approve: project '%s' in state '%s'") % (
1672
                    self.name, self.state)
1673
            raise AssertionError(m) # invalid argument
1681
                self.name, self.state)
1682
            raise AssertionError(m)  # invalid argument
1674 1683

  
1675 1684
        now = datetime.now()
1676 1685
        self.state = self.APPROVED
......
1698 1707
        policy = self.member_leave_policy
1699 1708
        return presentation.PROJECT_MEMBER_LEAVE_POLICIES.get(policy)
1700 1709

  
1710

  
1701 1711
class ProjectResourceGrant(models.Model):
1702 1712

  
1703
    resource                =   models.ForeignKey(Resource)
1704
    project_application     =   models.ForeignKey(ProjectApplication,
1705
                                                  null=True)
1706
    project_capacity        =   intDecimalField(null=True)
1707
    member_capacity         =   intDecimalField(default=0)
1713
    resource = models.ForeignKey(Resource)
1714
    project_application = models.ForeignKey(ProjectApplication,
1715
                                            null=True)
1716
    project_capacity = intDecimalField(null=True)
1717
    member_capacity = intDecimalField(default=0)
1708 1718

  
1709 1719
    objects = ExtendedManager()
1710 1720

  
......
1750 1760
            if value == 1:
1751 1761
                return '1 byte'
1752 1762
            else:
1753
               return '0'
1763
                return '0'
1754 1764

  
1755 1765

  
1756 1766
class ProjectManager(ForUpdateManager):
......
1769 1779

  
1770 1780
    def expired_projects(self):
1771 1781
        q = (~Q(state=Project.TERMINATED) &
1772
              Q(application__end_date__lt=datetime.now()))
1782
             Q(application__end_date__lt=datetime.now()))
1773 1783
        return self.filter(q)
1774 1784

  
1775 1785
    def search_by_name(self, *search_strings):
......
1781 1791

  
1782 1792
class Project(models.Model):
1783 1793

  
1784
    id                          =   models.OneToOneField(Chain,
1785
                                                      related_name='chained_project',
1786
                                                      db_column='id',
1787
                                                      primary_key=True)
1794
    id = models.OneToOneField(Chain,
1795
                              related_name='chained_project',
1796
                              db_column='id',
1797
                              primary_key=True)
1788 1798

  
1789
    application                 =   models.OneToOneField(
1790
                                            ProjectApplication,
1791
                                            related_name='project')
1792
    last_approval_date          =   models.DateTimeField(null=True)
1799
    application = models.OneToOneField(
1800
        ProjectApplication,
1801
        related_name='project')
1802
    last_approval_date = models.DateTimeField(null=True)
1793 1803

  
1794
    members                     =   models.ManyToManyField(
1795
                                            AstakosUser,
1796
                                            through='ProjectMembership')
1804
    members = models.ManyToManyField(
1805
        AstakosUser,
1806
        through='ProjectMembership')
1797 1807

  
1798
    deactivation_reason         =   models.CharField(max_length=255, null=True)
1799
    deactivation_date           =   models.DateTimeField(null=True)
1808
    deactivation_reason = models.CharField(max_length=255, null=True)
1809
    deactivation_date = models.DateTimeField(null=True)
1800 1810

  
1801
    creation_date               =   models.DateTimeField(auto_now_add=True)
1802
    name                        =   models.CharField(
1803
                                            max_length=80,
1804
                                            null=True,
1805
                                            db_index=True,
1806
                                            unique=True)
1811
    creation_date = models.DateTimeField(auto_now_add=True)
1812
    name = models.CharField(
1813
        max_length=80,
1814
        null=True,
1815
        db_index=True,
1816
        unique=True)
1807 1817

  
1808
    APPROVED    = 1
1809
    SUSPENDED   = 10
1810
    TERMINATED  = 100
1818
    APPROVED = 1
1819
    SUSPENDED = 10
1820
    TERMINATED = 100
1811 1821

  
1812
    state                       =   models.IntegerField(default=APPROVED,
1813
                                                        db_index=True)
1822
    state = models.IntegerField(default=APPROVED,
1823
                                db_index=True)
1814 1824

  
1815
    objects     =   ProjectManager()
1825
    objects = ProjectManager()
1816 1826

  
1817 1827
    # Compiled queries
1818
    Q_TERMINATED  = Q(state=TERMINATED)
1819
    Q_SUSPENDED   = Q(state=SUSPENDED)
1828
    Q_TERMINATED = Q(state=TERMINATED)
1829
    Q_SUSPENDED = Q(state=SUSPENDED)
1820 1830
    Q_DEACTIVATED = Q_TERMINATED | Q_SUSPENDED
1821 1831

  
1822 1832
    def __str__(self):
......
1829 1839
        return _("<project %s '%s'>") % (self.id, self.application.name)
1830 1840

  
1831 1841
    STATE_DISPLAY = {
1832
        APPROVED   : 'Active',
1833
        SUSPENDED  : 'Suspended',
1834
        TERMINATED : 'Terminated'
1835
        }
1842
        APPROVED:   'Active',
1843
        SUSPENDED:  'Suspended',
1844
        TERMINATED: 'Terminated'
1845
    }
1836 1846

  
1837 1847
    def state_display(self):
1838 1848
        return self.STATE_DISPLAY.get(self.state, _('Unknown'))
......
1902 1912
            return False
1903 1913
        return (len(self.approved_members) + adding > limit)
1904 1914

  
1905

  
1906 1915
    ### Other
1907 1916

  
1908 1917
    def count_pending_memberships(self):
......
1930 1939

  
1931 1940

  
1932 1941
CHAIN_STATE = {
1933
    (Project.APPROVED,   ProjectApplication.PENDING)  : Chain.APPROVED_PENDING,
1934
    (Project.APPROVED,   ProjectApplication.APPROVED) : Chain.APPROVED,
1935
    (Project.APPROVED,   ProjectApplication.DENIED)   : Chain.APPROVED,
1936
    (Project.APPROVED,   ProjectApplication.DISMISSED): Chain.APPROVED,
1937
    (Project.APPROVED,   ProjectApplication.CANCELLED): Chain.APPROVED,
1938

  
1939
    (Project.SUSPENDED,  ProjectApplication.PENDING)  : Chain.SUSPENDED_PENDING,
1940
    (Project.SUSPENDED,  ProjectApplication.APPROVED) : Chain.SUSPENDED,
1941
    (Project.SUSPENDED,  ProjectApplication.DENIED)   : Chain.SUSPENDED,
1942
    (Project.SUSPENDED,  ProjectApplication.DISMISSED): Chain.SUSPENDED,
1943
    (Project.SUSPENDED,  ProjectApplication.CANCELLED): Chain.SUSPENDED,
1944

  
1945
    (Project.TERMINATED, ProjectApplication.PENDING)  : Chain.TERMINATED_PENDING,
1946
    (Project.TERMINATED, ProjectApplication.APPROVED) : Chain.TERMINATED,
1947
    (Project.TERMINATED, ProjectApplication.DENIED)   : Chain.TERMINATED,
1942
    (Project.APPROVED, ProjectApplication.PENDING):   Chain.APPROVED_PENDING,
1943
    (Project.APPROVED, ProjectApplication.APPROVED):  Chain.APPROVED,
1944
    (Project.APPROVED, ProjectApplication.DENIED):    Chain.APPROVED,
1945
    (Project.APPROVED, ProjectApplication.DISMISSED): Chain.APPROVED,
1946
    (Project.APPROVED, ProjectApplication.CANCELLED): Chain.APPROVED,
1947

  
1948
    (Project.SUSPENDED, ProjectApplication.PENDING):   Chain.SUSPENDED_PENDING,
1949
    (Project.SUSPENDED, ProjectApplication.APPROVED):  Chain.SUSPENDED,
1950
    (Project.SUSPENDED, ProjectApplication.DENIED):    Chain.SUSPENDED,
1951
    (Project.SUSPENDED, ProjectApplication.DISMISSED): Chain.SUSPENDED,
1952
    (Project.SUSPENDED, ProjectApplication.CANCELLED): Chain.SUSPENDED,
1953

  
1954
    (Project.TERMINATED, ProjectApplication.PENDING): Chain.TERMINATED_PENDING,
1955
    (Project.TERMINATED, ProjectApplication.APPROVED):  Chain.TERMINATED,
1956
    (Project.TERMINATED, ProjectApplication.DENIED):    Chain.TERMINATED,
1948 1957
    (Project.TERMINATED, ProjectApplication.DISMISSED): Chain.TERMINATED,
1949 1958
    (Project.TERMINATED, ProjectApplication.CANCELLED): Chain.TERMINATED,
1950 1959

  
1951
    (None,               ProjectApplication.PENDING)  : Chain.PENDING,
1952
    (None,               ProjectApplication.DENIED)   : Chain.DENIED,
1953
    (None,               ProjectApplication.DISMISSED): Chain.DISMISSED,
1954
    (None,               ProjectApplication.CANCELLED): Chain.CANCELLED,
1955
    }
1960
    (None, ProjectApplication.PENDING):   Chain.PENDING,
1961
    (None, ProjectApplication.DENIED):    Chain.DENIED,
1962
    (None, ProjectApplication.DISMISSED): Chain.DISMISSED,
1963
    (None, ProjectApplication.CANCELLED): Chain.CANCELLED,
1964
}
1956 1965

  
1957 1966

  
1958 1967
class ProjectMembershipManager(ForUpdateManager):
......
1971 1980
    def suspended(self):
1972 1981
        return self.filter(state=ProjectMembership.USER_SUSPENDED)
1973 1982

  
1983

  
1974 1984
class ProjectMembership(models.Model):
1975 1985

  
1976
    person              =   models.ForeignKey(AstakosUser)
1977
    request_date        =   models.DateTimeField(auto_now_add=True)
1978
    project             =   models.ForeignKey(Project)
1986
    person = models.ForeignKey(AstakosUser)
1987
    request_date = models.DateTimeField(auto_now_add=True)
1988
    project = models.ForeignKey(Project)
1979 1989

  
1980
    REQUESTED           =   0
1981
    ACCEPTED            =   1
1982
    LEAVE_REQUESTED     =   5
1990
    REQUESTED = 0
1991
    ACCEPTED = 1
1992
    LEAVE_REQUESTED = 5
1983 1993
    # User deactivation
1984
    USER_SUSPENDED      =   10
1994
    USER_SUSPENDED = 10
1985 1995

  
1986
    REMOVED             =   200
1996
    REMOVED = 200
1987 1997

  
1988
    ASSOCIATED_STATES   =   set([REQUESTED,
1989
                                 ACCEPTED,
1990
                                 LEAVE_REQUESTED,
1991
                                 USER_SUSPENDED,
1992
                                 ])
1998
    ASSOCIATED_STATES = set([REQUESTED,
1999
                             ACCEPTED,
2000
                             LEAVE_REQUESTED,
2001
                             USER_SUSPENDED,
2002
                             ])
1993 2003

  
1994
    ACCEPTED_STATES     =   set([ACCEPTED,
1995
                                 LEAVE_REQUESTED,
1996
                                 USER_SUSPENDED,
1997
                                 ])
2004
    ACCEPTED_STATES = set([ACCEPTED,
2005
                           LEAVE_REQUESTED,
2006
                           USER_SUSPENDED,
2007
                           ])
1998 2008

  
1999
    ACTUALLY_ACCEPTED   =   set([ACCEPTED, LEAVE_REQUESTED])
2009
    ACTUALLY_ACCEPTED = set([ACCEPTED, LEAVE_REQUESTED])
2000 2010

  
2001
    state               =   models.IntegerField(default=REQUESTED,
2002
                                                db_index=True)
2003
    acceptance_date     =   models.DateTimeField(null=True, db_index=True)
2004
    leave_request_date  =   models.DateTimeField(null=True)
2011
    state = models.IntegerField(default=REQUESTED,
2012
                                db_index=True)
2013
    acceptance_date = models.DateTimeField(null=True, db_index=True)
2014
    leave_request_date = models.DateTimeField(null=True)
2005 2015

  
2006
    objects     =   ProjectMembershipManager()
2016
    objects = ProjectMembershipManager()
2007 2017

  
2008 2018
    # Compiled queries
2009 2019
    Q_ACCEPTED_STATES = ~Q(state=REQUESTED) & ~Q(state=REMOVED)
2010 2020
    Q_ACTUALLY_ACCEPTED = Q(state=ACCEPTED) | Q(state=LEAVE_REQUESTED)
2011 2021

  
2012 2022
    MEMBERSHIP_STATE_DISPLAY = {
2013
        REQUESTED           : _('Requested'),
2014
        ACCEPTED            : _('Accepted'),
2015
        LEAVE_REQUESTED     : _('Leave Requested'),
2016
        USER_SUSPENDED      : _('Suspended'),
2017
        REMOVED             : _('Pending removal'),
2018
        }
2023
        REQUESTED:       _('Requested'),
2024
        ACCEPTED:        _('Accepted'),
2025
        LEAVE_REQUESTED: _('Leave Requested'),
2026
        USER_SUSPENDED:  _('Suspended'),
2027
        REMOVED:         _('Pending removal'),
2028
    }
2019 2029

  
2020 2030
    USER_FRIENDLY_STATE_DISPLAY = {
2021
        REQUESTED           : _('Join requested'),
2022
        ACCEPTED            : _('Accepted member'),
2023
        LEAVE_REQUESTED     : _('Requested to leave'),
2024
        USER_SUSPENDED      : _('Suspended member'),
2025
        REMOVED             : _('Pending removal'),
2026
        }
2031
        REQUESTED:       _('Join requested'),
2032
        ACCEPTED:        _('Accepted member'),
2033
        LEAVE_REQUESTED: _('Requested to leave'),
2034
        USER_SUSPENDED:  _('Suspended member'),
2035
        REMOVED:         _('Pending removal'),
2036
    }
2027 2037

  
2028 2038
    def state_display(self):
2029 2039
        return self.MEMBERSHIP_STATE_DISPLAY.get(self.state, _('Unknown'))
......
2036 2046
        #index_together = [["project", "state"]]
2037 2047

  
2038 2048
    def __str__(self):
2039
        return uenc(_("<'%s' membership in '%s'>") % (
2040
                self.person.username, self.project))
2049
        return uenc(_("<'%s' membership in '%s'>") %
2050
                    (self.person.username, self.project))
2041 2051

  
2042 2052
    __repr__ = __str__
2043 2053

  
......
2050 2060
            reason = ProjectMembershipHistory.reasons.get(reason, -1)
2051 2061

  
2052 2062
        history_item = ProjectMembershipHistory(
2053
                            serial=self.id,
2054
                            person=self.person_id,
2055
                            project=self.project_id,
2056
                            date=date or datetime.now(),
2057
                            reason=reason)
2063
            serial=self.id,
2064
            person=self.person_id,
2065
            project=self.project_id,
2066
            date=date or datetime.now(),
2067
            reason=reason)
2058 2068
        history_item.save()
2059 2069
        serial = history_item.id
2060 2070

  
......
2150 2160

  
2151 2161

  
2152 2162
class Serial(models.Model):
2153
    serial  =   models.AutoField(primary_key=True)
2163
    serial = models.AutoField(primary_key=True)
2154 2164

  
2155 2165

  
2156 2166
class ProjectMembershipHistory(models.Model):
2157
    reasons_list    =   ['ACCEPT', 'REJECT', 'REMOVE']
2158
    reasons         =   dict((k, v) for v, k in enumerate(reasons_list))
2167
    reasons_list = ['ACCEPT', 'REJECT', 'REMOVE']
2168
    reasons = dict((k, v) for v, k in enumerate(reasons_list))
2169

  
2170
    person = models.BigIntegerField()
2171
    project = models.BigIntegerField()
2172
    date = models.DateTimeField(auto_now_add=True)
2173
    reason = models.IntegerField()
2174
    serial = models.BigIntegerField()
2159 2175

  
2160
    person  =   models.BigIntegerField()
2161
    project =   models.BigIntegerField()
2162
    date    =   models.DateTimeField(auto_now_add=True)
2163
    reason  =   models.IntegerField()
2164
    serial  =   models.BigIntegerField()
2165 2176

  
2166 2177
### SIGNALS ###
2167 2178
################
......
2178 2189
    except BaseException, e:
2179 2190
        logger.exception(e)
2180 2191

  
2192

  
2181 2193
def fix_superusers():
2182 2194
    # Associate superusers with AstakosUser
2183 2195
    admins = User.objects.filter(is_superuser=True)
2184 2196
    for u in admins:
2185 2197
        create_astakos_user(u)
2186 2198

  
2199

  
2187 2200
def user_post_save(sender, instance, created, **kwargs):
2188 2201
    if not created:
2189 2202
        return
2190 2203
    create_astakos_user(instance)
2191 2204
post_save.connect(user_post_save, sender=User)
2192 2205

  
2206

  
2193 2207
def astakosuser_post_save(sender, instance, created, **kwargs):
2194 2208
    pass
2195 2209

  
2196 2210
post_save.connect(astakosuser_post_save, sender=AstakosUser)
2197 2211

  
2212

  
2198 2213
def resource_post_save(sender, instance, created, **kwargs):
2199 2214
    pass
2200 2215

  
2201 2216
post_save.connect(resource_post_save, sender=Resource)
2202 2217

  
2218

  
2203 2219
def renew_token(sender, instance, **kwargs):
2204 2220
    if not instance.auth_token:
2205 2221
        instance.renew_token()

Also available in: Unified diff