Revision db99f198

b/snf-astakos-app/astakos/im/functions.py
713 713

  
714 714
def suspend(project_id):
715 715
    project = get_project_by_id(project_id)
716
    project.last_approval_date = None
717
    project.save()
716
    checkAlive(project)
717

  
718
    project.suspend()
718 719
    trigger_sync()
719 720

  
720 721
    project_suspension_notify(project)
722

  
723
def resume(project_id):
724
    project = get_project_for_update(project_id)
725

  
726
    if not project.is_suspended:
727
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
728
        raise PermissionDenied(m)
729

  
730
    project.resume()
731
    trigger_sync()
b/snf-astakos-app/astakos/im/management/commands/project-update.py
41 41
from astakos.im.models import (
42 42
    ProjectApplication, Project)
43 43

  
44
from astakos.im.functions import terminate, suspend
44
from astakos.im.functions import terminate, suspend, resume
45 45

  
46 46
@transaction.commit_manually
47 47
class Command(BaseCommand):
......
54 54
                    dest='terminate',
55 55
                    default=False,
56 56
                    help="Terminate group"),
57
        make_option('--resume',
58
                    action='store_true',
59
                    dest='resume',
60
                    default=False,
61
                    help="Resume group"),
57 62
        make_option('--suspend',
58 63
                    action='store_true',
59 64
                    dest='suspend',
......
73 78
            try:
74 79
                if options['terminate']:
75 80
                    terminate(id)
81
                elif options['resume']:
82
                    resume(id)
76 83
                elif options['suspend']:
77 84
                    suspend(id)
78 85
            except BaseException, e:
b/snf-astakos-app/astakos/im/messages.py
163 163
UNIQUE_PROJECT_NAME_CONSTRAIN_ERR       =   'The project name (as specified in its application\'s definition) must be unique among all active projects.'
164 164
INVALID_PROJECT                         =   'Project %(id)s is invalid.'
165 165
NOT_ALIVE_PROJECT                       =   'Project %(id)s is not alive.'
166
NOT_SUSPENDED_PROJECT                   =   'Project %(id)s is not suspended.'
166 167
NOT_ALLOWED                             =   'You do not have the permissions to perform this action.'
167 168
MEMBER_NUMBER_LIMIT_REACHED             =   'You have reached the maximum number of members for this Project.'
168 169
MEMBER_JOIN_POLICY_CLOSED               =   'The Project\'s member join policy is closed.'
b/snf-astakos-app/astakos/im/models.py
1524 1524

  
1525 1525
    def _q_terminated(self):
1526 1526
        return Q(state=Project.TERMINATED)
1527
    def _q_suspended(self):
1528
        return Q(state=Project.SUSPENDED)
1529
    def _q_deactivated(self):
1530
        return self._q_terminated() | self._q_suspended()
1527 1531

  
1528 1532
    def terminated_projects(self):
1529 1533
        q = self._q_terminated()
......
1537 1541
        q = self._q_terminated() & Q(is_active=True)
1538 1542
        return self.filter(q)
1539 1543

  
1544
    def deactivated_projects(self):
1545
        q = self._q_deactivated()
1546
        return self.filter(q)
1547

  
1548
    def deactivating_projects(self):
1549
        q = self._q_deactivated() & Q(is_active=True)
1550
        return self.filter(q)
1551

  
1540 1552
    def modified_projects(self):
1541 1553
        return self.filter(is_modified=True)
1542 1554

  
1555
    def reactivating_projects(self):
1556
        return self.filter(state=Project.APPROVED, is_active=False)
1543 1557

  
1544 1558
class Project(models.Model):
1545 1559

  
......
1603 1617
        self.deactivation_date = datetime.now()
1604 1618
        self.is_active = False
1605 1619

  
1620
    def reactivate(self):
1621
        self.deactivation_date = None
1622
        self.is_active = True
1623

  
1606 1624
    def terminate(self):
1607 1625
        self.deactivation_reason = 'TERMINATED'
1608 1626
        self.state = self.TERMINATED
1609 1627
        self.save()
1610 1628

  
1629
    def suspend(self):
1630
        self.deactivation_reason = 'SUSPENDED'
1631
        self.state = self.SUSPENDED
1632
        self.save()
1633

  
1634
    def resume(self):
1635
        self.deactivation_reason = None
1636
        self.state = self.APPROVED
1637
        self.save()
1611 1638

  
1612 1639
    ### Logical checks
1613 1640

  
......
1621 1648
    def is_active_strict(self):
1622 1649
        return self.is_active and self.state == self.APPROVED
1623 1650

  
1651
    def is_approved(self):
1652
        return self.state == self.APPROVED
1653

  
1624 1654
    @property
1625 1655
    def is_alive(self):
1626 1656
        return self.is_active_strict()
......
1631 1661

  
1632 1662
    @property
1633 1663
    def is_suspended(self):
1634
        return False
1664
        return self.is_deactivated(self.SUSPENDED)
1635 1665

  
1636 1666
    def violates_resource_grants(self):
1637 1667
        return False
......
1687 1717
    pass
1688 1718

  
1689 1719

  
1720
class ProjectMembershipManager(ForUpdateManager):
1721
    pass
1722

  
1690 1723
class ProjectMembership(models.Model):
1691 1724

  
1692 1725
    person              =   models.ForeignKey(AstakosUser)
1693 1726
    request_date        =   models.DateField(auto_now_add=True)
1694 1727
    project             =   models.ForeignKey(Project)
1695 1728

  
1696
    REQUESTED   =   0
1697
    ACCEPTED    =   1
1698
    SUSPENDED   =   10
1699
    TERMINATED  =   100
1700
    REMOVED     =   200
1729
    REQUESTED           =   0
1730
    ACCEPTED            =   1
1731
    # User deactivation
1732
    USER_SUSPENDED      =   10
1733
    # Project deactivation
1734
    PROJECT_DEACTIVATED =   100
1701 1735

  
1702
    ASSOCIATED_STATES   =   set([REQUESTED, ACCEPTED, SUSPENDED, TERMINATED])
1703
    ACCEPTED_STATES     =   set([ACCEPTED, SUSPENDED, TERMINATED])
1736
    REMOVED             =   200
1737

  
1738
    ASSOCIATED_STATES   =   set([REQUESTED,
1739
                                 ACCEPTED,
1740
                                 USER_SUSPENDED,
1741
                                 PROJECT_DEACTIVATED])
1742

  
1743
    ACCEPTED_STATES     =   set([ACCEPTED,
1744
                                 USER_SUSPENDED,
1745
                                 PROJECT_DEACTIVATED])
1704 1746

  
1705 1747
    state               =   models.IntegerField(default=REQUESTED,
1706 1748
                                                db_index=True)
......
1719 1761
    acceptance_date     =   models.DateField(null=True, db_index=True)
1720 1762
    leave_request_date  =   models.DateField(null=True)
1721 1763

  
1722
    objects     =   ForUpdateManager()
1764
    objects     =   ProjectMembershipManager()
1723 1765

  
1724 1766

  
1725 1767
    def get_combined_state(self):
......
1770 1812
        now = datetime.now()
1771 1813
        self.acceptance_date = now
1772 1814
        self._set_history_item(reason='ACCEPT', date=now)
1773
        if self.project.is_active_strict():
1815
        if self.project.is_approved():
1774 1816
            self.state = self.ACCEPTED
1775 1817
            self.is_pending = True
1776 1818
        else:
1777
            self.state = self.TERMINATED
1819
            self.state = self.PROJECT_DEACTIVATED
1778 1820

  
1779 1821
        self.save()
1780 1822

  
......
1784 1826
            raise AssertionError(m)
1785 1827

  
1786 1828
        state = self.state
1787
        if state not in [self.ACCEPTED, self.TERMINATED]:
1829
        if state not in self.ACCEPTED_STATES:
1788 1830
            m = _("%s: attempt to remove in state '%s'") % (self, state)
1789 1831
            raise AssertionError(m)
1790 1832

  
......
1869 1911
                self.is_pending = False
1870 1912
            self.save()
1871 1913

  
1872
        elif state == self.TERMINATED:
1914
        elif state == self.PROJECT_DEACTIVATED:
1873 1915
            if self.pending_application:
1874 1916
                m = _("%s: attempt to sync in state '%s' "
1875 1917
                      "with a pending application") % (self, state)
......
1893 1935
            raise AssertionError(m)
1894 1936

  
1895 1937
        state = self.state
1896
        if state in [self.ACCEPTED, self.TERMINATED, self.REMOVED]:
1938
        if state in [self.ACCEPTED, self.PROJECT_DEACTIVATED, self.REMOVED]:
1897 1939
            self.pending_application = None
1898 1940
            self.pending_serial = None
1899 1941
            self.save()
......
1933 1975

  
1934 1976
def pre_sync():
1935 1977
    ACCEPTED = ProjectMembership.ACCEPTED
1936
    TERMINATED = ProjectMembership.TERMINATED
1978
    PROJECT_DEACTIVATED = ProjectMembership.PROJECT_DEACTIVATED
1937 1979
    psfu = Project.objects.select_for_update()
1938 1980

  
1939 1981
    modified = psfu.modified_projects()
......
1945 1987
            membership.is_pending = True
1946 1988
            membership.save()
1947 1989

  
1948
    terminating = psfu.terminating_projects()
1949
    for project in terminating:
1990
    reactivating = psfu.reactivating_projects()
1991
    for project in reactivating:
1992
        objects = project.projectmembership_set.select_for_update()
1993
        memberships = objects.filter(state=PROJECT_DEACTIVATED)
1994
        for membership in memberships:
1995
            membership.is_pending = True
1996
            membership.state = ACCEPTED
1997
            membership.save()
1998

  
1999
    deactivating = psfu.deactivating_projects()
2000
    for project in deactivating:
1950 2001
        objects = project.projectmembership_set.select_for_update()
1951 2002

  
2003
        # Note: we keep a user-level deactivation (e.g. USER_SUSPENDED) intact
1952 2004
        memberships = objects.filter(state=ACCEPTED)
1953 2005
        for membership in memberships:
1954 2006
            membership.is_pending = True
1955
            membership.state = TERMINATED
2007
            membership.state = PROJECT_DEACTIVATED
1956 2008
            membership.save()
1957 2009

  
1958 2010
def do_sync():
......
1998 2050

  
1999 2051
def post_sync():
2000 2052
    ACCEPTED = ProjectMembership.ACCEPTED
2053
    PROJECT_DEACTIVATED = ProjectMembership.PROJECT_DEACTIVATED
2001 2054
    psfu = Project.objects.select_for_update()
2002 2055

  
2003 2056
    modified = psfu.modified_projects()
......
2009 2062
            project.is_modified = False
2010 2063
            project.save()
2011 2064

  
2012
    terminating = psfu.terminating_projects()
2013
    for project in terminating:
2065
    reactivating = psfu.reactivating_projects()
2066
    for project in reactivating:
2067
        objects = project.projectmembership_set.select_for_update()
2068
        memberships = list(objects.filter(Q(state=PROJECT_DEACTIVATED) |
2069
                                          Q(is_pending=True)))
2070
        if not memberships:
2071
            project.reactivate()
2072
            project.save()
2073

  
2074
    deactivating = psfu.deactivating_projects()
2075
    for project in deactivating:
2014 2076
        objects = project.projectmembership_set.select_for_update()
2015 2077

  
2016 2078
        memberships = list(objects.filter(Q(state=ACCEPTED) |

Also available in: Unified diff