Project

General

Profile

Download (22.3 KB) Statistics
| Branch: | Tag: | Revision:

root / extra / modules / myspace.ptl @ d3d607aa

1
try:
2
    import lasso
3
except ImportError:
4
    pass
5

    
6
from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session
7
from quixote.directory import AccessControlled, Directory
8
from quixote.util import StaticFile, FileStream
9

    
10
from qommon import template
11
from qommon.form import *
12
from qommon import get_cfg, get_logger
13
from qommon import errors
14

    
15
import qommon.ident.password
16
from qommon.ident.password_accounts import PasswordAccount
17

    
18
from qommon.admin.texts import TextsDirectory
19

    
20
from wcs.formdef import FormDef
21
import root
22

    
23
from announces import AnnounceSubscription
24
from strongbox import StrongboxItem, StrongboxType
25
from payments import Invoice, Regie, is_payment_supported
26

    
27

    
28
class InvoicesDirectory(Directory):
29
    _q_exports = ['']
30

    
31
    def _q_traverse(self, path):
32
        if not is_payment_supported():
33
            raise errors.TraversalError()
34
        get_response().breadcrumb.append(('invoices/', _('Invoices')))
35
        return Directory._q_traverse(self, path)
36

    
37
    def _q_index [html] (self):
38
        template.html_top(_('Invoices'))
39
        get_session().display_message()
40

    
41
        # TODO: a paragraph of explanations here could be useful
42

    
43
        invoices = Invoice.get_with_indexed_value(
44
                        str('user_id'), str(get_request().user.id))
45

    
46
        def cmp_invoice(a, b):
47
            t = cmp(a.regie_id, b.regie_id)
48
            if t != 0:
49
                return t
50
            return -cmp(a.date, b.date)
51

    
52
        invoices.sort(cmp_invoice)
53

    
54
        last_regie_id = None
55
        unpaid = False
56
        for invoice in invoices:
57
            if invoice.regie_id != last_regie_id:
58
                if last_regie_id:
59
                    '</ul>'
60
                    if unpaid:
61
                        '<input type="submit" value="%s"/>' % _('Pay Selected Invoices')
62
                    '</form>'
63
                last_regie_id = invoice.regie_id
64
                '<h3>%s</h3>' % Regie.get(last_regie_id).label
65
                unpaid = False
66
                '<form action="%spayment/init">' % get_publisher().get_root_url()
67
                '<ul>'
68

    
69
            '<li>'
70
            if not invoice.paid:
71
                '<input type="checkbox" name="invoice" value="%s"/>' % invoice.id
72
                unpaid = True
73
            misc.localstrftime(invoice.date)
74
            ' - '
75
            formdef = FormDef.get(invoice.formdef_id)
76
            try:
77
                formdata = formdef.data_class().get(invoice.formdata_id)
78
            except KeyError:
79
                # XXX: the form got removed, what to do with the invoice?
80
                # just ignore it?
81
                _('Unknown invoice')
82
            else:
83
                '<a href="%s">%s #%s</a>' % (formdata.get_url(), formdef.name, formdata.id)
84
            ' - '
85
            invoice.amount
86
            ' &euro;'
87
            ' - '
88
            if invoice.paid:
89
                _('paid on %s') % misc.localstrftime(invoice.paid_date)
90
            else:
91
                '<a href="%spayment/redirect?invoice=%s">%s</a>' % (
92
                            formdata.get_url(), invoice.id, _('Pay'))
93
            '</li>'
94

    
95
        if last_regie_id:
96
            '</ul>'
97
            if unpaid:
98
                '<input type="submit" value="%s"/>' % _('Pay Selected Invoices')
99
            '</form>'
100

    
101
class StrongboxDirectory(Directory):
102
    _q_exports = ['', 'add', 'download', 'remove', 'pick', 'validate']
103

    
104
    def _q_traverse(self, path):
105
        if not get_cfg('misc', {}).get('aq-strongbox'):
106
            raise errors.TraversalError()
107
        get_response().breadcrumb.append(('strongbox/', _('Strongbox')))
108
        return Directory._q_traverse(self, path)
109

    
110
    def get_form(self):
111
        types = [(x.id, x.label) for x in StrongboxType.select()]
112
        form = Form(action='add', enctype='multipart/form-data')
113
        form.add(StringWidget, 'description', title=_('Description'), size=60)
114
        form.add(FileWidget, 'file', title=_('File'), required=True)
115
        form.add(SingleSelectWidget, 'type_id', title=_('Document Type'),
116
                 options = [(None, _('Not specified'))] + types)
117
        form.add(DateWidget, 'date_time', title = _('Document Date'))
118
        form.add_submit('submit', _('Upload'))
119
        return form
120

    
121
    def _q_index [html] (self):
122
        template.html_top(_('Strongbox'))
123

    
124
        # TODO: a paragraph of explanations here could be useful
125

    
126
        sffiles = StrongboxItem.get_with_indexed_value(
127
                        str('user_id'), str(get_request().user.id))
128
        if sffiles:
129
            '<table id="strongbox-items">'
130
            '<tr><th></th><th>%s</th><th>%s</th><th></th></tr>' % (
131
                _('Type'), _('Expiration'))
132
        else:
133
            '<p>'
134
            _('There is currently nothing in your strongbox.')
135
            '</p>'
136
        has_items_to_validate = False
137
        for i, sffile in enumerate(sffiles):
138
            expired = False
139
            if not sffile.validated_time:
140
                has_items_to_validate = True
141
                continue
142
            if sffile.expiration_time and sffile.expiration_time < time.localtime():
143
                expired = True
144
            if i%2:
145
                classnames = ['odd']
146
            else:
147
                classnames = ['even']
148
            if expired:
149
                classnames.append('expired')
150
            '<tr class="%s">' % ' '.join(classnames)
151
            '<td class="label">'
152
            sffile.get_display_name()
153
            '</td>'
154
            if sffile.type_id:
155
                '<td class="type">%s</td>' % StrongboxType.get(sffile.type_id).label
156
            else:
157
                '<td class="type">-</td>'
158
            if sffile.expiration_time:
159
                '<td class="expiration">%s' % strftime(misc.date_format(), sffile.expiration_time)
160
                if expired:
161
                    ' (%s)' % _('expired')
162
                '</td>'
163
            else:
164
                '<td class="expiration">-</td>'
165
            '<td class="actions">'
166
            ' [<a href="download?id=%s">%s</a>] ' % (sffile.id, _('download'))
167
            '[<a href="remove?id=%s">%s</a>] ' % (sffile.id, _('remove'))
168
            '</td>'
169
            '</tr>'
170

    
171
        if has_items_to_validate:
172
            '<tr><td colspan="4"><h3>%s</h3></td></tr>' % _('Proposed Items')
173
            for sffile in sffiles:
174
                if sffile.validated_time:
175
                    continue
176
                if sffile.expiration_time and sffile.expiration_time < time.localtime():
177
                    expired = True
178
                if i%2:
179
                    classnames = ['odd']
180
                else:
181
                    classnames = ['even']
182
                if expired:
183
                    classnames.append('expired')
184
                '<tr class="%s">' % ' '.join(classnames)
185

    
186
                '<td class="label">'
187
                sffile.get_display_name()
188
                '</td>'
189
                if sffile.type_id:
190
                    '<td class="type">%s</td>' % StrongboxType.get(sffile.type_id).label
191
                else:
192
                    '<td class="type">-</td>'
193

    
194
                if sffile.expiration_time:
195
                    '<td class="expiration">%s' % strftime(misc.date_format(), sffile.expiration_time)
196
                    if expired:
197
                        ' (%s)' % _('expired')
198
                    '</td>'
199
                else:
200
                    '<td class="expiration">-</td>'
201
                '<td class="actions">'
202
                ' [<a href="download?id=%s">%s</a>] ' % (sffile.id, _('download'))
203
                ' [<a href="validate?id=%s">%s</a>] ' % (sffile.id, _('validate'))
204
                ' [<a href="remove?id=%s">%s</a>] ' % (sffile.id, _('reject'))
205
                '</td>'
206
                '</tr>'
207
        if sffiles:
208
            '</table>'
209

    
210
        '<h3>%s</h3>' % _('Add a file to the strongbox')
211
        form = self.get_form()
212
        form.render()
213

    
214
    def add(self):
215
        form = self.get_form()
216
        if not form.is_submitted():
217
            if get_request().form.get('mode') == 'pick':
218
                return redirect('pick')
219
            else:
220
                return redirect('.')
221

    
222
        sffile = StrongboxItem()
223
        sffile.user_id = get_request().user.id
224
        sffile.description = form.get_widget('description').parse()
225
        sffile.validated_time = time.localtime()
226
        sffile.type_id = form.get_widget('type_id').parse()
227
        v = form.get_widget('date_time').parse()
228
        sffile.set_expiration_time_from_date(v)
229
        sffile.store()
230
        sffile.set_file(form.get_widget('file').parse())
231
        sffile.store()
232
        if get_request().form.get('mode') == 'pick':
233
            return redirect('pick')
234
        else:
235
            return redirect('.')
236

    
237
    def download(self):
238
        id = get_request().form.get('id')
239
        if not id:
240
            raise errors.TraversalError()
241
        try:
242
            sffile = StrongboxItem.get(id)
243
        except KeyError:
244
            raise errors.TraversalError()
245
        if str(sffile.user_id) != str(get_request().user.id):
246
            raise errors.TraversalError()
247

    
248
        filename = sffile.file.filename
249
        fd = file(filename)
250
        size = os.path.getsize(filename)
251
        response = get_response()
252
        response.set_content_type('application/octet-stream')
253
        response.set_header('content-disposition', 'attachment; filename="%s"' % sffile.file.base_filename)
254
        return FileStream(fd, size)
255

    
256
    def validate(self):
257
        id = get_request().form.get('id')
258
        if not id:
259
            raise errors.TraversalError()
260
        try:
261
            sffile = StrongboxItem.get(id)
262
        except KeyError:
263
            raise errors.TraversalError()
264
        if str(sffile.user_id) != str(get_request().user.id):
265
            raise errors.TraversalError()
266
        sffile.validated_time = time.time()
267
        sffile.store()
268
        return redirect('.')
269

    
270
    def remove(self):
271
        id = get_request().form.get('id')
272
        if not id:
273
            raise errors.TraversalError()
274
        try:
275
            sffile = StrongboxItem.get(id)
276
        except KeyError:
277
            raise errors.TraversalError()
278
        if str(sffile.user_id) != str(get_request().user.id):
279
            raise errors.TraversalError()
280
        sffile.remove_self()
281
        sffile.remove_file()
282
        return redirect('.')
283

    
284
    def pick [html] (self):
285
        field_id = str(get_request().form.get('id'))
286
        sffiles = StrongboxItem.get_with_indexed_value(
287
                        str('user_id'), str(get_request().user.id))
288
        '''
289
<script type="text/javascript">
290
function pick_file()
291
{
292
        filewidget = $('input[name=%(id)s$file]');
293
        filewidget.attr('disabled', 'disabled');
294
        value = $('input[name=pick]:checked').val();
295
        $('input[name=%(id)s$strongbox]').val(value);
296
        $.modal.close();
297
        return false;
298
}
299
</script>
300
        ''' % {'id': field_id}
301
        '<form id="strongbox-pick">'
302
        '<ul>'
303
        for sffile in sffiles:
304
            '<li><input type="radio" name="pick" value="%s"><label for="%s">%s</label>' % (
305
                            sffile.id, sffile.id, sffile.get_display_name())
306
            ' [<a href="download?id=%s">%s</a>] ' % (sffile.id, _('view'))
307
            '</input>'
308
            '</li>'
309
        '</ul>'
310
        '<p class="popup-buttons">'
311
        '<a class="button" href="#" onclick="return pick_file()">%s</a>' % _('Select')
312
        '<a class="button cancel" href="#">%s</a>' % _('Cancel')
313
        '</p>'
314
        '</form>'
315

    
316
        root_url = get_publisher().get_root_url()
317
        '<p>'
318
        '<a href="%smyspace/strongbox/" target="_blank">%s</a>' % (root_url,
319
                _('Open Strongbox Management'))
320
        '</p>'
321

    
322

    
323
class MyspaceDirectory(Directory):
324
    _q_exports = ['', 'profile', 'new', 'password', 'remove', 'announces',
325
                  'strongbox', 'invoices']
326

    
327
    strongbox = StrongboxDirectory()
328
    invoices = InvoicesDirectory()
329

    
330
    def _q_traverse(self, path):
331
        if path != ['new'] and (not get_request().user or get_request().user.anonymous):
332
            raise errors.AccessUnauthorizedError()
333
        get_response().filter['bigdiv'] = 'profile'
334
        get_response().breadcrumb.append(('myspace/', _('My Space')))
335

    
336
        # Migrate custom text settings
337
        texts_cfg = get_cfg('texts', {})
338
        if 'text-aq-top-of-profile' in texts_cfg and (
339
                        not 'text-top-of-profile' in texts_cfg):
340
            texts_cfg['text-top-of-profile'] = texts_cfg['text-aq-top-of-profile']
341
            del texts_cfg['text-aq-top-of-profile']
342
            get_publisher().write_cfg()
343

    
344
        return Directory._q_traverse(self, path)
345

    
346

    
347
    def _q_index [html] (self):
348
        user = get_request().user
349
        if not user:
350
            raise errors.AccessUnauthorizedError()
351
        template.html_top(_('My Space'))
352
        if user.anonymous:
353
            return redirect('new')
354

    
355
        user_formdef = user.get_formdef()
356

    
357
        user_forms = []
358
        if user:
359
            formdefs = FormDef.select(lambda x: not x.disabled, order_by = 'name')
360
            user_forms = []
361
            for formdef in formdefs:
362
                user_forms.extend(formdef.data_class().get_with_indexed_value(
363
                            'user_id', user.id))
364
                try:
365
                    user_forms.extend(formdef.data_class().get_with_indexed_value(
366
                            'user_hash', user.hash))
367
                except AttributeError:
368
                    pass
369
            user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))
370

    
371
        profile_links = []
372
        if user_formdef:
373
            profile_links.append('<a href="#my-profile">%s</a>' % _('My Profile'))
374
        if user_forms:
375
            profile_links.append('<a href="#my-forms">%s</a>' % _('My Forms'))
376
        if get_cfg('misc', {}).get('aq-strongbox'):
377
            profile_links.append('<a href="strongbox/">%s</a>' % _('My Strongbox'))
378
        if is_payment_supported():
379
            profile_links.append('<a href="invoices/">%s</a>' % _('My Invoices'))
380

    
381
        root_url = get_publisher().get_root_url()
382
        if user.is_admin or user.roles:
383
            profile_links.append('<a href="%sbackoffice/">%s</a>' % (root_url, _('Back office')))
384
        if user.is_admin:
385
            profile_links.append('<a href="%sadmin/">%s</a>' % (root_url, _('Admin')))
386

    
387
        if profile_links:
388
            '<p id="profile-links">'
389
            ' - '.join(profile_links)
390
            '</p>'
391

    
392
        if user_formdef:
393
            self._my_profile(user_formdef, user)
394

    
395
        self._index_buttons(user_formdef)
396

    
397
        try:
398
            x = PasswordAccount.get_on_index(get_request().user.id, str('user_id'))
399
        except KeyError:
400
            pass
401
        else:
402
            '<p>'
403
            _('You can delete your account freely from the services portal. '
404
              'This action is irreversible; it will destruct your personal '
405
              'datas and destruct the access to your request history.')
406
            ' <strong><a href="remove" rel="popup">%s</a></strong>.' % _('Delete My Account')
407
            '</p>'
408

    
409
        options = get_cfg('misc', {}).get('announce_themes')
410
        if options:
411
            try:
412
                subscription = AnnounceSubscription.get_on_index(
413
                        get_request().user.id, str('user_id'))
414
            except KeyError:
415
                pass
416
            else:
417
                '<p class="command"><a href="announces">%s</a></p>' % _(
418
                        'Edit my Subscription to Announces')
419

    
420
        if user_forms:
421
            '<h3 id="my-forms">%s</h3>' % _('My Forms')
422
            root.FormsRootDirectory().user_forms(user_forms)
423

    
424
    def _my_profile [html] (self, user_formdef, user):
425
        '<h3 id="my-profile">%s</h3>' % _('My Profile')
426

    
427
        TextsDirectory.get_html_text('top-of-profile')
428

    
429
        if user.form_data:
430
            '<ul>'
431
            for field in user_formdef.fields:
432
                if not hasattr(field, str('get_view_value')):
433
                    continue
434
                value = user.form_data.get(field.id)
435
                '<li>'
436
                field.label
437
                ' : '
438
                field.get_view_value(value)
439
                '</li>'
440
            '</ul>'
441
        else:
442
            '<p>%s</p>' % _('Empty profile')
443

    
444
    def _index_buttons [html] (self, form_data):
445
        passwords_cfg = get_cfg('passwords', {})
446
        ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0]
447
        if get_session().lasso_session_dump:
448
            ident_method = 'idp'
449

    
450
        if form_data and ident_method != 'idp':
451
            '<p class="command"><a href="profile" rel="popup">%s</a></p>' % _('Edit My Profile')
452

    
453
        if ident_method == 'password' and passwords_cfg.get('can_change', False):
454
            '<p class="command"><a href="password" rel="popup">%s</a></p>' % _('Change My Password')
455

    
456
    def profile [html] (self):
457
        user = get_request().user
458
        if not user or user.anonymous:
459
            raise errors.AccessUnauthorizedError()
460

    
461
        form = Form(enctype = 'multipart/form-data')
462
        formdef = user.get_formdef()
463
        formdef.add_fields_to_form(form, form_data = user.form_data)
464

    
465
        form.add_submit('submit', _('Apply Changes'))
466
        form.add_submit('cancel', _('Cancel'))
467

    
468
        if form.get_submit() == 'cancel':
469
            return redirect('.')
470

    
471
        if form.is_submitted() and not form.has_errors():
472
            self.profile_submit(form, formdef)
473
            return redirect('.')
474

    
475
        template.html_top(_('Edit Profile'))
476
        form.render()
477

    
478
    def profile_submit(self, form, formdef):
479
        user = get_request().user
480
        data = formdef.get_data(form)
481

    
482
        user.set_attributes_from_formdata(data)
483
        user.form_data = data
484

    
485
        user.store()
486

    
487
    def password [html] (self):
488
        ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0]
489
        if ident_method != 'password':
490
            raise errors.TraversalError()
491

    
492
        user = get_request().user
493
        if not user or user.anonymous:
494
            raise errors.AccessUnauthorizedError()
495

    
496
        form = Form(enctype = 'multipart/form-data')
497
        form.add(PasswordWidget, 'new_password', title = _('New Password'),
498
                required=True)
499
        form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'),
500
                required=True) 
501

    
502
        form.add_submit('submit', _('Change Password'))
503
        form.add_submit('cancel', _('Cancel'))
504

    
505
        if form.get_submit() == 'cancel':
506
            return redirect('.')
507

    
508
        if form.is_submitted() and not form.has_errors():
509
            qommon.ident.password.check_password(form, 'new_password')
510
            new_password = form.get_widget('new_password').parse()
511
            new2_password = form.get_widget('new2_password').parse()
512
            if new_password != new2_password:
513
                form.set_error('new2_password', _('Passwords do not match'))
514

    
515
        if form.is_submitted() and not form.has_errors():
516
            self.submit_password(new_password)
517
            return redirect('.')
518

    
519
        template.html_top(_('Change Password'))
520
        form.render()
521

    
522
    def submit_password(self, new_password):
523
        passwords_cfg = get_cfg('passwords', {})
524
        account = PasswordAccount.get(get_session().username)
525
        account.hashing_algo = passwords_cfg.get('hashing_algo')
526
        account.set_password(new_password)
527
        account.store()
528

    
529
    def new [html] (self):
530
        if not get_request().user or not get_request().user.anonymous:
531
            raise errors.AccessUnauthorizedError()
532

    
533
        form = Form(enctype = 'multipart/form-data')
534
        formdef = get_publisher().user_class.get_formdef()
535
        if formdef:
536
            formdef.add_fields_to_form(form)
537
        else:
538
            get_logger().error('missing user formdef (in myspace/new)')
539

    
540
        form.add_submit('submit', _('Register'))
541

    
542
        if form.is_submitted() and not form.has_errors():
543
            user = get_publisher().user_class()
544
            data = formdef.get_data(form)
545
            user.set_attributes_from_formdata(data)
546
            user.name_identifiers = get_request().user.name_identifiers
547
            user.lasso_dump = get_request().user.lasso_dump
548
            user.set_attributes_from_formdata(data)
549
            user.form_data = data
550
            user.store()
551
            get_session().set_user(user.id)
552
            root_url = get_publisher().get_root_url()
553
            return redirect('%smyspace' % root_url)
554

    
555
        template.html_top(_('Welcome'))
556
        form.render()
557

    
558

    
559
    def remove [html] (self):
560
        user = get_request().user
561
        if not user or user.anonymous:
562
            raise errors.AccessUnauthorizedError()
563

    
564
        form = Form(enctype = 'multipart/form-data')
565
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
566
                        'Are you really sure you want to remove your account?')))
567
        form.add_submit('submit', _('Remove my account'))
568
        form.add_submit('cancel', _('Cancel'))
569

    
570
        if form.get_submit() == 'cancel':
571
            return redirect('.')
572

    
573
        if form.is_submitted() and not form.has_errors():
574
            user = get_request().user
575
            account = PasswordAccount.get_on_index(user.id, str('user_id'))
576
            get_session_manager().expire_session() 
577
            account.remove_self()
578
            return redirect(get_publisher().get_root_url())
579

    
580
        template.html_top(_('Removing Account'))
581
        form.render()
582

    
583
    def announces [html] (self):
584
        options = get_cfg('misc', {}).get('announce_themes')
585
        if not options:
586
            raise errors.TraversalError()
587
        subscription = AnnounceSubscription.get_on_index(get_request().user.id, str('user_id'))
588
        if not subscription:
589
            raise errors.TraversalError()
590

    
591
        if subscription.enabled_themes is None:
592
            enabled_themes = options
593
        else:
594
            enabled_themes = subscription.enabled_themes
595

    
596
        form = Form(enctype = 'multipart/form-data')
597
        form.add(CheckboxesWidget, 'themes', title=_('Announce Themes'),
598
                value=enabled_themes, elements=options,
599
                inline=False, required=False)
600

    
601
        form.add_submit('submit', _('Apply Changes'))
602
        form.add_submit('cancel', _('Cancel'))
603

    
604
        if form.get_submit() == 'cancel':
605
            return redirect('.')
606

    
607
        if form.is_submitted() and not form.has_errors():
608
            chosen_themes = form.get_widget('themes').parse()
609
            if chosen_themes == options:
610
                chosen_themes = None
611
            subscription.enabled_themes = chosen_themes
612
            subscription.store()
613
            return redirect('.')
614

    
615
        template.html_top()
616
        get_response().breadcrumb.append(('announces', _('Announce Subscription')))
617
        form.render()
618

    
(19-19/26)