Commit 270dd48d authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

Force user to accept service terms

Refs: #2019
parent 067961d3
......@@ -158,6 +158,8 @@ uniq User email (uniq identifier used by Astakos)
auth_token Authentication token
auth_token_expires Token expiration date
auth_token_created Token creation date
has_credits Whether user has credits
has_signed_terms Whether user has aggred on terms
=========================== ============================
Example reply:
......@@ -168,7 +170,9 @@ Example reply:
"uniq": "papagian@example.com"
"auth_token": "0000",
"auth_token_expires": "Tue, 11-Sep-2012 09:17:14 ",
"auth_token_created": "Sun, 11-Sep-2011 09:17:14 "}
"auth_token_created": "Sun, 11-Sep-2011 09:17:14 ",
"has_credits": false,
"has_signed_terms": true}
|
......@@ -177,7 +181,7 @@ Return Code Description
=========================== =====================
204 (No Content) The request succeeded
400 (Bad Request) The request is invalid
401 (Unauthorized) Missing token or inactive user
401 (Unauthorized) Missing token or inactive user or penging approval terms
500 (Internal Server Error) The request cannot be completed because of an internal error
=========================== =====================
......
......@@ -92,4 +92,5 @@ listusers List users
modifyuser Modify a user's attributes
showinvitation Show invitation info
showuser Show user info
addterms Add new approval terms
=============== ===========================
......@@ -46,6 +46,7 @@ from django.core.urlresolvers import reverse
from astakos.im.faults import BadRequest, Unauthorized, InternalServerError
from astakos.im.models import AstakosUser
from astakos.im.settings import CLOUD_SERVICES, INVITATIONS_ENABLED
from astakos.im.util import has_signed_terms
logger = logging.getLogger(__name__)
......@@ -85,7 +86,10 @@ def authenticate(request):
# Check if the token has expired.
if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
return render_fault(request, Unauthorized('Authentication expired'))
if not has_signed_terms(user):
return render_fault(request, Unauthorized('Pending approval terms'))
response = HttpResponse()
response.status=204
user_info = {'username':user.username,
......@@ -93,7 +97,8 @@ def authenticate(request):
'auth_token':user.auth_token,
'auth_token_created':user.auth_token_created.isoformat(),
'auth_token_expires':user.auth_token_expires.isoformat(),
'has_credits':user.has_credits}
'has_credits':user.has_credits,
'has_signed_terms':has_signed_terms(user)}
response.content = json.dumps(user_info)
response['Content-Type'] = 'application/json; charset=UTF-8'
response['Content-Length'] = len(response.content)
......
......@@ -43,7 +43,8 @@ def im_modules(request):
return {'im_modules': IM_MODULES}
def next(request):
return {'next' : request.GET.get('next', '')}
query_dict = request.__getattribute__(request.method)
return {'next' : query_dict.get('next', '')}
def code(request):
return {'code' : request.GET.get('code', '')}
......
......@@ -31,6 +31,7 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from urlparse import urljoin
from datetime import datetime
from django import forms
from django.utils.translation import ugettext as _
......@@ -40,10 +41,14 @@ from django.contrib.auth.tokens import default_token_generator
from django.template import Context, loader
from django.utils.http import int_to_base36
from django.core.urlresolvers import reverse
from django.utils.functional import lazy
from astakos.im.models import AstakosUser
from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL
from astakos.im.widgets import DummyWidget, RecaptchaWidget
from astakos.im.widgets import DummyWidget, RecaptchaWidget, ApprovalTermsWidget
# since Django 1.4 use django.core.urlresolvers.reverse_lazy instead
from astakos.im.util import reverse_lazy
import logging
import recaptcha.client.captcha as captcha
......@@ -63,7 +68,8 @@ class LocalUserCreationForm(UserCreationForm):
class Meta:
model = AstakosUser
fields = ("email", "first_name", "last_name")
fields = ("email", "first_name", "last_name", "has_signed_terms")
widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))}
def __init__(self, *args, **kwargs):
"""
......@@ -75,8 +81,9 @@ class LocalUserCreationForm(UserCreationForm):
super(LocalUserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['email', 'first_name', 'last_name',
'password1', 'password2',
'has_signed_terms',
'recaptcha_challenge_field',
'recaptcha_response_field']
'recaptcha_response_field',]
def clean_email(self):
email = self.cleaned_data['email']
......@@ -88,6 +95,12 @@ class LocalUserCreationForm(UserCreationForm):
except AstakosUser.DoesNotExist:
return email
def clean_has_signed_terms(self):
has_signed_terms = self.cleaned_data['has_signed_terms']
if not has_signed_terms:
raise forms.ValidationError(_('You have to agree with the terms'))
return has_signed_terms
def clean_recaptcha_response_field(self):
if 'recaptcha_challenge_field' in self.cleaned_data:
self.validate_captcha()
......@@ -112,6 +125,7 @@ class LocalUserCreationForm(UserCreationForm):
"""
user = super(LocalUserCreationForm, self).save(commit=False)
user.renew_token()
user.date_signed_terms = datetime.now()
if commit:
user.save()
logger.info('Created user %s', user)
......@@ -126,7 +140,8 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
class Meta:
model = AstakosUser
fields = ("email", "first_name", "last_name")
fields = ("email", "first_name", "last_name", "has_signed_terms")
widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))}
def __init__(self, *args, **kwargs):
"""
......@@ -135,6 +150,7 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
super(InvitedLocalUserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['email', 'inviter', 'first_name',
'last_name', 'password1', 'password2',
'has_signed_terms',
'recaptcha_challenge_field',
'recaptcha_response_field']
#set readonly form fields
......@@ -270,3 +286,28 @@ class ExtendedPasswordResetForm(PasswordResetForm):
from_email = DEFAULT_FROM_EMAIL
send_mail(_("Password reset on %s alpha2 testing") % SITENAME,
t.render(Context(c)), from_email, [user.email])
class SignApprovalTermsForm(forms.ModelForm):
class Meta:
model = AstakosUser
fields = ("has_signed_terms",)
def __init__(self, *args, **kwargs):
super(SignApprovalTermsForm, self).__init__(*args, **kwargs)
def clean_has_signed_terms(self):
has_signed_terms = self.cleaned_data['has_signed_terms']
if not has_signed_terms:
raise forms.ValidationError(_('You have to agree with the terms'))
return has_signed_terms
def save(self, commit=True):
"""
Saves the , after the normal
save behavior is complete.
"""
user = super(SignApprovalTermsForm, self).save(commit=False)
user.date_signed_terms = datetime.now()
if commit:
user.save()
return user
\ No newline at end of file
# Copyright 2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from optparse import make_option
from random import choice
from string import digits, lowercase, uppercase
from uuid import uuid4
from time import time
from django.core.management.base import BaseCommand, CommandError
from astakos.im.models import ApprovalTerms
class Command(BaseCommand):
args = "<location>"
help = "Insert approval terms"
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("Invalid number of arguments")
location = args[0].decode('utf8')
try:
f = open(location, 'r')
except IOError:
raise CommandError("Invalid location")
terms = ApprovalTerms(location=location)
terms.save()
msg = "Created term id %d" % (terms.id,)
self.stdout.write(msg + '\n')
......@@ -73,9 +73,11 @@ class Command(BaseCommand):
'invitation level': user.level,
'provider': user.provider,
'verified': format_bool(user.is_verified),
'has_credits': format_bool(user.has_credits)
'has_credits': format_bool(user.has_credits),
'has_signed_terms': format_bool(user.has_signed_terms),
'date_signed_terms': format_date(user.date_signed_terms)
}
for key, val in sorted(kv.items()):
line = '%s: %s\n' % (key.rjust(16), val)
line = '%s: %s\n' % (key.rjust(17), val)
self.stdout.write(line.encode('utf8'))
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'ApprovalTerms'
db.create_table('im_approvalterms', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2012, 3, 20, 14, 24, 30, 616341), db_index=True)),
('location', self.gf('django.db.models.fields.CharField')(max_length=255)),
))
db.send_create_signal('im', ['ApprovalTerms'])
# Adding field 'AstakosUser.has_signed_terms'
db.add_column('im_astakosuser', 'has_signed_terms', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
# Adding field 'AstakosUser.date_signed_terms'
db.add_column('im_astakosuser', 'date_signed_terms', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False)
def backwards(self, orm):
# Deleting model 'ApprovalTerms'
db.delete_table('im_approvalterms')
# Deleting field 'AstakosUser.has_signed_terms'
db.delete_column('im_astakosuser', 'has_signed_terms')
# Deleting field 'AstakosUser.date_signed_terms'
db.delete_column('im_astakosuser', 'date_signed_terms')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'im.approvalterms': {
'Meta': {'object_name': 'ApprovalTerms'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 3, 20, 14, 24, 30, 616341)', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'im.astakosuser': {
'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {}),
'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'})
},
'im.invitation': {
'Meta': {'object_name': 'Invitation'},
'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
}
}
complete_apps = ['im']
......@@ -76,6 +76,8 @@ class AstakosUser(User):
email_verified = models.BooleanField('Email verified?', default=False)
has_credits = models.BooleanField('Has credits?', default=False)
has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
date_signed_terms = models.DateTimeField('Signed terms date', null=True)
@property
def realname(self):
......@@ -130,6 +132,14 @@ class AstakosUser(User):
def __unicode__(self):
return self.username
class ApprovalTerms(models.Model):
"""
Model for approval terms
"""
date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
location = models.CharField('Terms location', max_length=255)
class Invitation(models.Model):
"""
Model for registring invitations
......@@ -171,6 +181,8 @@ def report_user_event(user):
eventType = 'create' if not user.id else 'modify'
body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
conn = exchange_connect(QUEUE_CONNECTION)
routing_key = '%s.user' % QUEUE_CONNECTION
parts = urlparse(exchange)
exchange = parts.path[1:]
routing_key = '%s.user' % exchange
exchange_send(conn, routing_key, body)
exchange_close(conn)
{% block body %}
{{ terms }}
{% if form %}
<div class="section">
<form action="{% url latest_terms %}" method="post" class="login innerlabels">{% csrf_token %}
{% include "im/form_render.html" %}
<input type="hidden" name="next" value="{{ next }}">
<div class="form-row submit">
<input type="submit" class="submit altcol" value="SUBMIT" />
</div>
</form>
{% endif %}
{% endblock body %}
\ No newline at end of file
......@@ -32,9 +32,11 @@
# or implied, of GRNET S.A.
from django.conf.urls.defaults import patterns, include, url
from django.contrib.auth.views import password_change
from astakos.im.forms import ExtendedPasswordResetForm, LoginForm
from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED
from astakos.im.views import signed_terms_required
urlpatterns = patterns('astakos.im.views',
url(r'^$', 'index', {}, name='index'),
......@@ -43,7 +45,10 @@ urlpatterns = patterns('astakos.im.views',
url(r'^feedback/?$', 'send_feedback'),
url(r'^signup/?$', 'signup', {'on_success':'im/login.html', 'extra_context':{'form':LoginForm()}}),
url(r'^logout/?$', 'logout', {'template':'im/login.html', 'extra_context':{'form':LoginForm()}}),
url(r'^activate/?$', 'activate')
url(r'^activate/?$', 'activate'),
url(r'^approval_terms/?$', 'approval_terms', {}, name='latest_terms'),
url(r'^approval_terms/(?P<term_id>\d+)?$', 'approval_terms'),
url(r'^password/?$', 'change_password', {}, name='password_change')
)
urlpatterns += patterns('astakos.im.target',
......@@ -62,7 +67,7 @@ if 'local' in IM_MODULES:
url(r'^local/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
'password_reset_confirm'),
url(r'^local/password/reset/complete/$', 'password_reset_complete'),
url(r'^password/?$', 'password_change', {'post_change_redirect':'profile'}, name='password_change')
url(r'^password_change/?$', 'password_change', {'post_change_redirect':'profile'})
)
if INVITATIONS_ENABLED:
......
......@@ -46,7 +46,7 @@ from django.utils.translation import ugettext as _
from django.contrib.auth import login, authenticate
from django.core.urlresolvers import reverse
from astakos.im.models import AstakosUser, Invitation
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
from astakos.im.settings import INVITATIONS_PER_LEVEL, COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE, FORCE_PROFILE_UPDATE
logger = logging.getLogger(__name__)
......@@ -128,7 +128,6 @@ def prepare_response(request, user, next='', renew=False):
expired, if the 'renew' parameter is present
or user has not a valid token.
"""
renew = renew or (not user.auth_token)
renew = renew or (user.auth_token_expires and user.auth_token_expires < datetime.datetime.now())
if renew:
......@@ -161,3 +160,28 @@ def set_cookie(response, user):
response.set_cookie(COOKIE_NAME, value=cookie_value,
expires=expire_fmt, path='/',
domain=COOKIE_DOMAIN, secure=COOKIE_SECURE)
class lazy_string(object):
def __init__(self, function, *args, **kwargs):
self.function=function
self.args=args
self.kwargs=kwargs
def __str__(self):
if not hasattr(self, 'str'):
self.str=self.function(*self.args, **self.kwargs)
return self.str
def reverse_lazy(*args, **kwargs):
return lazy_string(reverse, *args, **kwargs)
def has_signed_terms(user):
if not user.has_signed_terms:
return False
try:
term = ApprovalTerms.objects.order_by('-id')[0]
if user.date_signed_terms < term.date:
return False
except IndexError:
pass
return True
\ No newline at end of file
......@@ -51,10 +51,11 @@ from django.contrib.auth import logout as auth_logout
from django.utils.http import urlencode
from django.http import HttpResponseRedirect, HttpResponseBadRequest
from django.db.utils import IntegrityError
from django.contrib.auth.views import password_change
from astakos.im.models import AstakosUser, Invitation
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
from astakos.im.backends import get_backend
from astakos.im.util import get_context, prepare_response, set_cookie
from astakos.im.util import get_context, prepare_response, set_cookie, has_signed_terms
from astakos.im.forms import *
from astakos.im.functions import send_greeting
from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, BASEURL, LOGOUT_NEXT
......@@ -80,18 +81,34 @@ def render_response(template, tab=None, status=200, reset_cookie=False, context_
def requires_anonymous(func):
"""
Decorator checkes whether the request.user is Anonymous and in that case
Decorator checkes whether the request.user is not Anonymous and in that case
redirects to `logout`.
"""
@wraps(func)
def wrapper(request, *args):
if not request.user.is_anonymous():
next = urlencode({'next': request.build_absolute_uri()})
login_uri = reverse(logout) + '?' + next
return HttpResponseRedirect(login_uri)
logout_uri = reverse(logout) + '?' + next
return HttpResponseRedirect(logout_uri)
return func(request, *args)
return wrapper
def signed_terms_required(func):
"""
Decorator checkes whether the request.user is Anonymous and in that case
redirects to `logout`.
"""
@wraps(func)
def wrapper(request, *args, **kwargs):
if request.user.is_authenticated() and not has_signed_terms(request.user):
params = urlencode({'next': request.build_absolute_uri(),
'show_form':''})
terms_uri = reverse('latest_terms') + '?' + params
return HttpResponseRedirect(terms_uri)
return func(request, *args, **kwargs)
return wrapper
@signed_terms_required
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context={}):
"""
If there is logged on user renders the profile page otherwise renders login page.
......@@ -124,6 +141,7 @@ def index(request, login_template_name='im/login.html', profile_template_name='i
context_instance = get_context(request, extra_context))
@login_required
@signed_terms_required
@transaction.commit_manually
def invite(request, template_name='im/invitations.html', extra_context={}):
"""
......@@ -197,6 +215,7 @@ def invite(request, template_name='im/invitations.html', extra_context={}):
context_instance = context)
@login_required
@signed_terms_required
def edit_profile(request, template_name='im/profile.html', extra_context={}):
"""
Allows a user to edit his/her profile.
......@@ -321,6 +340,7 @@ def signup(request, on_failure='im/signup.html', on_success='im/signup_complete.
context_instance=get_context(request, extra_context))
@login_required
@signed_terms_required
def send_feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context={}):
"""
Allows a user to send feedback.
......@@ -426,3 +446,45 @@ def activate(request, email_template_name='im/welcome_email.txt', on_failure='')
messages.add_message(request, messages.ERROR, message)
transaction.rollback()
return signup(request, on_failure='im/signup.html')
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context={}):
term = None