Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / tables.py @ 9b32e2fb

History | View | Annotate | Download (12.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
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 do_join_project_checks
48

    
49
DEFAULT_DATE_FORMAT = "d/m/Y"
50

    
51

    
52
class LinkColumn(tables.LinkColumn):
53

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

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

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

    
74

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

    
78
    method = 'POST'
79

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

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

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

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

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

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

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

    
122
        return mark_safe(content)
123

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

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

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

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

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

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

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

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

    
179
        return contexts
180

    
181

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

    
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
210

    
211
    url = reverse(url, args=(application.pk, )) + append_url if url else ''
212
    return {'action': action,
213
            'confirm': confirm,
214
            'url': url,
215
            'prompt': prompt}
216

    
217

    
218
class UserTable(tables.Table):
219

    
220
    def __init__(self, *args, **kwargs):
221
        self.user = None
222

    
223
        if 'request' in kwargs and kwargs.get('request').user:
224
            self.user = kwargs.get('request').user
225

    
226
        if 'user' in kwargs:
227
            self.user = kwargs.pop('user')
228

    
229
        super(UserTable, self).__init__(*args, **kwargs)
230

    
231

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

    
244
# Table classes
245
class UserProjectApplicationsTable(UserTable):
246
    caption = _('My projects')
247

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

    
263

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

    
271
    class Meta:
272
        model = ProjectApplication
273
        fields = ('name', 'membership_status', 'issue_date', 'end_date', 'members_count')
274
        attrs = {'id': 'projects-list', 'class': 'my-projects alt-style'}
275
        template = "im/table_render.html"
276
        empty_text = _('No projects')
277

    
278
class ProjectModificationApplicationsTable(UserProjectApplicationsTable):
279
    name = LinkColumn('astakos.im.views.project_detail',
280
                      verbose_name=_('Action'),
281
                      coerce= lambda x: 'review',
282
                      args=(A('pk'),))
283
    class Meta:
284
        attrs = {'id': 'projects-list', 'class': 'my-projects alt-style'}
285
        fields = ('issue_date', 'membership_status')
286
        exclude = ('start_date', 'end_date', 'members_count', 'project_action')
287

    
288
def member_action_extra_context(membership, table, col):
289

    
290
    context = []
291
    urls, actions, prompts, confirms = [], [], [], []
292

    
293
    if membership.state == ProjectMembership.REQUESTED:
294
        urls = ['astakos.im.views.project_reject_member',
295
                'astakos.im.views.project_accept_member']
296
        actions = [_('Reject'), _('Approve')]
297
        prompts = [_('Are you sure you want to reject this member ?'),
298
                   _('Are you sure you want to approve this member ?')]
299
        confirms = [True, True]
300

    
301
    if membership.state == ProjectMembership.ACCEPTED:
302
        urls = ['astakos.im.views.project_remove_member']
303
        actions = [_('Remove')]
304
        if table.user == membership.person:
305
            actions = [_('Leave')]
306
        prompts = [_('Are you sure you want to remove this member ?')]
307
        confirms = [True, True]
308

    
309

    
310
    for i, url in enumerate(urls):
311
        context.append(dict(url=reverse(url, args=(table.project.pk,
312
                                                   membership.person.pk)),
313
                            action=actions[i], prompt=prompts[i],
314
                            confirm=confirms[i]))
315
    return context
316

    
317
class ProjectApplicationMembersTable(UserTable):
318
    name = tables.Column(accessor="person.last_name", verbose_name=_('Name'))
319
    status = tables.Column(accessor="state", verbose_name=_('Status'))
320
    project_action = RichLinkColumn(verbose_name=_('Action'),
321
                                    extra_context=member_action_extra_context,
322
                                    sortable=False)
323

    
324

    
325
    def __init__(self, project, *args, **kwargs):
326
        self.project = project
327
        super(ProjectApplicationMembersTable, self).__init__(*args, **kwargs)
328
        if not self.user.owns_project(self.project):
329
            self.exclude = ('project_action', )
330

    
331

    
332
    def render_name(self, value, record, *args, **kwargs):
333
        return record.person.realname
334

    
335
    def render_status(self, value, *args, **kwargs):
336
        return USER_STATUS_DISPLAY.get(value, 'Unknown')
337

    
338
    class Meta:
339
        template = "im/table_render.html"
340
        model = ProjectMembership
341
        fields = ('name', 'status')
342
        attrs = {'id': 'members-table', 'class': 'members-table alt-style'}
343
        empty_text = _('No members')
344