Projet

Général

Profil

Télécharger (23,6 ko) Statistiques
| Branche: | Tag: | Révision:

root / extra / modules / myspace.ptl @ c6ba8333

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 MyInvoicesDirectory(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
        user = get_request().user
39
        if not user or user.anonymous:
40
            raise errors.AccessUnauthorizedError()
41

    
42
        template.html_top(_('Invoices'))
43
        TextsDirectory.get_html_text('aq-myspace-invoice')
44

    
45
        get_session().display_message()
46

    
47
        invoices = []
48
        invoices.extend(Invoice.get_with_indexed_value(
49
            str('user_id'), str(user.id)))
50
        try:
51
            invoices.extend(Invoice.get_with_indexed_value(
52
                str('user_hash'), str(user.hash)))
53
        except AttributeError:
54
            pass
55

    
56
        def cmp_invoice(a, b):
57
            t = cmp(a.regie_id, b.regie_id)
58
            if t != 0:
59
                return t
60
            return -cmp(a.date, b.date)
61

    
62
        invoices.sort(cmp_invoice)
63

    
64
        last_regie_id = None
65
        unpaid = False
66
        for invoice in invoices:
67
            if invoice.regie_id != last_regie_id:
68
                if last_regie_id:
69
                    '</ul>'
70
                    if unpaid:
71
                        '<input type="submit" value="%s"/>' % _('Pay Selected Invoices')
72
                    '</form>'
73
                last_regie_id = invoice.regie_id
74
                '<h3>%s</h3>' % Regie.get(last_regie_id).label
75
                unpaid = False
76
                '<form action="%s/invoices/multiple">' % get_publisher().get_frontoffice_url()
77
                '<ul>'
78

    
79
            '<li>'
80
            if not (invoice.paid or invoice.canceled):
81
                '<input type="checkbox" name="invoice" value="%s"/>' % invoice.id
82
                unpaid = True
83
            misc.localstrftime(invoice.date)
84
            ' - '
85
            '%s' % invoice.subject
86
            ' - '
87
            '%s' % invoice.amount
88
            ' &euro;'
89
            ' - '
90
            button = '<span class="paybutton">%s</span>' % _('Pay')
91
            if invoice.canceled:
92
                _('canceled on %s') % misc.localstrftime(invoice.canceled_date)
93
                ' - '
94
                button = _('Details')
95
            if invoice.paid:
96
                _('paid on %s') % misc.localstrftime(invoice.paid_date)
97
                ' - '
98
                button = _('Details')
99
            '<a href="%s/invoices/%s">%s</a>' % (get_publisher().get_frontoffice_url(),
100
                    invoice.id, button)
101
            '</li>'
102

    
103
        if last_regie_id:
104
            '</ul>'
105
            if unpaid:
106
                '<input type="submit" value="%s"/>' % _('Pay Selected Invoices')
107
            '</form>'
108

    
109
class StrongboxDirectory(Directory):
110
    _q_exports = ['', 'add', 'download', 'remove', 'pick', 'validate']
111

    
112
    def _q_traverse(self, path):
113
        if not get_cfg('misc', {}).get('aq-strongbox'):
114
            raise errors.TraversalError()
115
        get_response().breadcrumb.append(('strongbox/', _('Strongbox')))
116
        return Directory._q_traverse(self, path)
117

    
118
    def get_form(self):
119
        types = [(x.id, x.label) for x in StrongboxType.select()]
120
        form = Form(action='add', enctype='multipart/form-data')
121
        form.add(StringWidget, 'description', title=_('Description'), size=60)
122
        form.add(FileWidget, 'file', title=_('File'), required=True)
123
        form.add(SingleSelectWidget, 'type_id', title=_('Document Type'),
124
                 options = [(None, _('Not specified'))] + types)
125
        form.add(DateWidget, 'date_time', title = _('Document Date'))
126
        form.add_submit('submit', _('Upload'))
127
        return form
128

    
129
    def _q_index [html] (self):
130
        template.html_top(_('Strongbox'))
131

    
132
        # TODO: a paragraph of explanations here could be useful
133

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

    
179
        if has_items_to_validate:
180
            '<tr><td colspan="4"><h3>%s</h3></td></tr>' % _('Proposed Items')
181
            for sffile in sffiles:
182
                if sffile.validated_time:
183
                    continue
184
                if sffile.expiration_time and sffile.expiration_time < time.localtime():
185
                    expired = True
186
                if i%2:
187
                    classnames = ['odd']
188
                else:
189
                    classnames = ['even']
190
                if expired:
191
                    classnames.append('expired')
192
                '<tr class="%s">' % ' '.join(classnames)
193

    
194
                '<td class="label">'
195
                sffile.get_display_name()
196
                '</td>'
197
                if sffile.type_id:
198
                    '<td class="type">%s</td>' % StrongboxType.get(sffile.type_id).label
199
                else:
200
                    '<td class="type">-</td>'
201

    
202
                if sffile.expiration_time:
203
                    '<td class="expiration">%s' % strftime(misc.date_format(), sffile.expiration_time)
204
                    if expired:
205
                        ' (%s)' % _('expired')
206
                    '</td>'
207
                else:
208
                    '<td class="expiration">-</td>'
209
                '<td class="actions">'
210
                ' [<a href="download?id=%s">%s</a>] ' % (sffile.id, _('download'))
211
                ' [<a href="validate?id=%s">%s</a>] ' % (sffile.id, _('validate'))
212
                ' [<a href="remove?id=%s">%s</a>] ' % (sffile.id, _('reject'))
213
                '</td>'
214
                '</tr>'
215
        if sffiles:
216
            '</table>'
217

    
218
        '<h3>%s</h3>' % _('Add a file to the strongbox')
219
        form = self.get_form()
220
        form.render()
221

    
222
    def add(self):
223
        form = self.get_form()
224
        if not form.is_submitted():
225
            if get_request().form.get('mode') == 'pick':
226
                return redirect('pick')
227
            else:
228
                return redirect('.')
229

    
230
        sffile = StrongboxItem()
231
        sffile.user_id = get_request().user.id
232
        sffile.description = form.get_widget('description').parse()
233
        sffile.validated_time = time.localtime()
234
        sffile.type_id = form.get_widget('type_id').parse()
235
        v = form.get_widget('date_time').parse()
236
        sffile.set_expiration_time_from_date(v)
237
        sffile.store()
238
        sffile.set_file(form.get_widget('file').parse())
239
        sffile.store()
240
        if get_request().form.get('mode') == 'pick':
241
            return redirect('pick')
242
        else:
243
            return redirect('.')
244

    
245
    def download(self):
246
        id = get_request().form.get('id')
247
        if not id:
248
            raise errors.TraversalError()
249
        try:
250
            sffile = StrongboxItem.get(id)
251
        except KeyError:
252
            raise errors.TraversalError()
253
        if str(sffile.user_id) != str(get_request().user.id):
254
            raise errors.TraversalError()
255

    
256
        filename = sffile.file.filename
257
        fd = file(filename)
258
        size = os.path.getsize(filename)
259
        response = get_response()
260
        response.set_content_type('application/octet-stream')
261
        response.set_header('content-disposition', 'attachment; filename="%s"' % sffile.file.base_filename)
262
        return FileStream(fd, size)
263

    
264
    def validate(self):
265
        id = get_request().form.get('id')
266
        if not id:
267
            raise errors.TraversalError()
268
        try:
269
            sffile = StrongboxItem.get(id)
270
        except KeyError:
271
            raise errors.TraversalError()
272
        if str(sffile.user_id) != str(get_request().user.id):
273
            raise errors.TraversalError()
274
        sffile.validated_time = time.time()
275
        sffile.store()
276
        return redirect('.')
277

    
278
    def remove [html] (self):
279
        id = get_request().form.get('id')
280
        if not id:
281
            raise errors.TraversalError()
282
        try:
283
            sffile = StrongboxItem.get(id)
284
        except KeyError:
285
            raise errors.TraversalError()
286
        if str(sffile.user_id) != str(get_request().user.id):
287
            raise errors.TraversalError()
288

    
289
        form = Form(enctype='multipart/form-data')
290
        form.add_hidden('id', get_request().form.get('id'))
291
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
292
                        'You are about to irrevocably delete this item from your strongbox.')))
293
        form.add_submit('submit', _('Submit'))
294
        form.add_submit('cancel', _('Cancel'))
295
        if form.get_submit() == 'cancel':
296
            return redirect('.')
297
        if not form.is_submitted() or form.has_errors():
298
            if sffile.type_id:
299
                '<h2>%s</h2>' % _('Deleting %(filetype)s: %(filename)s') % {
300
                        'filetype': StrongboxType.get(sffile.type_id).label,
301
                        'filename': sffile.get_display_name()
302
                    }
303
            else:
304
                '<h2>%s</h2>' % _('Deleting %(filename)s') % {'filename': sffile.get_display_name()}
305
            form.render()
306
        else:
307
            sffile.remove_self()
308
            sffile.remove_file()
309
            return redirect('.')
310

    
311
    def pick [html] (self):
312
        field_id = str(get_request().form.get('id'))
313
        sffiles = StrongboxItem.get_with_indexed_value(
314
                        str('user_id'), str(get_request().user.id))
315
        '''
316
<script type="text/javascript">
317
function pick_file()
318
{
319
        filewidget = $('input[name=%(id)s$file]');
320
        filewidget.attr('disabled', 'disabled');
321
        value = $('input[name=pick]:checked').val();
322
        $('input[name=%(id)s$strongbox]').val(value);
323
        $.modal.close();
324
        return false;
325
}
326
</script>
327
        ''' % {'id': field_id}
328
        '<form id="strongbox-pick">'
329
        '<ul>'
330
        for sffile in sffiles:
331
            '<li><input type="radio" name="pick" value="%s"><label for="%s">%s</label>' % (
332
                            sffile.id, sffile.id, sffile.get_display_name())
333
            ' [<a href="download?id=%s">%s</a>] ' % (sffile.id, _('view'))
334
            '</input>'
335
            '</li>'
336
        '</ul>'
337
        '<p class="popup-buttons">'
338
        '<a class="button" href="#" onclick="return pick_file()">%s</a>' % _('Select')
339
        '<a class="button cancel" href="#">%s</a>' % _('Cancel')
340
        '</p>'
341
        '</form>'
342

    
343
        root_url = get_publisher().get_root_url()
344
        '<p>'
345
        '<a href="%smyspace/strongbox/" target="_blank">%s</a>' % (root_url,
346
                _('Open Strongbox Management'))
347
        '</p>'
348

    
349

    
350
class MyspaceDirectory(Directory):
351
    _q_exports = ['', 'profile', 'new', 'password', 'remove', 'announces',
352
                  'strongbox', 'invoices']
353

    
354
    strongbox = StrongboxDirectory()
355
    invoices = MyInvoicesDirectory()
356

    
357
    def _q_traverse(self, path):
358
        if path != ['new'] and (not get_request().user or get_request().user.anonymous):
359
            raise errors.AccessUnauthorizedError()
360
        get_response().filter['bigdiv'] = 'profile'
361
        get_response().breadcrumb.append(('myspace/', _('My Space')))
362

    
363
        # Migrate custom text settings
364
        texts_cfg = get_cfg('texts', {})
365
        if 'text-aq-top-of-profile' in texts_cfg and (
366
                        not 'text-top-of-profile' in texts_cfg):
367
            texts_cfg['text-top-of-profile'] = texts_cfg['text-aq-top-of-profile']
368
            del texts_cfg['text-aq-top-of-profile']
369
            get_publisher().write_cfg()
370

    
371
        return Directory._q_traverse(self, path)
372

    
373

    
374
    def _q_index [html] (self):
375
        user = get_request().user
376
        if not user:
377
            raise errors.AccessUnauthorizedError()
378
        template.html_top(_('My Space'))
379
        if user.anonymous:
380
            return redirect('new')
381

    
382
        user_formdef = user.get_formdef()
383

    
384
        user_forms = []
385
        if user:
386
            formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name')
387
            user_forms = []
388
            for formdef in formdefs:
389
                user_forms.extend(formdef.data_class().get_with_indexed_value(
390
                            'user_id', user.id))
391
                try:
392
                    user_forms.extend(formdef.data_class().get_with_indexed_value(
393
                            'user_hash', user.hash))
394
                except AttributeError:
395
                    pass
396
            user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))
397

    
398
        profile_links = []
399
        if user_formdef:
400
            profile_links.append('<a href="#my-profile">%s</a>' % _('My Profile'))
401
        if user_forms:
402
            profile_links.append('<a href="#my-forms">%s</a>' % _('My Forms'))
403
        if get_cfg('misc', {}).get('aq-strongbox'):
404
            profile_links.append('<a href="strongbox/">%s</a>' % _('My Strongbox'))
405
        if is_payment_supported():
406
            profile_links.append('<a href="invoices/">%s</a>' % _('My Invoices'))
407

    
408
        root_url = get_publisher().get_root_url()
409
        if user.is_admin or user.roles:
410
            profile_links.append('<a href="%sbackoffice/">%s</a>' % (root_url, _('Back office')))
411
        if user.is_admin:
412
            profile_links.append('<a href="%sadmin/">%s</a>' % (root_url, _('Admin')))
413

    
414
        if profile_links:
415
            '<p id="profile-links">'
416
            ' - '.join(profile_links)
417
            '</p>'
418

    
419
        if user_formdef:
420
            self._my_profile(user_formdef, user)
421

    
422
        self._index_buttons(user_formdef)
423

    
424
        try:
425
            x = PasswordAccount.get_on_index(get_request().user.id, str('user_id'))
426
        except KeyError:
427
            pass
428
        else:
429
            '<p>'
430
            _('You can delete your account freely from the services portal. '
431
              'This action is irreversible; it will destruct your personal '
432
              'datas and destruct the access to your request history.')
433
            ' <strong><a href="remove" rel="popup">%s</a></strong>.' % _('Delete My Account')
434
            '</p>'
435

    
436
        options = get_cfg('misc', {}).get('announce_themes')
437
        if options:
438
            try:
439
                subscription = AnnounceSubscription.get_on_index(
440
                        get_request().user.id, str('user_id'))
441
            except KeyError:
442
                pass
443
            else:
444
                '<p class="command"><a href="announces">%s</a></p>' % _(
445
                        'Edit my Subscription to Announces')
446

    
447
        if user_forms:
448
            '<h3 id="my-forms">%s</h3>' % _('My Forms')
449
            root.FormsRootDirectory().user_forms(user_forms)
450

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

    
454
        TextsDirectory.get_html_text('top-of-profile')
455

    
456
        if user.form_data:
457
            '<ul>'
458
            for field in user_formdef.fields:
459
                if not hasattr(field, str('get_view_value')):
460
                    continue
461
                value = user.form_data.get(field.id)
462
                '<li>'
463
                field.label
464
                ' : ' 
465
                if value:
466
                    field.get_view_value(value)
467
                '</li>'
468
            '</ul>'
469
        else:
470
            '<p>%s</p>' % _('Empty profile')
471

    
472
    def _index_buttons [html] (self, form_data):
473
        passwords_cfg = get_cfg('passwords', {})
474
        ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0]
475
        if get_session().lasso_session_dump:
476
            ident_method = 'idp'
477

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

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

    
484
    def profile [html] (self):
485
        user = get_request().user
486
        if not user or user.anonymous:
487
            raise errors.AccessUnauthorizedError()
488

    
489
        form = Form(enctype = 'multipart/form-data')
490
        formdef = user.get_formdef()
491
        formdef.add_fields_to_form(form, form_data = user.form_data)
492

    
493
        form.add_submit('submit', _('Apply Changes'))
494
        form.add_submit('cancel', _('Cancel'))
495

    
496
        if form.get_submit() == 'cancel':
497
            return redirect('.')
498

    
499
        if form.is_submitted() and not form.has_errors():
500
            self.profile_submit(form, formdef)
501
            return redirect('.')
502

    
503
        template.html_top(_('Edit Profile'))
504
        form.render()
505

    
506
    def profile_submit(self, form, formdef):
507
        user = get_request().user
508
        data = formdef.get_data(form)
509

    
510
        user.set_attributes_from_formdata(data)
511
        user.form_data = data
512

    
513
        user.store()
514

    
515
    def password [html] (self):
516
        ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0]
517
        if ident_method != 'password':
518
            raise errors.TraversalError()
519

    
520
        user = get_request().user
521
        if not user or user.anonymous:
522
            raise errors.AccessUnauthorizedError()
523

    
524
        form = Form(enctype = 'multipart/form-data')
525
        form.add(PasswordWidget, 'new_password', title = _('New Password'),
526
                required=True)
527
        form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'),
528
                required=True) 
529

    
530
        form.add_submit('submit', _('Change Password'))
531
        form.add_submit('cancel', _('Cancel'))
532

    
533
        if form.get_submit() == 'cancel':
534
            return redirect('.')
535

    
536
        if form.is_submitted() and not form.has_errors():
537
            qommon.ident.password.check_password(form, 'new_password')
538
            new_password = form.get_widget('new_password').parse()
539
            new2_password = form.get_widget('new2_password').parse()
540
            if new_password != new2_password:
541
                form.set_error('new2_password', _('Passwords do not match'))
542

    
543
        if form.is_submitted() and not form.has_errors():
544
            self.submit_password(new_password)
545
            return redirect('.')
546

    
547
        template.html_top(_('Change Password'))
548
        form.render()
549

    
550
    def submit_password(self, new_password):
551
        passwords_cfg = get_cfg('passwords', {})
552
        account = PasswordAccount.get(get_session().username)
553
        account.hashing_algo = passwords_cfg.get('hashing_algo')
554
        account.set_password(new_password)
555
        account.store()
556

    
557
    def new [html] (self):
558
        if not get_request().user or not get_request().user.anonymous:
559
            raise errors.AccessUnauthorizedError()
560

    
561
        form = Form(enctype = 'multipart/form-data')
562
        formdef = get_publisher().user_class.get_formdef()
563
        if formdef:
564
            formdef.add_fields_to_form(form)
565
        else:
566
            get_logger().error('missing user formdef (in myspace/new)')
567

    
568
        form.add_submit('submit', _('Register'))
569

    
570
        if form.is_submitted() and not form.has_errors():
571
            user = get_publisher().user_class()
572
            data = formdef.get_data(form)
573
            user.set_attributes_from_formdata(data)
574
            user.name_identifiers = get_request().user.name_identifiers
575
            user.lasso_dump = get_request().user.lasso_dump
576
            user.set_attributes_from_formdata(data)
577
            user.form_data = data
578
            user.store()
579
            get_session().set_user(user.id)
580
            root_url = get_publisher().get_root_url()
581
            return redirect('%smyspace' % root_url)
582

    
583
        template.html_top(_('Welcome'))
584
        form.render()
585

    
586

    
587
    def remove [html] (self):
588
        user = get_request().user
589
        if not user or user.anonymous:
590
            raise errors.AccessUnauthorizedError()
591

    
592
        form = Form(enctype = 'multipart/form-data')
593
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
594
                        'Are you really sure you want to remove your account?')))
595
        form.add_submit('submit', _('Remove my account'))
596
        form.add_submit('cancel', _('Cancel'))
597

    
598
        if form.get_submit() == 'cancel':
599
            return redirect('.')
600

    
601
        if form.is_submitted() and not form.has_errors():
602
            user = get_request().user
603
            account = PasswordAccount.get_on_index(user.id, str('user_id'))
604
            get_session_manager().expire_session() 
605
            account.remove_self()
606
            return redirect(get_publisher().get_root_url())
607

    
608
        template.html_top(_('Removing Account'))
609
        form.render()
610

    
611
    def announces [html] (self):
612
        options = get_cfg('misc', {}).get('announce_themes')
613
        if not options:
614
            raise errors.TraversalError()
615
        subscription = AnnounceSubscription.get_on_index(get_request().user.id, str('user_id'))
616
        if not subscription:
617
            raise errors.TraversalError()
618

    
619
        if subscription.enabled_themes is None:
620
            enabled_themes = options
621
        else:
622
            enabled_themes = subscription.enabled_themes
623

    
624
        form = Form(enctype = 'multipart/form-data')
625
        form.add(CheckboxesWidget, 'themes', title=_('Announce Themes'),
626
                value=enabled_themes, elements=options,
627
                inline=False, required=False)
628

    
629
        form.add_submit('submit', _('Apply Changes'))
630
        form.add_submit('cancel', _('Cancel'))
631

    
632
        if form.get_submit() == 'cancel':
633
            return redirect('.')
634

    
635
        if form.is_submitted() and not form.has_errors():
636
            chosen_themes = form.get_widget('themes').parse()
637
            if chosen_themes == options:
638
                chosen_themes = None
639
            subscription.enabled_themes = chosen_themes
640
            subscription.store()
641
            return redirect('.')
642

    
643
        template.html_top()
644
        get_response().breadcrumb.append(('announces', _('Announce Subscription')))
645
        form.render()
646

    
647

    
648
TextsDirectory.register('aq-myspace-invoice',
649
        N_('Message on top of invoices page'),
650
        category = N_('Invoices'))
651

    
(19-19/26)