Revision 05617ab9

b/snf-astakos-app/astakos/im/forms.py
780 780
    def clean_start_date(self):
781 781
        start_date = self.cleaned_data.get('start_date')
782 782
        if not self.precursor_application:
783
            if start_date and (start_date - datetime.now()).days < 0:
783
            if start_date and (start_date - datetime.now()).days < -1:
784 784
                raise forms.ValidationError(
785 785
                _(astakos_messages.INVALID_PROJECT_START_DATE))
786 786
        return start_date
b/snf-astakos-app/astakos/im/models.py
683 683
    def owns_project(self, project):
684 684
        return project.owner == self
685 685

  
686
    def is_project_member(self, project):
687
        return project.user_status(self) in [0,1,2,3]
686
    def is_project_member(self, project_or_application):
687
        return self.get_status_in_project(project_or_application) in \
688
                                        ProjectMembership.ASSOCIATED_STATES
688 689

  
689
    def is_project_accepted_member(self, project):
690
        return project.user_status(self) == 2
690
    def is_project_accepted_member(self, project_or_application):
691
        return self.get_status_in_project(project_or_application) in \
692
                                            ProjectMembership.ACCEPTED_STATES
693

  
694
    def get_status_in_project(self, project_or_application):
695
        application = project_or_application
696
        if isinstance(project_or_application, Project):
697
            application = project_or_application.project
698
        return application.user_status(self)
691 699

  
692 700

  
693 701
class AstakosUserAuthProviderManager(models.Manager):
......
1148 1156

  
1149 1157
class ProjectApplicationManager(ForUpdateManager):
1150 1158

  
1151
    def user_projects(self, user):
1159
    def user_visible_projects(self, *filters, **kw_filters):
1160
        return self.filter(Q(state=ProjectApplication.PENDING)|\
1161
                           Q(state=ProjectApplication.APPROVED))
1162

  
1163
    def user_visible_by_last_of_chain(self, *filters, **kw_filters):
1164
        by_chain = self.user_visible_projects(*filters, **kw_filters).values('chain')
1165
        by_chain = by_chain.annotate(last_id=models.Min('id')).values_list('last_id', flat=True)
1166
        return self.filter(id__in=by_chain)
1167

  
1168
    def user_accessible_projects(self, user):
1152 1169
        """
1153 1170
        Return projects accessed by specified user.
1154 1171
        """
1155
        participates_fitlers = Q(owner=user) | Q(applicant=user) | \
1172
        participates_filters = Q(owner=user) | Q(applicant=user) | \
1156 1173
                               Q(project__projectmembership__person=user)
1157
        state_filters = (Q(state=ProjectApplication.PENDING) & \
1158
                        Q(precursor_application__isnull=True)) | \
1159
                        Q(state=ProjectApplication.APPROVED)
1160
        return self.filter(participates_fitlers & state_filters).order_by('issue_date').distinct()
1174

  
1175
        return self.user_visible_by_last_of_chain(participates_filters).order_by('issue_date').distinct()
1161 1176

  
1162 1177
    def search_by_name(self, *search_strings):
1163 1178
        q = Q()
......
1253 1268
        q.create(resource=resource, member_capacity=uplimit)
1254 1269

  
1255 1270
    def user_status(self, user):
1256
        """
1257
        100 OWNER
1258
        0   REQUESTED
1259
        1   PENDING
1260
        2   ACCEPTED
1261
        3   REMOVING
1262
        4   REMOVED
1263
       -1   User has no association with the project
1264
        """
1265 1271
        try:
1266
            membership = self.project.projectmembership_set.get(person=user)
1272
            project = self.get_project()
1273
            if not project:
1274
                return -1
1275
            membership = project.projectmembership_set.get(person=user)
1276
            membership = membership.exclude(state=ProjectMembership.REMOVED)
1267 1277
            status = membership.state
1268
        except Project.DoesNotExist:
1269
            status = -1
1270 1278
        except ProjectMembership.DoesNotExist:
1271 1279
            status = -1
1272 1280

  
......
1303 1311
            return
1304 1312

  
1305 1313
    def followers(self):
1306
        current = self
1307
        try:
1308
            while current.projectapplication:
1309
                yield current.follower
1310
                current = current.follower
1311
        except:
1312
            pass
1314
        followers = ProjectApplication.objects.filter(chain=self.chain)
1315
        return followers.exclude(pk=self.pk).order_by('id')
1313 1316

  
1314 1317
    def last_follower(self):
1315 1318
        try:
1316
            return list(self.followers())[-1]
1319
            return self.followers().filter(
1320
                          state__in=[self.PENDING, self.APPROVED]).order_by('-id')[0]
1317 1321
        except IndexError:
1322
            import traceback; print traceback.print_exc()
1323
            return None
1324

  
1325
    def has_pending_modifications(self):
1326
        return bool(self.last_follower())
1327

  
1328
    def get_project(self):
1329
        try:
1330
            return Project.objects.get(id=self.chain)
1331
        except Project.DoesNotExist:
1318 1332
            return None
1319 1333

  
1320 1334
    def _get_project_for_update(self):
......
1599 1613
    TERMINATED  =   100
1600 1614
    REMOVED     =   200
1601 1615

  
1616
    ASSOCIATED_STATES   =   set([REQUESTED, ACCEPTED, SUSPENDED, TERMINATED])
1617
    ACCEPTED_STATES     =   set([ACCEPTED, SUSPENDED, TERMINATED])
1618

  
1602 1619
    state               =   models.IntegerField(default=REQUESTED,
1603 1620
                                                db_index=True)
1604 1621
    is_pending          =   models.BooleanField(default=False, db_index=True)
b/snf-astakos-app/astakos/im/tables.py
44 44

  
45 45
from astakos.im.models import *
46 46
from astakos.im.templatetags.filters import truncatename
47
from astakos.im.functions import do_join_project_checks
47
from astakos.im.functions import do_join_project_checks, \
48
                                 do_leave_project_checks
48 49

  
49 50
DEFAULT_DATE_FORMAT = "d/m/Y"
50 51

  
......
184 185
    url, action, confirm, prompt = '', '', True, ''
185 186
    append_url = ''
186 187

  
187
    can_join = True
188

  
189
    try:
190
        project = Project.objects.get(application=application)
191
        do_join_project_checks(project)
192
    except (PermissionDenied, Project.DoesNotExist), e:
193
        can_join = False
194

  
195
    if can_join:
196
        if user.is_project_accepted_member(application):
197
            url = 'astakos.im.views.project_leave'
198
            action = _('Leave')
199
            confirm = True
200
            prompt = _('Are you sure you want to leave from the project ?')
201
        elif not user.is_project_member(application):
202
            url = 'astakos.im.views.project_join'
203
            action = _('Join')
204
            confirm = True
205
            prompt = _('Are you sure you want to join this project ?')
206
        else:
207
            action = ''
208
            confirm = False
209
            url = None
188
    can_join = can_leave = False
189
    member_status = application.user_status(user)
190
    project = application.get_project()
191

  
192
    if project:
193
        try:
194
            do_join_project_checks(project)
195
            can_join = True
196
        except PermissionDenied:
197
            pass
198

  
199
        try:
200
            do_leave_project_checks(project)
201
            can_leave = True
202
        except PermissionDenied:
203
            pass
204

  
205
    if can_leave and user.is_project_accepted_member(application):
206
        url = 'astakos.im.views.project_leave'
207
        action = _('Leave')
208
        confirm = True
209
        prompt = _('Are you sure you want to leave from the project ?')
210
    elif can_join and user.is_project_member(application):
211
        url = 'astakos.im.views.project_join'
212
        action = _('Join')
213
        confirm = True
214
        prompt = _('Are you sure you want to join this project ?')
215
    else:
216
        action = ''
217
        confirm = False
218
        url = None
210 219

  
211 220
    url = reverse(url, args=(application.pk, )) + append_url if url else ''
212 221
    return {'action': action,
......
229 238
        super(UserTable, self).__init__(*args, **kwargs)
230 239

  
231 240

  
232
def project_name_append(record, column):
233
    try:
234
        if record.projectapplication and column.table.user.owns_project(record):
235
            if record.state == ProjectApplication.APPROVED:
236
                return mark_safe("<br /><i class='tiny'>%s</i>" % _('modifications pending'))
237
            else:
238
                return u''
239
        else:
240
            return u''
241
    except:
242
        return u''
241
def project_name_append(application, column):
242
    if application.has_pending_modifications():
243
        return mark_safe("<br /><i class='tiny'>%s</i>" % \
244
                                                _('modifications pending'))
245
    return u''
243 246

  
244 247
# Table classes
245 248
class UserProjectApplicationsTable(UserTable):
b/snf-astakos-app/astakos/im/views.py
1048 1048
@signed_terms_required
1049 1049
@login_required
1050 1050
def project_list(request):
1051
    projects = ProjectApplication.objects.user_projects(request.user).select_related()
1051
    projects = ProjectApplication.objects.user_accessible_projects(request.user).select_related()
1052 1052
    table = tables.UserProjectApplicationsTable(projects, user=request.user,
1053 1053
                                                prefix="my_projects_")
1054 1054
    RequestConfig(request, paginate={"per_page": PAGINATE_BY}).configure(table)

Also available in: Unified diff