Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / tables.py @ ff67242a

History | View | Annotate | Download (13.1 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
from django.core.exceptions import PermissionDenied
41

    
42
from django_tables2 import A
43
import django_tables2 as tables
44

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

    
50
DEFAULT_DATE_FORMAT = "d/m/Y"
51

    
52

    
53
class LinkColumn(tables.LinkColumn):
54

    
55
    def __init__(self, *args, **kwargs):
56
        self.coerce = kwargs.pop('coerce', None)
57
        self.append = kwargs.pop('append', None)
58
        super(LinkColumn, self).__init__(*args, **kwargs)
59

    
60
    def render(self, value, record, bound_column):
61
        link = super(LinkColumn, self).render(value, record, bound_column)
62
        extra = ''
63
        if self.append:
64
            if callable(self.append):
65
                extra = self.append(record, bound_column)
66
            else:
67
                extra = self.append
68
        return mark_safe(link + extra)
69

    
70
    def render_link(self, uri, text, attrs=None):
71
        if self.coerce:
72
            text = self.coerce(text)
73
        return super(LinkColumn, self).render_link(uri, text, attrs)
74

    
75

    
76
# Helper columns
77
class RichLinkColumn(tables.TemplateColumn):
78

    
79
    method = 'POST'
80

    
81
    confirm_prompt = _('Yes')
82
    cancel_prompt = _('No')
83
    confirm = True
84

    
85
    prompt = _('Confirm action ?')
86

    
87
    action_tpl = None
88
    action = _('Action')
89
    extra_context = lambda record, table, column: {}
90

    
91
    url = None
92
    url_args = ()
93
    resolve_func = None
94

    
95
    def __init__(self, *args, **kwargs):
96
        kwargs['template_name'] = kwargs.get('template_name',
97
                                             'im/table_rich_link_column.html')
98
        for attr in ['method', 'confirm_prompt',
99
                     'cancel_prompt', 'prompt', 'url',
100
                     'url_args', 'action', 'confirm',
101
                     'resolve_func', 'extra_context']:
102
            setattr(self, attr, kwargs.pop(attr, getattr(self, attr)))
103

    
104
        super(RichLinkColumn, self).__init__(*args, **kwargs)
105

    
106
    def render(self, record, table, value, bound_column, **kwargs):
107
        # If the table is being rendered using `render_table`, it hackily
108
        # attaches the context to the table as a gift to `TemplateColumn`. If
109
        # the table is being rendered via `Table.as_html`, this won't exist.
110
        content = ''
111
        for extra_context in self.get_template_context(record, table, value,
112
                                                       bound_column, **kwargs):
113
            context = getattr(table, 'context', Context())
114
            context.update(extra_context)
115
            try:
116
                if self.template_code:
117
                    content += Template(self.template_code).render(context)
118
                else:
119
                    content += render_to_string(self.template_name, context)
120
            finally:
121
                context.pop()
122

    
123
        return mark_safe(content)
124

    
125
    def get_confirm(self, record, table):
126
        if callable(self.confirm):
127
            return self.confirm(record, table)
128
        return self.confirm
129

    
130
    def resolved_url(self, record, table):
131
        if callable(self.url):
132
            return self.url(record, table)
133

    
134
        if not self.url:
135
            return '#'
136

    
137
        args = list(self.url_args)
138
        for index, arg in enumerate(args):
139
            if isinstance(arg, A):
140
                args[index] = arg.resolve(record)
141
        return reverse(self.url, args=args)
142

    
143
    def get_action(self, record, table):
144
        if callable(self.action):
145
            return self.action(record, table)
146
        return self.action
147

    
148
    def get_prompt(self, record, table):
149
        if callable(self.prompt):
150
            return self.prompt(record, table)
151
        return self.prompt
152

    
153
    def get_template_context(self, record, table, value, bound_column, **kwargs):
154
        context = {'default': bound_column.default,
155
                   'record': record,
156
                   'value': value,
157
                   'col': self,
158
                   'url': self.resolved_url(record, table),
159
                   'prompt': self.get_prompt(record, table),
160
                   'action': self.get_action(record, table),
161
                   'confirm': self.get_confirm(record, table)
162
                  }
163

    
164
        # decide whether to return dict or a list of dicts in case we want to
165
        # display multiple actions within a cell.
166
        if self.extra_context:
167
            contexts = []
168
            extra_contexts = self.extra_context(record, table, self)
169
            if isinstance(extra_contexts, list):
170
                for extra_context in extra_contexts:
171
                    newcontext = dict(context)
172
                    newcontext.update(extra_context)
173
                    contexts.append(newcontext)
174
            else:
175
                context.update(extra_contexts)
176
                contexts = [context]
177
        else:
178
            contexts = [context]
179

    
180
        return contexts
181

    
182

    
183
def action_extra_context(application, table, self):
184
    user = table.user
185
    url, action, confirm, prompt = '', '', True, ''
186
    append_url = ''
187

    
188
    can_join = can_leave = False
189
    project = application.get_project()
190

    
191
    if project:
192
        try:
193
            join_project_checks(project)
194
            can_join = True
195
        except PermissionDenied, e:
196
            pass
197

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

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

    
219
    url = reverse(url, args=(application.pk, )) + append_url if url else ''
220

    
221
    return {'action': action,
222
            'confirm': confirm,
223
            'url': url,
224
            'prompt': prompt}
225

    
226

    
227
class UserTable(tables.Table):
228

    
229
    def __init__(self, *args, **kwargs):
230
        self.user = None
231

    
232
        if 'request' in kwargs and kwargs.get('request').user:
233
            self.user = kwargs.get('request').user
234

    
235
        if 'user' in kwargs:
236
            self.user = kwargs.pop('user')
237

    
238
        super(UserTable, self).__init__(*args, **kwargs)
239

    
240

    
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''
246

    
247
# Table classes
248
class UserProjectApplicationsTable(UserTable):
249
    caption = _('My projects')
250

    
251
    name = LinkColumn('astakos.im.views.project_detail',
252
                      coerce=lambda x: truncatename(x, 25),
253
                      append=project_name_append,
254
                      args=(A('pk'),))
255
    issue_date = tables.DateColumn(verbose_name=_('Application'), format=DEFAULT_DATE_FORMAT)
256
    start_date = tables.DateColumn(format=DEFAULT_DATE_FORMAT)
257
    end_date = tables.DateColumn(verbose_name=_('Expiration'), format=DEFAULT_DATE_FORMAT)
258
    members_count = tables.Column(verbose_name=_("Members"), default=0,
259
                                  orderable=False)
260
    membership_status = tables.Column(verbose_name=_("Status"), empty_values=(),
261
                                      orderable=False)
262
    project_action = RichLinkColumn(verbose_name=_('Action'),
263
                                    extra_context=action_extra_context,
264
                                    orderable=False)
265

    
266

    
267
    def render_membership_status(self, record, *args, **kwargs):
268
        if self.user.owns_project(record):
269
            return record.state_display()
270
        else:
271
            status = record.user_status(self.user)
272
            return record.user_status_display(self.user)
273

    
274
    def render_members_count(self, record, *args, **kwargs):
275
        append = ""
276
        application = record
277
        project = application.get_project()
278
        if project is None:
279
            append = mark_safe("<i class='tiny'>%s</i>" % (_('pending'),))
280

    
281
        c = project.count_pending_memberships()
282
        if c > 0:
283
            append = mark_safe("<i class='tiny'> - %d %s</i>"
284
                                % (c, _('pending')))
285

    
286
        return mark_safe(str(record.members_count()) + append)
287
        
288
    class Meta:
289
        model = ProjectApplication
290
        fields = ('name', 'membership_status', 'issue_date', 'end_date', 'members_count')
291
        attrs = {'id': 'projects-list', 'class': 'my-projects alt-style'}
292
        template = "im/table_render.html"
293
        empty_text = _('No projects')
294
        exclude = ('start_date', )
295

    
296
class ProjectModificationApplicationsTable(UserProjectApplicationsTable):
297
    name = LinkColumn('astakos.im.views.project_detail',
298
                      verbose_name=_('Action'),
299
                      coerce= lambda x: 'review',
300
                      args=(A('pk'),))
301
    class Meta:
302
        attrs = {'id': 'projects-list', 'class': 'my-projects alt-style'}
303
        fields = ('issue_date', 'membership_status')
304
        exclude = ('start_date', 'end_date', 'members_count', 'project_action')
305

    
306
def member_action_extra_context(membership, table, col):
307

    
308
    context = []
309
    urls, actions, prompts, confirms = [], [], [], []
310

    
311
    if membership.state == ProjectMembership.REQUESTED:
312
        urls = ['astakos.im.views.project_reject_member',
313
                'astakos.im.views.project_accept_member']
314
        actions = [_('Reject'), _('Accept')]
315
        prompts = [_('Are you sure you want to reject this member ?'),
316
                   _('Are you sure you want to accept this member ?')]
317
        confirms = [True, True]
318

    
319
    if membership.state == ProjectMembership.ACCEPTED:
320
        urls = ['astakos.im.views.project_remove_member']
321
        actions = [_('Remove')]
322
        if table.user == membership.person:
323
            actions = [_('Leave')]
324
        prompts = [_('Are you sure you want to remove this member ?')]
325
        confirms = [True, True]
326

    
327

    
328
    for i, url in enumerate(urls):
329
        context.append(dict(url=reverse(url, args=(table.project.pk,
330
                                                   membership.person.pk)),
331
                            action=actions[i], prompt=prompts[i],
332
                            confirm=confirms[i]))
333
    return context
334

    
335
class ProjectApplicationMembersTable(UserTable):
336
    name = tables.Column(accessor="person.last_name", verbose_name=_('Name'))
337
    status = tables.Column(accessor="state", verbose_name=_('Status'))
338
    project_action = RichLinkColumn(verbose_name=_('Action'),
339
                                    extra_context=member_action_extra_context,
340
                                    orderable=False)
341

    
342

    
343
    def __init__(self, project, *args, **kwargs):
344
        self.project = project
345
        super(ProjectApplicationMembersTable, self).__init__(*args, **kwargs)
346
        if not self.user.owns_project(self.project):
347
            self.exclude = ('project_action', )
348

    
349

    
350
    def render_name(self, value, record, *args, **kwargs):
351
        return record.person.realname
352

    
353
    def render_status(self, value, *args, **kwargs):
354
        return USER_STATUS_DISPLAY.get(value, 'Unknown')
355

    
356
    class Meta:
357
        template = "im/table_render.html"
358
        model = ProjectMembership
359
        fields = ('name', 'status')
360
        attrs = {'id': 'members-table', 'class': 'members-table alt-style'}
361
        empty_text = _('No members')
362