Revision 792c2f3b

b/snf-astakos-app/astakos/im/static/im/css/modules.css
458 458
.stats .green .img-wrap							{ background-position: -263px 7px; }
459 459
.projects .editable form textarea				{ width:70%; height:50px; max-width:70%; width:270px; height:120px;}
460 460

  
461
/* temp style to hide extra menu for groups */
462
.navigation ul+ul								{ display:none; }
463 461

  
464 462
table .msg-wrap									{ position:relative; }
465 463
table .msg-wrap .dialog							{ position:absolute; border:1px dashed #ccc;  padding:15px; width:200px; bottom:30px; left:0; background:#fff; display:none; }
......
487 485
.content .how-it-works a.submit:focus			{ border-color: #B3B3B3}
488 486
.content .how-it-works a.submit:focus:hover		{ border-color: #55B577}
489 487

  
490
.auth_methods									{ margin:3em 0; }
491
.auth_methods .wrap								{   }
492
.auth_methods h2								{ font-size:1.154em; }
493
.auth_methods table								{ width:100%; color:#222; margin:1em 0; }
494
.auth_methods table th							{ font-weight:normal; color:#3582AC }
488
.auth_methods ul								{ margin:1em 0; padding:0; list-style:none outside none; }
489
.auth_methods ul li 							{ margin:0 0 1em 0; padding:0; list-style:none outside none; font-size:1.154em; }
490
.auth_methods ul li>a							{ padding-right:20px; background:url(../images/arrow-down_green.png) no-repeat center right; color:#55B577 }
491
.auth_methods ul li>a.up						{ background-image:url(../images/arrow-up_green.png); }
492
.auth_methods ul li .wrap						{ font-size:0.867em; margin-top:1em; display:none; }
493
.auth_methods ul li .actions a					{ margin-right:20px; }
494
.auth_methods ul li a.red						{ color:#F24E53; }
495
.auth_methods ul.notassigned					{ margin-top:3em; }
496
.auth_methods ul.notassigned li>a				{ background:transparent; color: #F89A1C}
497
.auth_methods .dialog-wrap						{ display:inline; position:relative; }
498
.auth_methods .dialog							{ background:#fff; border:1px dashed #ccc; position:absolute; bottom:30px; left:0; padding:15px; width:220px; display:none;}
499
.auth_methods .dialog .submit					{ min-width:30px; padding:5px 22px; }
500

  
501
.content input:-webkit-autofill 							{ background: transparent; }
b/snf-astakos-app/astakos/im/static/im/js/common.js
288 288
         $('.hidden-submit .form-row.submit').show(500);
289 289
    });
290 290
    
291
     $('.auth_methods').find('li>a').click(function(e){
292
     	e.preventDefault();
293
     	$(this).siblings('.wrap').toggle('slow');
294
     	$(this).toggleClass('up');
295
     });
296
     
297
     $('.auth_methods a.red').click(function(e){
298
     	e.preventDefault();
299
     	$(this).siblings('.dialog').show();
300
     })
301
     
302
      
303
     $('.auth_methods .dialog .no').click( function(e){	 
304
     	e.preventDefault();
305
     	console.log($(this));
306
     	$(this).parents('.dialog').hide();
307
     })
308
    
309
    setTimeout(function() {
310
      if ($('input#id_username').val()){ 
311
      	$('input#id_username').siblings('label').css('opacity','0');
312
      };
313
      if ($('input#id_password').val()){ 
314
      	$('input#id_password').siblings('label').css('opacity','0');
315
      }
316
	}, 100);
317
    
318
    
291 319
});
292 320
	
293 321
$(window).resize(function() {
b/snf-astakos-app/astakos/im/templates/im/invitations.html
1 1
{% extends "im/account_base.html" %}
2 2

  
3 3
{% block page.body %}
4
<div class="full bottom-bordered clearfix">
4
<div class="full  clearfix">
5 5
	<div class="lt-div">
6 6
		<p>Invite someone else</p>
7 7
	</div>
......
22 22
	 
23 23
</div>
24 24
 
25
	<div class="full {% block innerpage.class %}{% endblock %}">
25
	<div class="full full-dotted">
26 26
	    
27 27
	    <h2>You have <em>{{ inviter.invitations }}</em> invitation{{ inviter.invitations|pluralize }} left.</h2>
28 28
	    {% if sent|length %}
b/snf-astakos-app/astakos/im/templates/im/profile.html
13 13
        <input type="hidden" name="auth" value="{{ user.auth_token }}">
14 14
        <input type="submit" class="submit altcol" value="UPDATE" />
15 15
    </div>
16
</form>
16 17

  
17
    <div class="auth_methods">
18
      <br /><br />
19
        <div class="assigned">
20
          <h4>Authentication methods</h4>
21
          <p>You can login to your account using the following methods</p>
22
          <ul class="auth_providers">
23
            {% for provider in user_providers %}
24
            <li>
25
            <h2>
26
                {{ provider.settings.title }}
27
                <span class="actions" style="margin-left: 40px">
28
                  {% for name, url in provider.settings.extra_actions %}
29
                  <a href="{{ url }}" title="{{ name }}">{{ name }}</a>
30
                  {% endfor %}
31
                  {% if provider.can_remove %}
32
                      <a href="{% url remove_auth_provider provider.pk %}" title="disble">Remove</a>
33
                  {% endif %}
34
                </span>
35
            </h2>
36
            <p>{{ provider.details_display }}</p>
37
            <br />
38
            </li>
39
            {% empty %}
40
            <li>No available authentication methods</li>
41
            {% endfor %}
42
          </ul>
43
        </div>
44
        <div class="notassigned">
45
          <p>You can add the following authentication methods to your account </p>
46
          <ul class="auth_providers">
47
            {% for provider in user_available_providers %}
48
            <li>
49
            <h2><a href="{{ provider.add_url }}">{{ provider.title }}</a></h2>
50
            <p>{{ provider.add_description }}</p>
51
            <br />
52
            </li>
53
            {% empty %}
54
            No available providers.
55
            {% endfor %}
56
          </ul>
57
        </div>
18
<div class="full-dotted">
19
	<div class="auth_methods">
20
        <h2>ENABLED AUTHENTICATION METHODS</h2>
21
        <ul>
22
        	{% for provider in user_providers %}
23
        	<li>
24
        		<a href="#">{{ provider.settings.title }}</a>
25
        		<div class="wrap">
26
        			<p>{{ provider.details_display }}</p>
27
        			<div class="actions">
28
  
29
		                  {% for name, url in provider.settings.extra_actions %}
30
		                  <a href="{{ url }}" title="{{ name }}">{{ name }}</a>
31
		                  {% endfor %}
32
		                  {% if provider.can_remove %}
33
		                      <div class="dialog-wrap">
34
		                      	<a href="#" title="disable" class="red">
35
		                      	Disable x</a>
36
		                      	<div class="dialog">Are you sure you want to disable this method?
37
		                      		<a href="{% url remove_auth_provider provider.pk %}" class="yes submit">Yes</a>&nbsp;&nbsp;&nbsp;<a href="#" class="no submit">No</a>
38
		                      	</div>	
39
		                      </div>
40
		                      	
41
		                  {% endif %}
42
        			</div>
43
        		</div>
44
        	</li>
45
        	{% empty %}
46
        	<li>No available authentication methods</li>
47
        	{% endfor %}
48
        	
49
        </ul>
50
        <ul class="notassigned">
51
        	<li>
52
        		<a href="#">+ Add new authentication method</a>
53
        		
54
        		<div class="wrap">
55
        			{% for provider in user_available_providers %}
56
        				<p><a href="{{ provider.add_url }}">{{ provider.title }}</a> ( {{ provider.add_description }})</p>
57
        			{% empty %}
58
	        		No available providers 
59
	        		{% endfor %}	
60
        		</div>
61
        		
62
        	</li>
63
        </ul>
64
		
58 65
    </div>
66
</div>
59 67

  
60
</form>
61

  
62
<div class="two-cols-links">
63
	<p><a href="{% url password_change %}">Change Password</a></p>
64
	<p>
65
		<a href="https://okeanos.grnet.gr/home/">Back to ~okeanos</a>
66
		<a href="https://cyclades.okeanos.grnet.gr/ui/">Take me to cyclades</a>
67
		<a href="https://pithos.okeanos.grnet.gr/ui/">Take me to pithos+</a>
68
	</p>
68
<div class="full-dotted">
69
	<ul class="options">
70
		<li><a href="https://okeanos.grnet.gr/home/" class="blue">Back to ~okeanos ></a></li>
71
		<li><a href="https://cyclades.okeanos.grnet.gr/ui/" class="blue">Take me to cyclades ></a></li>
72
		<li><a href="https://pithos.okeanos.grnet.gr/ui/" class="blue">Take me to pithos+ ></a></li>
73
	</ul>
74
		 
69 75
</div>
70 76
{% endblock body %}
b/snf-astakos-app/astakos/im/templates/im/profile_bak.html
1
{% extends "im/account_base.html" %}
2

  
3
{% block body %}
4

  
5
<form action={%url edit_profile %} method="post" class="withlabels hidden-submit">{% csrf_token %}
6
    
7
    {% with profile_form as form %}
8
    {% include "im/form_render.html" %}
9
    {% endwith %}
10

  
11
    <div class="form-row submit">
12
        <input type="hidden" name="next" value="{{ next }}">
13
        <input type="hidden" name="auth" value="{{ user.auth_token }}">
14
        <input type="submit" class="submit altcol" value="UPDATE" />
15
    </div>
16

  
17

  
18
    <div class="auth_methods">
19
      <br /><br />
20
        <div class="assigned">
21
          <h2><a href="#">Available authentication methods</a></h2>
22
          <p>You can login to your account using the following methods</p>
23
          <ul class="auth_providers">
24
            {% for provider in user_providers %}
25
            <li>
26
            <h2>
27
                {{ provider.settings.title }}
28
                <span class="actions" style="margin-left: 40px">
29
                  {% for name, url in provider.settings.extra_actions %}
30
                  <a href="{{ url }}" title="{{ name }}">{{ name }}</a>
31
                  {% endfor %}
32
                  {% if provider.can_remove %}
33
                      <a href="{% url remove_auth_provider provider.pk %}" title="disble">Remove</a>
34
                  {% endif %}
35
                </span>
36
            </h2>
37
            <p>{{ provider.details_display }}</p>
38
            <br />
39
            </li>
40
            {% empty %}
41
            <li>No available authentication methods</li>
42
            {% endfor %}
43
          </ul>
44
        </div>
45
        <div class="notassigned">
46
          <p>You can add the following authentication methods to your account </p>
47
          <ul class="auth_providers">
48
            {% for provider in user_available_providers %}
49
            <li>
50
            <h2><a href="{{ provider.add_url }}">{{ provider.title }}</a></h2>
51
            <p>{{ provider.add_description }}</p>
52
            <br />
53
            </li>
54
            {% empty %}
55
            No available providers.
56
            {% endfor %}
57
          </ul>
58
        </div>
59
    </div>
60

  
61
</form>
62

  
63
<div class="two-cols-links">
64
	<p><a href="{% url password_change %}">Change Password</a></p>
65
	<p>
66
		<a href="https://okeanos.grnet.gr/home/">Back to ~okeanos</a>
67
		<a href="https://cyclades.okeanos.grnet.gr/ui/">Take me to cyclades</a>
68
		<a href="https://pithos.okeanos.grnet.gr/ui/">Take me to pithos+</a>
69
	</p>
70
</div>
71
{% endblock body %}
b/snf-astakos-app/astakos/im/views.py
57 57
                                                get_model_and_form_class)
58 58
from django.views.generic.list_detail import object_list
59 59
from django.core.xheaders import populate_xheaders
60
from django.core.exceptions import ValidationError, PermissionDenied
60 61

  
61 62
from django.template.loader import render_to_string
62 63
from django.views.decorators.http import require_http_methods
......
64 65

  
65 66
from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
66 67
                               EmailChange, GroupKind, Membership,
67
                               RESOURCE_SEPARATOR)
68
                               RESOURCE_SEPARATOR, AstakosUserAuthProvider)
68 69
from astakos.im.util import get_context, prepare_response, get_query, restrict_next
69 70
from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
70 71
                              FeedbackForm, SignApprovalTermsForm,
......
87 88
from astakos.im.api.callpoint import AstakosCallpoint
88 89

  
89 90
import astakos.im.messages as astakos_messages
91
from astakos.im import settings
92
from astakos.im import auth_providers
90 93

  
91 94
logger = logging.getLogger(__name__)
92 95

  
......
109 112
    response = HttpResponse(html, status=status)
110 113
    return response
111 114

  
115
def requires_auth_provider(provider_id, **perms):
116
    """
117
    """
118
    def decorator(func, *args, **kwargs):
119
        @wraps(func)
120
        def wrapper(request, *args, **kwargs):
121
            provider = auth_providers.get_provider(provider_id)
122

  
123
            if not provider or not provider.is_active():
124
                raise PermissionDenied
125

  
126
            if provider:
127
                for pkey, value in perms.iteritems():
128
                    attr = 'is_available_for_%s' % pkey.lower()
129
                    if getattr(provider, attr)() != value:
130
                        raise PermissionDenied
131
            return func(request, *args)
132
        return wrapper
133
    return decorator
134

  
112 135

  
113 136
def requires_anonymous(func):
114 137
    """
......
169 192
    template_name = login_template_name
170 193
    if request.user.is_authenticated():
171 194
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
172
    
195

  
173 196
    return render_response(
174 197
        template_name,
175 198
        login_form = LoginForm(request=request),
......
319 342
            except ValueError, ve:
320 343
                messages.success(request, ve)
321 344
    elif request.method == "GET":
322
        if not request.user.is_verified:
323
            request.user.is_verified = True
324
            request.user.save()
345
        request.user.is_verified = True
346
        request.user.save()
347

  
348
    # existing providers
349
    user_providers = request.user.get_active_auth_providers()
350

  
351
    # providers that user can add
352
    user_available_providers = request.user.get_available_auth_providers()
353

  
325 354
    return render_response(template_name,
326 355
                           profile_form = form,
356
                           user_providers = user_providers,
357
                           user_available_providers = user_available_providers,
327 358
                           context_instance = get_context(request,
328 359
                                                          extra_context))
329 360

  
......
370 401
        return HttpResponseRedirect(reverse('edit_profile'))
371 402

  
372 403
    provider = get_query(request).get('provider', 'local')
404
    if not auth_providers.get_provider(provider).is_available_for_create():
405
        raise PermissionDenied
406

  
373 407
    id = get_query(request).get('id')
374 408
    try:
375 409
        instance = AstakosUser.objects.get(id=id) if id else None
......
390 424
                result = backend.handle_activation(user)
391 425
                status = messages.SUCCESS
392 426
                message = result.message
393
                user.save()
427

  
428
                form.store_user(user, request)
429

  
394 430
                if 'additional_email' in form.cleaned_data:
395 431
                    additional_email = form.cleaned_data['additional_email']
396 432
                    if additional_email != user.email:
......
406 442
                    transaction.commit()
407 443
                    return response
408 444
                messages.add_message(request, status, message)
445
                transaction.commit()
409 446
                return render_response(
410 447
                    on_success,
411 448
                    context_instance=get_context(
......
655 692

  
656 693

  
657 694
def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
695

  
696
    if settings.MODERATION_ENABLED:
697
        raise PermissionDenied
698

  
658 699
    extra_context = extra_context or {}
659 700
    try:
660 701
        u = AstakosUser.objects.get(id=user_id)
......
677 718
    )
678 719

  
679 720
class ResourcePresentation():
680
    
721

  
681 722
    def __init__(self, data):
682 723
        self.data = data
683
        
724

  
684 725
    def update_from_result(self, result):
685 726
        if result.is_success:
686 727
            for r in result.data:
687 728
                rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
688 729
                if not rname in self.data['resources']:
689 730
                    self.data['resources'][rname] = {}
690
                    
731

  
691 732
                self.data['resources'][rname].update(r)
692 733
                self.data['resources'][rname]['id'] = rname
693 734
                group = r.get('group')
694 735
                if not group in self.data['groups']:
695 736
                    self.data['groups'][group] = {}
696
                    
737

  
697 738
                self.data['groups'][r.get('group')].update({'name': r.get('group')})
698
    
739

  
699 740
    def test(self, quota_dict):
700 741
        for k, v in quota_dict.iteritems():
701 742
            rname = k
702 743
            value = v
703 744
            if not rname in self.data['resources']:
704 745
                self.data['resources'][rname] = {}
705
                    
706
 
746

  
747

  
707 748
            self.data['resources'][rname]['value'] = value
708
            
709
    
749

  
750

  
710 751
    def update_from_result_report(self, result):
711 752
        if result.is_success:
712 753
            for r in result.data:
713 754
                rname = r.get('name')
714 755
                if not rname in self.data['resources']:
715 756
                    self.data['resources'][rname] = {}
716
                    
757

  
717 758
                self.data['resources'][rname].update(r)
718 759
                self.data['resources'][rname]['id'] = rname
719 760
                group = r.get('group')
720 761
                if not group in self.data['groups']:
721 762
                    self.data['groups'][group] = {}
722
                    
763

  
723 764
                self.data['groups'][r.get('group')].update({'name': r.get('group')})
724
                
765

  
725 766
    def get_group_resources(self, group):
726 767
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
727
    
768

  
728 769
    def get_groups_resources(self):
729 770
        for g in self.data['groups']:
730 771
            yield g, self.get_group_resources(g)
731
    
772

  
732 773
    def get_quota(self, group_quotas):
733 774
        for r, v in group_quotas.iteritems():
734 775
            rname = str(r)
735 776
            quota = self.data['resources'].get(rname)
736 777
            quota['value'] = v
737 778
            yield quota
738
    
739
    
779

  
780

  
740 781
    def get_policies(self, policies_data):
741 782
        for policy in policies_data:
742 783
            rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
743 784
            policy.update(self.data['resources'].get(rname))
744 785
            yield policy
745
        
786

  
746 787
    def __repr__(self):
747 788
        return self.data.__repr__()
748
                
789

  
749 790
    def __iter__(self, *args, **kwargs):
750 791
        return self.data.__iter__(*args, **kwargs)
751
    
792

  
752 793
    def __getitem__(self, *args, **kwargs):
753 794
        return self.data.__getitem__(*args, **kwargs)
754
    
795

  
755 796
    def get(self, *args, **kwargs):
756 797
        return self.data.get(*args, **kwargs)
757
        
758
        
798

  
799

  
759 800

  
760 801
@require_http_methods(["GET", "POST"])
761 802
@signed_terms_required
762 803
@login_required
763 804
def group_add(request, kind_name='default'):
764
    
805

  
765 806
    result = callpoint.list_resources()
766 807
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
767 808
    resource_catalog.update_from_result(result)
768
    
809

  
769 810
    if not result.is_success:
770 811
        messages.error(
771 812
            request,
772 813
            'Unable to retrieve system resources: %s' % result.reason
773 814
    )
774
    
815

  
775 816
    try:
776 817
        kind = GroupKind.objects.get(name=kind_name)
777 818
    except:
778 819
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
779
    
780
    
820

  
821

  
781 822

  
782 823
    post_save_redirect = '/im/group/%(id)s/'
783 824
    context_processors = None
......
785 826
        model=None,
786 827
        form_class=AstakosGroupCreationForm
787 828
    )
788
    
829

  
789 830
    if request.method == 'POST':
790 831
        form = form_class(request.POST, request.FILES)
791 832
        if form.is_valid():
......
796 837
                policies = resource_catalog.get_policies(form.policies()),
797 838
                resource_catalog= resource_catalog,
798 839
            )
799
         
840

  
800 841
    else:
801 842
        now = datetime.now()
802 843
        data = {
......
806 847
            data['is_selected_%s' % group] = False
807 848
            for resource in resources:
808 849
                data['%s_uplimit' % resource] = ''
809
        
850

  
810 851
        form = form_class(data)
811 852

  
812 853
    # Create the template, context, response
......
839 880
            msg = _(astakos_messages.OBJECT_CREATED) %\
840 881
                {"verbose_name": model._meta.verbose_name}
841 882
            messages.success(request, msg, fail_silently=True)
842
            
883

  
843 884
            # send notification
844 885
            try:
845 886
                send_group_creation_notification(
......
857 898
        else:
858 899
            d = {"verbose_name": model._meta.verbose_name,
859 900
                 "reason":result.reason}
860
            msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d 
901
            msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
861 902
            messages.error(request, msg, fail_silently=True)
862 903
    return render_response(
863 904
        template='im/astakosgroup_form_summary.html',
......
895 936
            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
896 937
        LEFT JOIN auth_user as owner ON (
897 938
            im_astakosuser_owner.astakosuser_id = owner.id)
898
        WHERE im_membership.person_id = %s 
939
        WHERE im_membership.person_id = %s
899 940
        """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id)
900
       
941

  
901 942
    if sorting:
902
        query = query+" ORDER BY %s ASC" %sorting    
943
        query = query+" ORDER BY %s ASC" %sorting
903 944
    else:
904
        query = query+" ORDER BY groupname ASC"     
945
        query = query+" ORDER BY groupname ASC"
905 946
    q = AstakosGroup.objects.raw(query)
906 947

  
907
       
908
       
948

  
949

  
909 950
    # Create the template, context, response
910 951
    template_name = "%s/%s_list.html" % (
911 952
        q.model._meta.app_label,
......
987 1028
        form = MembersSortForm({'sort_by': sorting})
988 1029
        if form.is_valid():
989 1030
            sorting = form.cleaned_data.get('sort_by')
990
    
1031

  
991 1032
    else:
992 1033
        form = MembersSortForm({'sort_by': 'person_first_name'})
993
    
1034

  
994 1035
    result = callpoint.list_resources()
995 1036
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
996 1037
    resource_catalog.update_from_result(result)
997 1038

  
998
    
1039

  
999 1040
    if not result.is_success:
1000 1041
        messages.error(
1001 1042
            request,
1002 1043
            'Unable to retrieve system resources: %s' % result.reason
1003 1044
    )
1004
    
1045

  
1005 1046
    extra_context = {'update_form': update_form,
1006 1047
                     'addmembers_form': addmembers_form,
1007 1048
                     'page': request.GET.get('page', 1),
......
1062 1103
                        SELECT id FROM im_astakosuser_owner
1063 1104
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1064 1105
                        AND astakosuser_id = %s)
1065
                        THEN 1 ELSE 0 END""" % request.user.id, 
1106
                        THEN 1 ELSE 0 END""" % request.user.id,
1066 1107
                    })
1067 1108
        if sorting:
1068 1109
            # TODO check sorting value
......
1118 1159
        q = q.order_by(sorting)
1119 1160
    else:
1120 1161
        q = q.order_by("groupname")
1121
        
1162

  
1122 1163
    return object_list(
1123 1164
        request,
1124 1165
        q,
......
1238 1279
        if max_value > 0 :
1239 1280
            entry['ratio'] = (curr_value / max_value) * 100
1240 1281
        else:
1241
            entry['ratio'] = 0 
1282
            entry['ratio'] = 0
1242 1283
        if entry['ratio'] < 66:
1243 1284
            entry['load_class'] = 'yellow'
1244 1285
        if entry['ratio'] < 33:
......
1258 1299
        messages.error(request, result.reason)
1259 1300
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1260 1301
    resource_catalog.update_from_result_report(result)
1261
    
1262 1302

  
1263
    
1303

  
1304

  
1264 1305
    return render_response('im/resource_list.html',
1265 1306
                           data=data,
1266 1307
                           context_instance=get_context(request),
......
1333 1374
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1334 1375

  
1335 1376
    return data
1336
     
1337
     
1377

  
1378

  
1338 1379
#@require_http_methods(["GET"])
1339 1380
@require_http_methods(["POST", "GET"])
1340 1381
@signed_terms_required
......
1365 1406
                           timeline_body=timeline_body)
1366 1407
    return data
1367 1408

  
1409

  
1368 1410
# TODO: action only on POST and user should confirm the removal
1369 1411
@require_http_methods(["GET", "POST"])
1370 1412
@login_required
......
1381 1423
    else:
1382 1424
        raise PermissionDenied
1383 1425

  
1426

  
1384 1427
def how_it_works(request):
1385 1428
    return render_response(
1386 1429
        template='im/how_it_works.html',
1387
        context_instance=get_context(request),)
1430
        context_instance=get_context(request),)
1431
    

Also available in: Unified diff