Revision 8e1a5af5

b/snf-astakos-app/astakos/im/models.py
68 68
    DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
69 69
    AUTH_TOKEN_DURATION, EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
70 70
    SITENAME, SERVICES, MODERATION_ENABLED, RESOURCES_PRESENTATION_DATA,
71
    PROJECT_MEMBER_JOIN_POLICIES, PROJECT_MEMBER_LEAVE_POLICIES)
71
    PROJECT_MEMBER_JOIN_POLICIES, PROJECT_MEMBER_LEAVE_POLICIES, PROJECT_ADMINS)
72 72
from astakos.im import settings as astakos_settings
73 73
from astakos.im.endpoints.qh import (
74 74
    register_users, send_quotas, qh_check_users, qh_get_quota_limits,
......
428 428
                                   content_type=get_content_type())
429 429
        self.user_permissions.remove(p)
430 430

  
431
    def is_project_admin(self, application_id=None):
432
        return self.uuid in PROJECT_ADMINS
433

  
431 434
    @property
432 435
    def invitation(self):
433 436
        try:
......
815 818
            return m.user_friendly_state_display()
816 819

  
817 820
    def non_owner_can_view(self, maybe_project):
821
        if self.is_project_admin():
822
            return True
818 823
        if maybe_project is None:
819 824
            return False
820 825
        project = maybe_project
......
1422 1427
        """
1423 1428
        Return projects accessed by specified user.
1424 1429
        """
1425
        participates_filters = Q(owner=user) | Q(applicant=user) | \
1426
                               Q(project__projectmembership__person=user)
1430
        if user.is_project_admin():
1431
            participates_filters = Q()
1432
        else:
1433
            participates_filters = Q(owner=user) | Q(applicant=user) | \
1434
                                   Q(project__projectmembership__person=user)
1427 1435

  
1428 1436
        return self.user_visible_by_chain(participates_filters).order_by('issue_date').distinct()
1429 1437

  
......
1612 1620
        except Project.DoesNotExist:
1613 1621
            return None
1614 1622

  
1623
    def can_be_approved(self):
1624
        return self.state == self.PENDING
1625

  
1626
    def can_be_dismissed(self):
1627
        return self.state == self.DENIED
1628

  
1615 1629
    def can_cancel(self):
1616 1630
        return self.state == self.PENDING
1617 1631

  
b/snf-astakos-app/astakos/im/settings.py
331 331
ACTIVATION_REDIRECT_URL = getattr(settings,
332 332
                                  'ASTAKOS_ACTIVATION_REDIRECT_URL',
333 333
                                  "/im/landing")
334

  
335
# Users that can approve or deny project applications from the web.
336
PROJECT_ADMINS = getattr(settings, 'ASTAKOS_PROJECT_ADMINS', set())
b/snf-astakos-app/astakos/im/tables.py
286 286
                project = record.project
287 287
                return self.user.membership_display(project)
288 288
            except Project.DoesNotExist:
289
                return _(Unknown)
289
                return _("Unknown")
290 290

  
291 291
    def render_members_count(self, record, *args, **kwargs):
292 292
        append = ""
b/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html
7 7
<div class="projects">
8 8
  <h2>
9 9
    <em>
10
      {% if owner_mode %}
10
      {% if owner_mode or admin_mode %}
11 11
        {% if project_view %}
12 12
           PROJECT {{ object.project_state_display|upper }}
13 13
          {% if object.has_pending_modifications %} -
......
43 43
    </span>
44 44

  
45 45
    <!-- make room for buttons -->
46
    {% if owner_mode or can_join_request or can_leave_request %}
46
    {% if owner_mode or admin_mode or can_join_request or can_leave_request %}
47 47
      <br />
48 48
    {% endif %}
49 49

  
50
    {% if owner_mode %}
50
    {% if owner_mode or admin_mode %}
51 51
      <a style="font-size:0.7em"
52 52
         href="{% url astakos.im.views.project_modify object.pk %}">MODIFY</a>
53 53

  
54
      {% with object.last_pending_incl_me as last_pending %}
55
      {% if last_pending %}
56
        -
57
        <a style="font-size:0.7em"
58
           href="{% url astakos.im.views.project_app_cancel last_pending.pk %}">
59
          CANCEL {% if object.project_exists %} MODIFICATION {% else %}
60
          PROJECT {% endif %} REQUEST
61
        </a>
54
      {% if owner_mode %}
55
          {% with object.last_pending_incl_me as last_pending %}
56
          {% if last_pending %}
57
            -
58
            <a style="font-size:0.7em"
59
               href="{% url astakos.im.views.project_app_cancel last_pending.pk %}">
60
              CANCEL {% if object.project_exists %} MODIFICATION {% else %}
61
              PROJECT {% endif %} REQUEST
62
            </a>
63
          {% endif %}
64
          {% endwith %}
62 65
      {% endif %}
63
      {% endwith %}
64 66

  
67
      {% if admin_mode %}
68
          {% if object.can_be_approved %}
69
              - <a style="font-size:0.7em"
70
                  href="{% url astakos.im.views.project_app_approve object.pk %}">
71
                  APPROVE</a>
72
              - <a style="font-size:0.7em"
73
                  href="{% url astakos.im.views.project_app_deny object.pk %}">
74
                  DENY</a>
75
          {% endif %}
76
      {% endif %}
77

  
78
      {% if owner_mode %}
79
          {% if object.can_be_dismissed %}
80
             - <a style="font-size:0.7em"
81
                href="{% url astakos.im.views.project_app_dismiss object.pk %}">
82
                DISMISS</a>
83
          {% endif %}
84
      {% endif %}
65 85
      <!-- only one is possible, perhaps add cancel button too -->
66 86
      {% if can_join_request or can_leave_request %}
67 87
        <br />
b/snf-astakos-app/astakos/im/urls.py
71 71
    url(r'^projects/(?P<chain_id>\d+)/(?P<user_id>\d+)/remove/?$', 'project_remove_member', {}, name='project_remove_member'),
72 72
    url(r'^projects/app/(?P<application_id>\d+)/?$', 'project_app', {}, name='project_app'),
73 73
    url(r'^projects/app/(?P<application_id>\d+)/modify$', 'project_modify', {}, name='project_modify'),
74
    url(r'^projects/app/(?P<application_id>\d+)/approve$', 'project_app_approve', {}, name='project_app_approve'),
75
    url(r'^projects/app/(?P<application_id>\d+)/deny$', 'project_app_deny', {}, name='project_app_deny'),
76
    url(r'^projects/app/(?P<application_id>\d+)/dismiss$', 'project_app_dismiss', {}, name='project_app_dismiss'),
74 77
    url(r'^projects/app/(?P<application_id>\d+)/cancel$', 'project_app_cancel', {}, name='project_app_cancel'),
75 78

  
76 79
    url(r'^projects/how_it_works/?$', 'how_it_works', {}, name='how_it_works'),
b/snf-astakos-app/astakos/im/views.py
100 100
    SendNotificationError,
101 101
    accept_membership, reject_membership, remove_membership, cancel_membership,
102 102
    leave_project, join_project, enroll_member, can_join_request, can_leave_request,
103
    cancel_application, get_related_project_id,
104
    get_by_chain_or_404)
103
    get_related_project_id, get_by_chain_or_404,
104
    approve_application, deny_application,
105
    cancel_application, dismiss_application)
105 106
from astakos.im.settings import (
106 107
    COOKIE_DOMAIN, LOGOUT_NEXT,
107 108
    LOGGING_LEVEL, PAGINATE_BY,
......
1246 1247
    modifications_table = None
1247 1248

  
1248 1249
    user = request.user
1250
    is_project_admin = user.is_project_admin(application_id=application.id)
1249 1251
    is_owner = user.owns_application(application)
1250 1252
    if not is_owner and not project_view:
1251 1253
        m = _(astakos_messages.NOT_ALLOWED)
......
1277 1279
            'addmembers_form':addmembers_form,
1278 1280
            'members_table': members_table,
1279 1281
            'owner_mode': is_owner,
1282
            'admin_mode': is_project_admin,
1280 1283
            'modifications_table': modifications_table,
1281 1284
            'mem_display': mem_display,
1282 1285
            'can_join_request': can_join_req,
......
1473 1476
        messages.success(request, msg)
1474 1477
    return redirect(reverse('project_detail', args=(chain_id,)))
1475 1478

  
1479
@require_http_methods(["POST", "GET"])
1480
@signed_terms_required
1481
@login_required
1482
@project_transaction_context(sync=True)
1483
def project_app_approve(request, application_id, ctx=None):
1484

  
1485
    if not request.user.is_project_admin():
1486
        m = _(astakos_messages.NOT_ALLOWED)
1487
        raise PermissionDenied(m)
1488

  
1489
    try:
1490
        app = ProjectApplication.objects.get(id=application_id)
1491
    except ProjectApplication.DoesNotExist:
1492
        raise Http404
1493

  
1494
    approve_application(application_id)
1495
    chain_id = get_related_project_id(application_id)
1496
    return redirect(reverse('project_detail', args=(chain_id,)))
1497

  
1498
@require_http_methods(["POST", "GET"])
1499
@signed_terms_required
1500
@login_required
1501
@project_transaction_context()
1502
def project_app_deny(request, application_id, ctx=None):
1503

  
1504
    if not request.user.is_project_admin():
1505
        m = _(astakos_messages.NOT_ALLOWED)
1506
        raise PermissionDenied(m)
1507

  
1508
    try:
1509
        app = ProjectApplication.objects.get(id=application_id)
1510
    except ProjectApplication.DoesNotExist:
1511
        raise Http404
1512

  
1513
    deny_application(application_id)
1514
    return redirect(reverse('project_list'))
1515

  
1516
@require_http_methods(["POST", "GET"])
1517
@signed_terms_required
1518
@login_required
1519
@project_transaction_context()
1520
def project_app_dismiss(request, application_id, ctx=None):
1521
    try:
1522
        app = ProjectApplication.objects.get(id=application_id)
1523
    except ProjectApplication.DoesNotExist:
1524
        raise Http404
1525

  
1526
    if not request.user.owns_application(app):
1527
        m = _(astakos_messages.NOT_ALLOWED)
1528
        raise PermissionDenied(m)
1529

  
1530
    # XXX: dismiss application also does authorization
1531
    dismiss_application(application_id, request_user=request.user)
1532
    return redirect(reverse('project_list'))
1533

  
1476 1534
def landing(request):
1477 1535
    return render_response(
1478 1536
        'im/landing.html',
b/snf-astakos-app/conf/20-snf-astakos-app-settings.conf
139 139
# NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
140 140

  
141 141
# Permit local account migration
142
# ENABLE_LOCAL_ACCOUNT_MIGRATION = getattr(settings, 'ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION', True)
142
# ENABLE_LOCAL_ACCOUNT_MIGRATION = getattr(settings, 'ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION', True)
143

  
144
# UUIDs of users that can approve or deny project applications from the web.
145
# ASTAKOS_PROJECT_ADMINS = set() # e.g. set(['01234567-89ab-cdef-0123-456789abcdef'])

Also available in: Unified diff