Projet

Général

Profil

Télécharger (22,3 ko) Statistiques
| Branche: | Tag: | Révision:

root / extra / modules / myspace.ptl @ 1eca5abc

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
                if value:
439
                    field.get_view_value(value)
440
                '</li>'
441
            '</ul>'
442
        else:
443
            '<p>%s</p>' % _('Empty profile')
444

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

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

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

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

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

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

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

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

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

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

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

    
486
        user.store()
487

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
559

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

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

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

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

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

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

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

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

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

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

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

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

    
(19-19/26)