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:
"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}
|
=========================== =====================
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
=========================== =====================
modifyuser Modify a user's attributes
showinvitation Show invitation info
showuser Show user info
+addterms Add new approval terms
=============== ===========================
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__)
# 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,
'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)
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', '')}
# 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 _
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
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):
"""
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']
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()
"""
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)
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):
"""
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
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
--- /dev/null
+# 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')
'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'))
--- /dev/null
+# 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']
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):
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
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)
--- /dev/null
+{% 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
# 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'),
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',
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:
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__)
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:
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
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
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.
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={}):
"""
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.
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.
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
+ terms = None
+ if not term_id:
+ try:
+ term = ApprovalTerms.objects.order_by('-id')[0]
+ except IndexError:
+ pass
+ else:
+ try:
+ term = ApprovalTerms.objects.get(id=term_id)
+ except ApprovalTermDoesNotExist, e:
+ pass
+
+ if not term:
+ return HttpResponseBadRequest(_('No approval terms found.'))
+ f = open(term.location, 'r')
+ terms = f.read()
+
+ if request.method == 'POST':
+ next = request.POST.get('next')
+ if not next:
+ return HttpResponseBadRequest(_('No next param.'))
+ form = SignApprovalTermsForm(request.POST, instance=request.user)
+ if not form.is_valid():
+ return render_response(template_name,
+ terms = terms,
+ form = form,
+ context_instance = get_context(request, extra_context))
+ user = form.save()
+ return HttpResponseRedirect(next)
+ else:
+ form = SignApprovalTermsForm(instance=request.user) if request.user.is_authenticated() else None
+ return render_response(template_name,
+ terms = terms,
+ form = form,
+ context_instance = get_context(request, extra_context))
+
+@signed_terms_required
+def change_password(request):
+ return password_change(request, post_change_redirect=reverse('astakos.im.views.edit_profile'))
\ No newline at end of file
from django import forms
from django.utils.safestring import mark_safe
from django.utils import simplejson as json
+from django.utils.translation import ugettext as _
from astakos.im.settings import RECAPTCHA_PUBLIC_KEY, RECAPTCHA_OPTIONS, \
RECAPTCHA_USE_SSL
is_hidden=True
def render(self, *args, **kwargs):
return ''
+
+class ApprovalTermsWidget(forms.CheckboxInput):
+ """
+ A CheckboxInput class with a link to the approval terms.
+ """
+ def __init__(self, attrs=None, check_test=bool, terms_uri='', terms_label=_('Read the terms')):
+ super(ApprovalTermsWidget, self).__init__(attrs, check_test)
+ self.uri = terms_uri
+ self.label = terms_label
+
+ def render(self, name, value, attrs=None):
+ html = super(ApprovalTermsWidget, self).render(name, value, attrs)
+ return html + mark_safe('<a href=%s target="_blank">%s</a>' % (self.uri, self.label))
\ No newline at end of file