Project

General

Profile

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

root / extra / modules / myspace.ptl @ 2750dbae

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
                value = user.form_data.get(field.id)
428
                '<li>'
429
                field.label
430
                ' : '
431
                value
432
                '</li>'
433
            '</ul>'
434
        else:
435
            '<p>%s</p>' % _('Empty profile')
436

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

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

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

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

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

    
458
        form.add_submit('submit', _('Apply Changes'))
459
        form.add_submit('cancel', _('Cancel'))
460

    
461
        if form.get_submit() == 'cancel':
462
            return redirect('.')
463

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

    
468
        template.html_top(_('Edit Profile'))
469
        form.render()
470

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

    
475
        user.set_attributes_from_formdata(data)
476
        user.form_data = data
477

    
478
        user.store()
479

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

    
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
        form.add(PasswordWidget, 'new_password', title = _('New Password'),
491
                required=True)
492
        form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'),
493
                required=True) 
494

    
495
        form.add_submit('submit', _('Change Password'))
496
        form.add_submit('cancel', _('Cancel'))
497

    
498
        if form.get_submit() == 'cancel':
499
            return redirect('.')
500

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

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

    
512
        template.html_top(_('Change Password'))
513
        form.render()
514

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

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

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

    
533
        form.add_submit('submit', _('Register'))
534

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

    
548
        template.html_top(_('Welcome'))
549
        form.render()
550

    
551

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

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

    
563
        if form.get_submit() == 'cancel':
564
            return redirect('.')
565

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

    
573
        template.html_top(_('Removing Account'))
574
        form.render()
575

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

    
584
        if subscription.enabled_themes is None:
585
            enabled_themes = options
586
        else:
587
            enabled_themes = subscription.enabled_themes
588

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

    
594
        form.add_submit('submit', _('Apply Changes'))
595
        form.add_submit('cancel', _('Cancel'))
596

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

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

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

    
(19-19/26)