Commit 24e5c4e0 authored by Kostas Papadimitriou's avatar Kostas Papadimitriou
Browse files

Merge branch 'feature-astakos-tables' into latest-quota

parents c989aac5 56327f5a
......@@ -781,3 +781,4 @@ class ProjectMembersSortForm(forms.Form):
class ProjectSearchForm(forms.Form):
q = forms.CharField(max_length=200, label='Search project', required=False)
......@@ -664,6 +664,12 @@ class AstakosUser(User):
return mark_safe(message + u' '+ msg_extra)
def owns_project(self, project):
return project.user_status(self) == 100
def is_project_member(self, project):
return project.user_status(self) in [0,1,2,3]
class AstakosUserAuthProviderManager(models.Manager):
......@@ -1121,6 +1127,22 @@ def make_synced(prefix='sync', name='SyncedState'):
SyncedState = make_synced(prefix='sync', name='SyncedState')
class ProjectApplicationManager(ForUpdateManager):
def user_projects(self, user):
"""
Return projects accessed by specified user.
"""
return self.filter(Q(owner=user) | Q(applicant=user) | \
Q(project__in=user.projectmembership_set.filter()))
def search_by_name(self, *search_strings):
q = Q()
for s in search_strings:
q = q | Q(name__icontains=s)
return self.filter(q)
class ProjectApplication(models.Model):
PENDING, APPROVED, REPLACED, UNKNOWN = 'Pending', 'Approved', 'Replaced', 'Unknown'
applicant = models.ForeignKey(
......@@ -1159,7 +1181,8 @@ class ProjectApplication(models.Model):
comments = models.TextField(null=True, blank=True)
issue_date = models.DateTimeField()
objects = ForUpdateManager()
objects = ProjectApplicationManager()
def add_resource_policy(self, service, resource, uplimit):
"""Raises ObjectDoesNotExist, IntegrityError"""
......@@ -1167,11 +1190,36 @@ class ProjectApplication(models.Model):
resource = Resource.objects.get(service__name=service, name=resource)
q.create(resource=resource, member_capacity=uplimit)
def user_status(self, user):
"""
100 OWNER
0 REQUESTED
1 PENDING
2 ACCEPTED
3 REMOVING
4 REMOVED
-1 User has no association with the project
"""
if user == self.owner:
status = 100
else:
try:
membership = self.project.projectmembership_set.get(person=user)
status = membership.state
except Project.DoesNotExist:
status = -1
except ProjectMembership.DoesNotExist:
status = -1
return status
def members_count(self):
return self.project.approved_memberships.count()
@property
def grants(self):
return self.projectresourcegrant_set.values('member_capacity', 'resource__name', 'resource__service__name')
@property
def resource_policies(self):
return self.projectresourcegrant_set.all()
......
......@@ -43,6 +43,7 @@ installed_apps = [
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django_tables2',
# 'debug_toolbar',
]
......
# Copyright 2011-2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.template import Context, Template
from django.template.loader import render_to_string
from django_tables2 import A
import django_tables2 as tables
from astakos.im.models import *
DEFAULT_DATE_FORMAT = "d/m/Y"
MEMBER_STATUS_DISPLAY = {
100: _('Owner'),
0: _('Requested'),
1: _('Pending'),
2: _('Accepted'),
3: _('Removing'),
4: _('Removed'),
-1: _('Unregistered'),
}
# Helper columns
class RichLinkColumn(tables.TemplateColumn):
method = 'POST'
confirm_prompt = _('Yes')
cancel_prompt = _('No')
confirm = True
prompt = _('Confirm action ?')
action_tpl = None
action = _('Action')
extra_context = lambda record, table, column: {}
url = None
url_args = ()
resolve_func = None
def __init__(self, *args, **kwargs):
kwargs['template_name'] = kwargs.get('template_name',
'im/table_rich_link_column.html')
for attr in ['method', 'confirm_prompt',
'cancel_prompt', 'prompt', 'url',
'url_args', 'action', 'confirm',
'resolve_func', 'extra_context']:
setattr(self, attr, kwargs.pop(attr, getattr(self, attr)))
super(RichLinkColumn, self).__init__(*args, **kwargs)
def render(self, record, table, value, bound_column, **kwargs):
# If the table is being rendered using `render_table`, it hackily
# attaches the context to the table as a gift to `TemplateColumn`. If
# the table is being rendered via `Table.as_html`, this won't exist.
context = getattr(table, 'context', Context())
context.update(self.get_template_context(record, table, value,
bound_column, **kwargs))
try:
if self.template_code:
return Template(self.template_code).render(context)
else:
return render_to_string(self.template_name, context)
finally:
context.pop()
def get_confirm(self, record, table):
if callable(self.confirm):
return self.confirm(record, table)
return self.confirm
def resolved_url(self, record, table):
if callable(self.url):
return self.url(record, table)
if not self.url:
return '#'
args = list(self.url_args)
for index, arg in enumerate(args):
if isinstance(arg, A):
args[index] = arg.resolve(record)
return reverse(self.url, args=args)
def get_action(self, record, table):
if callable(self.action):
return self.action(record, table)
return self.action
def get_prompt(self, record, table):
if callable(self.prompt):
return self.prompt(record, table)
return self.prompt
def get_template_context(self, record, table, value, bound_column, **kwargs):
context = {'default': bound_column.default,
'record': record,
'value': value,
'col': self,
'url': self.resolved_url(record, table),
'prompt': self.get_prompt(record, table),
'action': self.get_action(record, table),
'confirm': self.get_confirm(record, table)
}
if self.extra_context:
context.update(self.extra_context(record, table, self))
return context
def action_extra_context(project, table, self):
user = table.user
url, action, confirm, prompt = '', '', True, ''
append_url = ''
if user.owns_project(project):
url = 'astakos.im.views.project_update'
action = _('Update')
confirm = False
prompt = ''
elif user.is_project_member(project):
url = 'astakos.im.views.project_leave'
action = _('- Leave')
confirm = True
prompt = _('Are you sure you want to leave from the project ?')
else:
url = 'astakos.im.views.project_join'
action = _('+ Join')
confirm = True
prompt = _('Are you sure you want to join this project ?')
return {'action': action, 'confirm': confirm,
'url': reverse(url, args=(project.pk, )) + append_url,
'prompt': prompt}
# Table classes
class UserProjectApplicationsTable(tables.Table):
def __init__(self, *args, **kwargs):
self.user = None
if 'request' in kwargs and kwargs.get('request').user:
self.user = kwargs.get('request').user
if 'user' in kwargs:
self.user = kwargs.pop('user')
super(UserProjectApplicationsTable, self).__init__(*args, **kwargs)
name = tables.LinkColumn('astakos.im.views.project_detail', args=(A('pk'),))
issue_date = tables.DateColumn(format=DEFAULT_DATE_FORMAT)
start_date = tables.DateColumn(format=DEFAULT_DATE_FORMAT)
members_count = tables.Column(verbose_name=_("Enrolled"), default=0,
sortable=False)
membership_status = tables.Column(verbose_name=_("My status"), empty_values=(),
orderable=False)
project_action = RichLinkColumn(verbose_name=_('Action'),
extra_context=action_extra_context,
sortable=False)
def render_membership_status(self, record, *args, **kwargs):
status = record.user_status(self.user)
if status == 100:
return record.state
else:
return MEMBER_STATUS_DISPLAY.get(status, 'Unknown')
class Meta:
model = ProjectApplication
fields = ('name', 'membership_status', 'issue_date', 'start_date', 'members_count')
attrs = {'id': 'projects-list', 'class': 'my-projects alt-style'}
caption = _('My projects')
template = "im/table_render.html"
class ProjectApplicationMembersTable(tables.Table):
name = tables.Column(accessor="person.last_name", verbose_name=_('Name'))
status = tables.Column(accessor="state", verbose_name=_('Status'))
def render_name(self, value, record, *args, **kwargs):
return record.person.realname
def render_status(self, value, *args, **kwargs):
return MEMBER_STATUS_DISPLAY.get(value, 'Unknown')
class Meta:
template = "im/table_render.html"
model = ProjectMembership
fields = ('name', 'status')
attrs = {'id': 'members-table', 'class': 'members-table alt-style'}
......@@ -180,7 +180,6 @@ def authenticated(
# authenticate user
response = prepare_response(request,
user,
userid,
request.GET.get('next'),
'renew' in request.GET)
messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
......
......@@ -65,7 +65,8 @@ def login(request):
"""
next = request.GET.get('next')
if not next:
return HttpResponseBadRequest(_(astakos_messages.MISSING_NEXT_PARAMETER))
next = reverse('astakos.im.views.home')
if not restrict_next(
next, domain=COOKIE_DOMAIN, allowed_schemes=('pithos',)
):
......
<div class="two-cols clearfix">
<div class="rt">
&nbsp;
</div>
<div class="lt">
<p>~okeanos gives the opportunity to Greek Academic or Research Organizations/Institutions/Faculty to run their own projects remotely on virtual infrastructure. Simple, fast and with minimal to no cost at all.</p>
<p><a href="{% url how_it_works %}" style="font-size:1.154em;">How it works ></a></p>
</div>
</div>
<div class="widjets">
<!--<a href="#" class="widjet-x" title="remove boxes">X</a>-->
<ul class="clearfix">
<li class="create">
<div>
<div class="wrap">
<p class="centered"><a href="{% url project_add %}"><img alt="THINK ABOUT IT" src="/static/im/images/create.png"></a></p>
<p class="txt">Create a new Project in seconds. Specify how many members it will have, which and how many virtual resources it will provide to its members. Describe its purpose. Submit your request and if accepted, you and your colleagues are ready to deploy!<br><br> </p>
<p><a href="{% url project_add %}">create a project ></a></p>
</div>
</div>
</li>
<li class="join">
<div>
<div class="wrap">
<p class="centered"><a href="{% url project_search %}"><img alt="THINK ABOUT IT" src="/static/im/images/join.png"></a></p>
<p class="txt">Become a member of an existing Project and instantly gain access to the resources it has to offer you. Search for open Projects and join for free. Contact the closed Projects administrators, if you think they will accept you. In two words: try to Join now. </p>
<p><a href="{% url project_search %}">join a project ></a></p>
</div>
</div>
</li>
</ul>
</div>
{% extends "im/account_base.html" %}
{% load filters %}
{% load astakos_tags filters django_tables2 %}
{% block page.body %}
{% with object.project.approved_members as approved_members %}
......@@ -120,70 +120,10 @@
</div>
{% if object.project.is_alive %}
<div class="full-dotted">
{% with page|concat:sorting as args %}
{% with object.project.projectmembership_set.select_related.all|paginate:args as membership %}
{% if membership %}
<table class="alt-style" id="members-table">
<caption>MEMBERS:</caption>
<thead>
<tr>
{%if user.is_superuser or user == object.owner %}<th>User Email</th>{% endif %}
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for m in membership.object_list %}
<tr>
{%if user.is_superuser or user == object.owner %}<td>{{m.person.email}}</td>{% endif %}
<td>{{m.person.realname}}</td>
{% if m.person == object.owner %}
<td>Owner</td>
{% else %}
{% if m.acceptance_date %}
<td>Approved {% if m.leave_request_date%}(User has requested to leave the project on:{{m.leave_request_date}}){% endif %}
{% if user == object.owner and user != m.person %}
<a href="{% url project_remove_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Remove</a>
{% endif %}
</td>
{% else %}
<td>Pending
{% if user == object.owner %}
<a href="{% url project_accept_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Accept</a>
<a href="{% url project_reject_member object.id m.person.id %}?{% if page %}page={{ page }}{% endif %}{% if sorting %}&sorting={{sorting}}{% endif %}">Reject</a>
{% endif %}
</td>
{% endif %}
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<p class="next-prev">
{% if membership.has_previous %}
<a href="?page={{ membership.previous_page_number }}{% if sorting %}&sorting={{sorting}}{% endif %}">< previous</a>
{% else %}
<a href="javascript:void()" class="disabled">< previous</a>
{% endif %}
{% if membership.has_next %}
<a href="?page={{ membership.next_page_number }}{% if sorting %}&sorting={{sorting}}{% endif %}">next ></a>
{% else %}
<a href="javascript:void()" class="disabled">next ></a>
{% endif %}
</p>
<p class="nums">
<span class="current">
Page {{ membership.number }} of {{ membership.paginator.num_pages }}
</span>
</p>
</div>
{% else %}
<p>No members yet!</p>
{% endif %}
{% endwith %}
{% endwith %}
<h3>MEMBERS</h3>
{% if members_table %}
{% render_table members_table %}
{% endif %}
</div>
......
{% extends "im/account_base.html" %}
{% load astakos_tags %}
{% load filters %}
{% load astakos_tags filters django_tables2 %}
{% block page.body %}
<div class="maincol {% block innerpage.class %}{% endblock %}">
<div class="projects">
<h2>PROJECTS</h2>
{% if form %}
<p>Search for existing Projects and join the ones you like. Please search by Project name. </p>
<form action="{% url project_search %}" method="post" class="withlabels signup submit-inline">{% csrf_token %}
{% include "im/form_render.html" %}
<div class="form-row submit">
<input type="submit" class="submit altcol" value="SEARCH" />
{% if q %}<a href="{% url project_search %}">clear</a>{% endif %}
</div>
</form>
{% include "im/projects/search_form.html" %}
{% else %}
<div class="two-cols clearfix">
<div class="rt">
&nbsp;
</div>
<div class="lt">
<p>~okeanos gives the opportunity to Greek Academic or Research Organizations/Institutions/Faculty to run their own projects remotely on virtual infrastructure. Simple, fast and with minimal to no cost at all.</p>
<p><a href="{% url how_it_works %}" style="font-size:1.154em;">How it works ></a></p>
</div>
</div>
<div class="widjets">
<!--<a href="#" class="widjet-x" title="remove boxes">X</a>-->
<ul class="clearfix">
<li class="create">
<div>
<div class="wrap">
<p class="centered"><a href="{% url project_add %}"><img alt="THINK ABOUT IT" src="/static/im/images/create.png"></a></p>
<p class="txt">Create a new Project in seconds. Specify how many members it will have, which and how many virtual resources it will provide to its members. Describe its purpose. Submit your request and if accepted, you and your colleagues are ready to deploy!<br><br> </p>
<p><a href="{% url project_add %}">create a project ></a></p>
</div>
</div>
</li>
<li class="join">
<div>
<div class="wrap">
<p class="centered"><a href="{% url project_search %}"><img alt="THINK ABOUT IT" src="/static/im/images/join.png"></a></p>
<p class="txt">Become a member of an existing Project and instantly gain access to the resources it has to offer you. Search for open Projects and join for free. Contact the closed Projects administrators, if you think they will accept you. In two words: try to Join now. </p>
<p><a href="{% url project_search %}">join a project ></a></p>
</div>
</div>
</li>
</ul>
{% include "im/projects/intro.html" %}
</div>
{% endif %}
{% with page_obj.object_list as object_list %}
<!-- Search project -->
{% if is_search %}
{% if object_list %}
<div>
<table class="alt-style complex" id="searchResults">
<caption>
{% if q %}SEARCH RESULTS{% else %}ALL PROJECTS{% endif %}
</caption>
<thead>
<tr>
<th><a href="?{% if q %}q={{q}}&{% endif %}sorting={% if sorting == 'name' %}-name{% else %}name{% endif %}#searchResults" class="sortable {% if sorting == 'name' %}asc{% endif %}">Name</a></th>
<th><a href="?{% if q %}q={{q}}&{% endif %}sorting={% if sorting == 'start_date' %}-start_date{% else %}start_date{% endif %}#searchResults" class="sortable {% if sorting == 'start_date' %}asc{% endif %}">Start Date</a></th>
<th><a href="?{% if q %}q={{q}}&{% endif %}sorting={% if sorting == 'end_date' %}-end_date{% else %}end_date{% endif %}#searchResults" class="sortable {% if sorting == 'end_date' %}asc{% endif %}">End Date</a></th>
<th ><a href="#searchResults">Members</a></th>
<th><a href="?{% if q %}q={{q}}&{% endif %}sorting={% if sorting == 'state' %}-state{% else %}state{% endif %}#searchResults" class="sortable {% if sorting == 'state' %}asc{% endif %}">Status</a></th>
<th>&nbsp;</th>
<th><a href="#searchResults">Membership Status</a></th>
<th>&nbsp;</th>
<!-- <th>&nbsp;</th>-->
</tr>
</thead>
<tbody>
{% for o in object_list %}
{% with o.project.members.all as members %}
{% with o.project.approved_members as approved_members%}
<tr class="{% cycle 'tr1' 'tr2' %}">
<td><a href="{% url project_detail o.id %}" title="visit project page">{{o.name|truncatename}}</a></td>
<td>{{o.start_date|date:"d/m/Y"}}</td>
<td>{{o.end_date|date:"d/m/Y"}}</td>
<td>{{approved_members|length}}</td>
<td>
{{o.state}}
</td>
<td>{% if user == o.owner %}{% if o.state != 'Replaced' %}<a href="{% url project_update o.id %}">Update</a>{% endif %}{% else %}&nbsp;{% endif %}</td>
<td>
<div class="msg-wrap">
{% if user == o.owner %}
Owner
{% else %}
{% if not user in members %}
Not member
{% else %}
{% if user in approved_members %}
Registered
{% else %}
Activation pending
{% endif %}
{% endif %}
{% endif %}
</div>
</td>
<td>
<div class="msg-wrap">
{% if user in members %}
{% if user in approved_members %}
{% if not user == o.owner %}
<form action="{% url project_leave o.id %}?next={{request.path}}" method="post" class="link-like">{% csrf_token %}
<input type="submit" value="x leave" class="leave"/>
</form>
<div class="dialog">