Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / tables.py @ 6c22d993

History | View | Annotate | Download (10.5 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from collections import defaultdict
35

    
36
from django.utils.translation import ugettext as _
37
from django.utils.safestring import mark_safe
38
from django.template import Context, Template
39
from django.template.loader import render_to_string
40

    
41
from django_tables2 import A
42
import django_tables2 as tables
43

    
44
from astakos.im.models import *
45

    
46
DEFAULT_DATE_FORMAT = "d/m/Y"
47

    
48

    
49
MEMBER_STATUS_DISPLAY = {
50
    100: _('Owner'),
51
      0: _('Requested'),
52
      1: _('Pending'),
53
      2: _('Accepted'),
54
      3: _('Removing'),
55
      4: _('Removed'),
56
     -1: _('Unregistered'),
57
}
58

    
59
# Helper columns
60
class RichLinkColumn(tables.TemplateColumn):
61

    
62
    method = 'POST'
63

    
64
    confirm_prompt = _('Yes')
65
    cancel_prompt = _('No')
66
    confirm = True
67

    
68
    prompt = _('Confirm action ?')
69

    
70
    action_tpl = None
71
    action = _('Action')
72
    extra_context = lambda record, table, column: {}
73

    
74
    url = None
75
    url_args = ()
76
    resolve_func = None
77

    
78
    def __init__(self, *args, **kwargs):
79
        kwargs['template_name'] = kwargs.get('template_name',
80
                                             'im/table_rich_link_column.html')
81
        for attr in ['method', 'confirm_prompt',
82
                     'cancel_prompt', 'prompt', 'url',
83
                     'url_args', 'action', 'confirm',
84
                     'resolve_func', 'extra_context']:
85
            setattr(self, attr, kwargs.pop(attr, getattr(self, attr)))
86

    
87
        super(RichLinkColumn, self).__init__(*args, **kwargs)
88

    
89
    def render(self, record, table, value, bound_column, **kwargs):
90
        # If the table is being rendered using `render_table`, it hackily
91
        # attaches the context to the table as a gift to `TemplateColumn`. If
92
        # the table is being rendered via `Table.as_html`, this won't exist.
93
        content = ''
94
        for extra_context in self.get_template_context(record, table, value,
95
                                                       bound_column, **kwargs):
96
            context = getattr(table, 'context', Context())
97
            context.update(extra_context)
98
            try:
99
                if self.template_code:
100
                    content += Template(self.template_code).render(context)
101
                else:
102
                    content += render_to_string(self.template_name, context)
103
            finally:
104
                context.pop()
105

    
106
        return mark_safe(content)
107

    
108
    def get_confirm(self, record, table):
109
        if callable(self.confirm):
110
            return self.confirm(record, table)
111
        return self.confirm
112

    
113
    def resolved_url(self, record, table):
114
        if callable(self.url):
115
            return self.url(record, table)
116

    
117
        if not self.url:
118
            return '#'
119

    
120
        args = list(self.url_args)
121
        for index, arg in enumerate(args):
122
            if isinstance(arg, A):
123
                args[index] = arg.resolve(record)
124
        return reverse(self.url, args=args)
125

    
126
    def get_action(self, record, table):
127
        if callable(self.action):
128
            return self.action(record, table)
129
        return self.action
130

    
131
    def get_prompt(self, record, table):
132
        if callable(self.prompt):
133
            return self.prompt(record, table)
134
        return self.prompt
135

    
136
    def get_template_context(self, record, table, value, bound_column, **kwargs):
137
        context = {'default': bound_column.default,
138
                   'record': record,
139
                   'value': value,
140
                   'col': self,
141
                   'url': self.resolved_url(record, table),
142
                   'prompt': self.get_prompt(record, table),
143
                   'action': self.get_action(record, table),
144
                   'confirm': self.get_confirm(record, table)
145
                  }
146

    
147
        # decide whether to return dict or a list of dicts in case we want to
148
        # display multiple actions within a cell.
149
        if self.extra_context:
150
            contexts = []
151
            extra_contexts = self.extra_context(record, table, self)
152
            if isinstance(extra_contexts, list):
153
                for extra_context in extra_contexts:
154
                    newcontext = dict(context)
155
                    newcontext.update(extra_context)
156
                    contexts.append(newcontext)
157
            else:
158
                context.update(extra_contexts)
159
                contexts = [context]
160
        else:
161
            contexts = [context]
162

    
163
        return contexts
164

    
165

    
166
def action_extra_context(project, table, self):
167
    user = table.user
168
    url, action, confirm, prompt = '', '', True, ''
169
    append_url = ''
170

    
171
    if user.owns_project(project):
172
        url = 'astakos.im.views.project_update'
173
        action = _('Update')
174
        confirm = False
175
        prompt = ''
176
    elif user.is_project_accepted_member(project):
177
        url = 'astakos.im.views.project_leave'
178
        action = _('- Leave')
179
        confirm = True
180
        prompt = _('Are you sure you want to leave from the project ?')
181
    elif not user.is_project_member(project):
182
        url = 'astakos.im.views.project_join'
183
        action = _('+ Join')
184
        confirm = True
185
        prompt = _('Are you sure you want to join this project ?')
186
    else:
187
        action = ''
188
        confirm = False
189
        url = None
190

    
191
    url = reverse(url, args=(project.pk, )) + append_url if url else ''
192
    return {'action': action,
193
            'confirm': confirm,
194
            'url': url,
195
            'prompt': prompt}
196

    
197

    
198
class UserTable(tables.Table):
199

    
200
    def __init__(self, *args, **kwargs):
201
        self.user = None
202

    
203
        if 'request' in kwargs and kwargs.get('request').user:
204
            self.user = kwargs.get('request').user
205

    
206
        if 'user' in kwargs:
207
            self.user = kwargs.pop('user')
208

    
209
        super(UserTable, self).__init__(*args, **kwargs)
210

    
211

    
212
# Table classes
213
class UserProjectApplicationsTable(UserTable):
214
    caption = _('My projects')
215

    
216
    name = tables.LinkColumn('astakos.im.views.project_detail', args=(A('pk'),))
217
    issue_date = tables.DateColumn(format=DEFAULT_DATE_FORMAT)
218
    start_date = tables.DateColumn(format=DEFAULT_DATE_FORMAT)
219
    end_date = tables.DateColumn(format=DEFAULT_DATE_FORMAT)
220
    members_count = tables.Column(verbose_name=_("Enrolled"), default=0,
221
                                  sortable=False)
222
    membership_status = tables.Column(verbose_name=_("Status"), empty_values=(),
223
                                      sortable=False)
224
    project_action = RichLinkColumn(verbose_name=_('Action'),
225
                                    extra_context=action_extra_context,
226
                                    sortable=False)
227

    
228

    
229
    def render_membership_status(self, record, *args, **kwargs):
230
        status = record.user_status(self.user)
231
        if status == 100:
232
            return record.state
233
        else:
234
            return MEMBER_STATUS_DISPLAY.get(status, 'Unknown')
235

    
236
    class Meta:
237
        model = ProjectApplication
238
        fields = ('name', 'membership_status', 'issue_date', 'start_date','end_date', 'members_count')
239
        attrs = {'id': 'projects-list', 'class': 'my-projects alt-style'}
240
        template = "im/table_render.html"
241
        empty_text = _('No projects')
242

    
243

    
244
def member_action_extra_context(membership, table, col):
245

    
246
    context = []
247
    urls, actions, prompts, confirms = [], [], [], []
248

    
249
    if membership.state == ProjectMembership.REQUESTED:
250
        urls = ['astakos.im.views.project_reject_member',
251
                'astakos.im.views.project_accept_member']
252
        actions = [_('Reject'), _('Approve')]
253
        prompts = [_('Are you sure you want to reject this member ?'),
254
                   _('Are you sure you want to approve this member ?')]
255
        confirms = [True, True]
256

    
257
    if membership.state == ProjectMembership.ACCEPTED:
258
        urls = ['astakos.im.views.project_remove_member']
259
        actions = [_('Remove')]
260
        prompts = [_('Are you sure you want to remove this member ?')]
261
        confirms = [True, True]
262

    
263

    
264
    for i, url in enumerate(urls):
265
        context.append(dict(url=reverse(url, args=(table.project.pk,
266
                                                   membership.person.pk)),
267
                            action=actions[i], prompt=prompts[i],
268
                            confirm=confirms[i]))
269
    return context
270

    
271
class ProjectApplicationMembersTable(UserTable):
272
    name = tables.Column(accessor="person.last_name", verbose_name=_('Name'))
273
    status = tables.Column(accessor="state", verbose_name=_('Status'))
274
    project_action = RichLinkColumn(verbose_name=_('Action'),
275
                                    extra_context=member_action_extra_context,
276
                                    sortable=False)
277

    
278

    
279
    def __init__(self, project, *args, **kwargs):
280
        self.project = project
281
        super(ProjectApplicationMembersTable, self).__init__(*args, **kwargs)
282
        if not self.user.owns_project(self.project):
283
            self.exclude = ('project_action', )
284

    
285

    
286
    def render_name(self, value, record, *args, **kwargs):
287
        return record.person.realname
288

    
289
    def render_status(self, value, *args, **kwargs):
290
        return MEMBER_STATUS_DISPLAY.get(value, 'Unknown')
291

    
292
    class Meta:
293
        template = "im/table_render.html"
294
        model = ProjectMembership
295
        fields = ('name', 'status')
296
        attrs = {'id': 'members-table', 'class': 'members-table alt-style'}
297
        empty_text = _('No members')
298