Project

General

Profile

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

root / extra / modules / myspace.ptl @ 88b81eff

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
            user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time))
365

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

    
376
        root_url = get_publisher().get_root_url()
377
        if user.is_admin or user.roles:
378
            profile_links.append('<a href="%sbackoffice/">%s</a>' % (root_url, _('Back office')))
379
        if user.is_admin:
380
            profile_links.append('<a href="%sadmin/">%s</a>' % (root_url, _('Admin')))
381

    
382
        if profile_links:
383
            '<p id="profile-links">'
384
            ' - '.join(profile_links)
385
            '</p>'
386

    
387
        if user_formdef:
388
            self._my_profile(user_formdef, user)
389

    
390
        self._index_buttons(user_formdef)
391

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

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

    
415
        if user_forms:
416
            '<h3 id="my-forms">%s</h3>' % _('My Forms')
417
            root.FormsRootDirectory().user_forms(user_forms)
418

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

    
422
        TextsDirectory.get_html_text('top-of-profile')
423

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

    
439
    def _index_buttons [html] (self, form_data):
440
        passwords_cfg = get_cfg('passwords', {})
441
        ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0]
442
        if get_session().lasso_session_dump:
443
            ident_method = 'idp'
444

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

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

    
451
    def profile [html] (self):
452
        user = get_request().user
453
        if not user or user.anonymous:
454
            raise errors.AccessUnauthorizedError()
455

    
456
        form = Form(enctype = 'multipart/form-data')
457
        formdef = user.get_formdef()
458
        formdef.add_fields_to_form(form, form_data = user.form_data)
459

    
460
        form.add_submit('submit', _('Apply Changes'))
461
        form.add_submit('cancel', _('Cancel'))
462

    
463
        if form.get_submit() == 'cancel':
464
            return redirect('.')
465

    
466
        if form.is_submitted() and not form.has_errors():
467
            self.profile_submit(form, formdef)
468
            return redirect('.')
469

    
470
        template.html_top(_('Edit Profile'))
471
        form.render()
472

    
473
    def profile_submit(self, form, formdef):
474
        user = get_request().user
475
        data = formdef.get_data(form)
476

    
477
        user.set_attributes_from_formdata(data)
478
        user.form_data = data
479

    
480
        user.store()
481

    
482
    def password [html] (self):
483
        ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0]
484
        if ident_method != 'password':
485
            raise errors.TraversalError()
486

    
487
        user = get_request().user
488
        if not user or user.anonymous:
489
            raise errors.AccessUnauthorizedError()
490

    
491
        form = Form(enctype = 'multipart/form-data')
492
        form.add(PasswordWidget, 'new_password', title = _('New Password'),
493
                required=True)
494
        form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'),
495
                required=True) 
496

    
497
        form.add_submit('submit', _('Change Password'))
498
        form.add_submit('cancel', _('Cancel'))
499

    
500
        if form.get_submit() == 'cancel':
501
            return redirect('.')
502

    
503
        if form.is_submitted() and not form.has_errors():
504
            qommon.ident.password.check_password(form, 'new_password')
505
            new_password = form.get_widget('new_password').parse()
506
            new2_password = form.get_widget('new2_password').parse()
507
            if new_password != new2_password:
508
                form.set_error('new2_password', _('Passwords do not match'))
509

    
510
        if form.is_submitted() and not form.has_errors():
511
            self.submit_password(new_password)
512
            return redirect('.')
513

    
514
        template.html_top(_('Change Password'))
515
        form.render()
516

    
517
    def submit_password(self, new_password):
518
        passwords_cfg = get_cfg('passwords', {})
519
        account = PasswordAccount.get(get_session().username)
520
        account.hashing_algo = passwords_cfg.get('hashing_algo')
521
        account.set_password(new_password)
522
        account.store()
523

    
524
    def new [html] (self):
525
        if not get_request().user or not get_request().user.anonymous:
526
            raise errors.AccessUnauthorizedError()
527

    
528
        form = Form(enctype = 'multipart/form-data')
529
        formdef = get_publisher().user_class.get_formdef()
530
        if formdef:
531
            formdef.add_fields_to_form(form)
532
        else:
533
            get_logger().error('missing user formdef (in myspace/new)')
534

    
535
        form.add_submit('submit', _('Register'))
536

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

    
550
        template.html_top(_('Welcome'))
551
        form.render()
552

    
553

    
554
    def remove [html] (self):
555
        user = get_request().user
556
        if not user or user.anonymous:
557
            raise errors.AccessUnauthorizedError()
558

    
559
        form = Form(enctype = 'multipart/form-data')
560
        form.widgets.append(HtmlWidget('<p>%s</p>' % _(
561
                        'Are you really sure you want to remove your account?')))
562
        form.add_submit('submit', _('Remove my account'))
563
        form.add_submit('cancel', _('Cancel'))
564

    
565
        if form.get_submit() == 'cancel':
566
            return redirect('.')
567

    
568
        if form.is_submitted() and not form.has_errors():
569
            user = get_request().user
570
            account = PasswordAccount.get_on_index(user.id, str('user_id'))
571
            get_session_manager().expire_session() 
572
            account.remove_self()
573
            return redirect(get_publisher().get_root_url())
574

    
575
        template.html_top(_('Removing Account'))
576
        form.render()
577

    
578
    def announces [html] (self):
579
        options = get_cfg('misc', {}).get('announce_themes')
580
        if not options:
581
            raise errors.TraversalError()
582
        subscription = AnnounceSubscription.get_on_index(get_request().user.id, str('user_id'))
583
        if not subscription:
584
            raise errors.TraversalError()
585

    
586
        if subscription.enabled_themes is None:
587
            enabled_themes = options
588
        else:
589
            enabled_themes = subscription.enabled_themes
590

    
591
        form = Form(enctype = 'multipart/form-data')
592
        form.add(CheckboxesWidget, 'themes', title=_('Announce Themes'),
593
                value=enabled_themes, elements=options,
594
                inline=False, required=False)
595

    
596
        form.add_submit('submit', _('Apply Changes'))
597
        form.add_submit('cancel', _('Cancel'))
598

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

    
602
        if form.is_submitted() and not form.has_errors():
603
            chosen_themes = form.get_widget('themes').parse()
604
            if chosen_themes == options:
605
                chosen_themes = None
606
            subscription.enabled_themes = chosen_themes
607
            subscription.store()
608
            return redirect('.')
609

    
610
        template.html_top()
611
        get_response().breadcrumb.append(('announces', _('Announce Subscription')))
612
        form.render()
613

    
(19-19/26)