# Copyright 2011-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. import logging import calendar import inflect engine = inflect.engine() from urllib import quote from functools import wraps from datetime import datetime from django.shortcuts import get_object_or_404 from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse from django.db import transaction from django.db.utils import IntegrityError from django.http import ( HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect, HttpResponseBadRequest, Http404) from django.shortcuts import redirect from django.template import RequestContext, loader as template_loader from django.utils.http import urlencode from django.utils.translation import ugettext as _ from django.views.generic.create_update import ( apply_extra_context, lookup_object, delete_object, get_model_and_form_class) from django.views.generic.list_detail import object_list, object_detail from django.core.xheaders import populate_xheaders from django.core.exceptions import ValidationError, PermissionDenied from django.template.loader import render_to_string from django.views.decorators.http import require_http_methods from django.db.models import Q from django.core.exceptions import PermissionDenied import astakos.im.messages as astakos_messages from astakos.im.activation_backends import get_backend, SimpleBackend from astakos.im.models import ( AstakosUser, ApprovalTerms, EmailChange, GroupKind, RESOURCE_SEPARATOR, AstakosUserAuthProvider, PendingThirdPartyUser, ProjectApplication, ProjectMembership, Project) from astakos.im.util import ( get_context, prepare_response, get_query, restrict_next) from astakos.im.forms import ( LoginForm, InvitationForm, ProfileForm, FeedbackForm, SignApprovalTermsForm, EmailChangeForm, ProjectApplicationForm, ProjectSortForm, AddProjectMembersForm, ProjectSearchForm, ProjectMembersSortForm) from astakos.im.functions import ( send_feedback, SendMailError, logout as auth_logout, activate as activate_func, invite, send_activation as send_activation_func, SendNotificationError, accept_membership, reject_membership, remove_membership, leave_project, join_project, enroll_member) # from astakos.im.endpoints.qh import timeline_charge from astakos.im.settings import ( COOKIE_DOMAIN, LOGOUT_NEXT, LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL, MODERATION_ENABLED) from astakos.im import settings as astakos_settings #from astakos.im.tasks import request_billing from astakos.im.api.callpoint import AstakosCallpoint from astakos.im import auth_providers from astakos.im.templatetags.filters import ResourcePresentation logger = logging.getLogger(__name__) callpoint = AstakosCallpoint() def render_response(template, tab=None, status=200, context_instance=None, **kwargs): """ Calls ``django.template.loader.render_to_string`` with an additional ``tab`` keyword argument and returns an ``django.http.HttpResponse`` with the specified ``status``. """ if tab is None: tab = template.partition('_')[0].partition('.html')[0] kwargs.setdefault('tab', tab) html = template_loader.render_to_string( template, kwargs, context_instance=context_instance) response = HttpResponse(html, status=status) return response def requires_auth_provider(provider_id, **perms): """ """ def decorator(func, *args, **kwargs): @wraps(func) def wrapper(request, *args, **kwargs): provider = auth_providers.get_provider(provider_id) if not provider or not provider.is_active(): raise PermissionDenied if provider: for pkey, value in perms.iteritems(): attr = 'is_available_for_%s' % pkey.lower() if getattr(provider, attr)() != value: #TODO: add session message return HttpResponseRedirect(reverse('login')) return func(request, *args) return wrapper return decorator def requires_anonymous(func): """ 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()}) logout_uri = reverse(logout) + '?' + next return HttpResponseRedirect(logout_uri) return func(request, *args) return wrapper def signed_terms_required(func): """ Decorator checks 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 request.user.signed_terms: 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 def required_auth_methods_assigned(only_warn=False): """ Decorator that checks whether the request.user has all required auth providers assigned. """ required_providers = auth_providers.REQUIRED_PROVIDERS.keys() def decorator(func): if not required_providers: return func @wraps(func) def wrapper(request, *args, **kwargs): if request.user.is_authenticated(): for required in required_providers: if not request.user.has_auth_provider(required): provider = auth_providers.get_provider(required) if only_warn: messages.error(request, _(astakos_messages.AUTH_PROVIDER_REQUIRED % { 'provider': provider.get_title_display})) else: return HttpResponseRedirect(reverse('edit_profile')) return func(request, *args, **kwargs) return wrapper return decorator def valid_astakos_user_required(func): return signed_terms_required(required_auth_methods_assigned()(login_required(func))) @require_http_methods(["GET", "POST"]) @signed_terms_required def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None): """ If there is logged on user renders the profile page otherwise renders login page. **Arguments** ``login_template_name`` A custom login template to use. This is optional; if not specified, this will default to ``im/login.html``. ``profile_template_name`` A custom profile template to use. This is optional; if not specified, this will default to ``im/profile.html``. ``extra_context`` An dictionary of variables to add to the template context. **Template:** im/profile.html or im/login.html or ``template_name`` keyword argument. """ extra_context = extra_context or {} template_name = login_template_name if request.user.is_authenticated(): return HttpResponseRedirect(reverse('astakos.im.views.edit_profile')) third_party_token = request.GET.get('key', False) if third_party_token: messages.info(request, astakos_messages.AUTH_PROVIDER_LOGIN_TO_ADD) return render_response( template_name, login_form = LoginForm(request=request), context_instance = get_context(request, extra_context) ) @require_http_methods(["GET", "POST"]) @valid_astakos_user_required @transaction.commit_manually def invite(request, template_name='im/invitations.html', extra_context=None): """ Allows a user to invite somebody else. In case of GET request renders a form for providing the invitee information. In case of POST checks whether the user has not run out of invitations and then sends an invitation email to singup to the service. The view uses commit_manually decorator in order to ensure the number of the user invitations is going to be updated only if the email has been successfully sent. If the user isn't logged in, redirects to settings.LOGIN_URL. **Arguments** ``template_name`` A custom template to use. This is optional; if not specified, this will default to ``im/invitations.html``. ``extra_context`` An dictionary of variables to add to the template context. **Template:** im/invitations.html or ``template_name`` keyword argument. **Settings:** The view expectes the following settings are defined: * LOGIN_URL: login uri """ extra_context = extra_context or {} status = None message = None form = InvitationForm() inviter = request.user if request.method == 'POST': form = InvitationForm(request.POST) if inviter.invitations > 0: if form.is_valid(): try: email = form.cleaned_data.get('username') realname = form.cleaned_data.get('realname') invite(inviter, email, realname) message = _(astakos_messages.INVITATION_SENT) % locals() messages.success(request, message) except SendMailError, e: message = e.message messages.error(request, message) transaction.rollback() except BaseException, e: message = _(astakos_messages.GENERIC_ERROR) messages.error(request, message) logger.exception(e) transaction.rollback() else: transaction.commit() else: message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED) messages.error(request, message) sent = [{'email': inv.username, 'realname': inv.realname, 'is_consumed': inv.is_consumed} for inv in request.user.invitations_sent.all()] kwargs = {'inviter': inviter, 'sent': sent} context = get_context(request, extra_context, **kwargs) return render_response(template_name, invitation_form=form, context_instance=context) @require_http_methods(["GET", "POST"]) @required_auth_methods_assigned(only_warn=True) @login_required @signed_terms_required def edit_profile(request, template_name='im/profile.html', extra_context=None): """ Allows a user to edit his/her profile. In case of GET request renders a form for displaying the user information. In case of POST updates the user informantion and redirects to ``next`` url parameter if exists. If the user isn't logged in, redirects to settings.LOGIN_URL. **Arguments** ``template_name`` A custom template to use. This is optional; if not specified, this will default to ``im/profile.html``. ``extra_context`` An dictionary of variables to add to the template context. **Template:** im/profile.html or ``template_name`` keyword argument. **Settings:** The view expectes the following settings are defined: * LOGIN_URL: login uri """ extra_context = extra_context or {} form = ProfileForm( instance=request.user, session_key=request.session.session_key ) extra_context['next'] = request.GET.get('next') if request.method == 'POST': form = ProfileForm( request.POST, instance=request.user, session_key=request.session.session_key ) if form.is_valid(): try: prev_token = request.user.auth_token user = form.save() form = ProfileForm( instance=user, session_key=request.session.session_key ) next = restrict_next( request.POST.get('next'), domain=COOKIE_DOMAIN ) if next: return redirect(next) msg = _(astakos_messages.PROFILE_UPDATED) messages.success(request, msg) except ValueError, ve: messages.success(request, ve) elif request.method == "GET": request.user.is_verified = True request.user.save() # existing providers user_providers = request.user.get_active_auth_providers() # providers that user can add user_available_providers = request.user.get_available_auth_providers() return render_response(template_name, profile_form = form, user_providers = user_providers, user_available_providers = user_available_providers, context_instance = get_context(request, extra_context)) @transaction.commit_manually @require_http_methods(["GET", "POST"]) def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None): """ Allows a user to create a local account. In case of GET request renders a form for entering the user information. In case of POST handles the signup. The user activation will be delegated to the backend specified by the ``backend`` keyword argument if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend`` if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not (see activation_backends); Upon successful user creation, if ``next`` url parameter is present the user is redirected there otherwise renders the same page with a success message. On unsuccessful creation, renders ``template_name`` with an error message. **Arguments** ``template_name`` A custom template to render. This is optional; if not specified, this will default to ``im/signup.html``. ``on_success`` A custom template to render in case of success. This is optional; if not specified, this will default to ``im/signup_complete.html``. ``extra_context`` An dictionary of variables to add to the template context. **Template:** im/signup.html or ``template_name`` keyword argument. im/signup_complete.html or ``on_success`` keyword argument. """ extra_context = extra_context or {} if request.user.is_authenticated(): return HttpResponseRedirect(reverse('edit_profile')) provider = get_query(request).get('provider', 'local') if not auth_providers.get_provider(provider).is_available_for_create(): raise PermissionDenied id = get_query(request).get('id') try: instance = AstakosUser.objects.get(id=id) if id else None except AstakosUser.DoesNotExist: instance = None third_party_token = request.REQUEST.get('third_party_token', None) if third_party_token: pending = get_object_or_404(PendingThirdPartyUser, token=third_party_token) provider = pending.provider instance = pending.get_user_instance() try: if not backend: backend = get_backend(request) form = backend.get_signup_form(provider, instance) except Exception, e: form = SimpleBackend(request).get_signup_form(provider) messages.error(request, e) if request.method == 'POST': if form.is_valid(): user = form.save(commit=False) try: result = backend.handle_activation(user) status = messages.SUCCESS message = result.message form.store_user(user, request) if 'additional_email' in form.cleaned_data: additional_email = form.cleaned_data['additional_email'] if additional_email != user.email: user.additionalmail_set.create(email=additional_email) msg = 'Additional email: %s saved for user %s.' % ( additional_email, user.email ) logger._log(LOGGING_LEVEL, msg, []) if user and user.is_active: next = request.POST.get('next', '') response = prepare_response(request, user, next=next) transaction.commit() return response messages.add_message(request, status, message) transaction.commit() return render_response( on_success, context_instance=get_context( request, extra_context ) ) except SendMailError, e: logger.exception(e) status = messages.ERROR message = e.message messages.error(request, message) transaction.rollback() except BaseException, e: logger.exception(e) message = _(astakos_messages.GENERIC_ERROR) messages.error(request, message) logger.exception(e) transaction.rollback() return render_response(template_name, signup_form=form, third_party_token=third_party_token, provider=provider, context_instance=get_context(request, extra_context)) @require_http_methods(["GET", "POST"]) @required_auth_methods_assigned(only_warn=True) @login_required @signed_terms_required def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None): """ Allows a user to send feedback. In case of GET request renders a form for providing the feedback information. In case of POST sends an email to support team. If the user isn't logged in, redirects to settings.LOGIN_URL. **Arguments** ``template_name`` A custom template to use. This is optional; if not specified, this will default to ``im/feedback.html``. ``extra_context`` An dictionary of variables to add to the template context. **Template:** im/signup.html or ``template_name`` keyword argument. **Settings:** * LOGIN_URL: login uri """ extra_context = extra_context or {} if request.method == 'GET': form = FeedbackForm() if request.method == 'POST': if not request.user: return HttpResponse('Unauthorized', status=401) form = FeedbackForm(request.POST) if form.is_valid(): msg = form.cleaned_data['feedback_msg'] data = form.cleaned_data['feedback_data'] try: send_feedback(msg, data, request.user, email_template_name) except SendMailError, e: messages.error(request, message) else: message = _(astakos_messages.FEEDBACK_SENT) messages.success(request, message) return render_response(template_name, feedback_form=form, context_instance=get_context(request, extra_context)) @require_http_methods(["GET"]) @signed_terms_required def logout(request, template='registration/logged_out.html', extra_context=None): """ Wraps `django.contrib.auth.logout`. """ extra_context = extra_context or {} response = HttpResponse() if request.user.is_authenticated(): email = request.user.email auth_logout(request) else: response['Location'] = reverse('index') response.status_code = 301 return response next = restrict_next( request.GET.get('next'), domain=COOKIE_DOMAIN ) if next: response['Location'] = next response.status_code = 302 elif LOGOUT_NEXT: response['Location'] = LOGOUT_NEXT response.status_code = 301 else: messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS)) response['Location'] = reverse('index') response.status_code = 301 return response @require_http_methods(["GET", "POST"]) @transaction.commit_manually def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'): """ Activates the user identified by the ``auth`` request parameter, sends a welcome email and renews the user token. The view uses commit_manually decorator in order to ensure the user state will be updated only if the email will be send successfully. """ token = request.GET.get('auth') next = request.GET.get('next') try: user = AstakosUser.objects.get(auth_token=token) except AstakosUser.DoesNotExist: return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN)) if user.is_active: message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE) messages.error(request, message) return index(request) try: activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True) response = prepare_response(request, user, next, renew=True) transaction.commit() return response except SendMailError, e: message = e.message messages.add_message(request, messages.ERROR, message) transaction.rollback() return index(request) except BaseException, e: status = messages.ERROR message = _(astakos_messages.GENERIC_ERROR) messages.add_message(request, messages.ERROR, message) logger.exception(e) transaction.rollback() return index(request) @require_http_methods(["GET", "POST"]) def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None): extra_context = extra_context or {} 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 ApprovalTerms.DoesNotExist, e: pass if not term: messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS)) return HttpResponseRedirect(reverse('index')) f = open(term.location, 'r') terms = f.read() if request.method == 'POST': next = restrict_next( request.POST.get('next'), domain=COOKIE_DOMAIN ) if not next: next = reverse('index') form = SignApprovalTermsForm(request.POST, instance=request.user) if not form.is_valid(): return render_response(template_name, terms=terms, approval_terms_form=form, context_instance=get_context(request, extra_context)) user = form.save() return HttpResponseRedirect(next) else: form = None if request.user.is_authenticated() and not request.user.signed_terms: form = SignApprovalTermsForm(instance=request.user) return render_response(template_name, terms=terms, approval_terms_form=form, context_instance=get_context(request, extra_context)) @require_http_methods(["GET", "POST"]) @valid_astakos_user_required @transaction.commit_manually def change_email(request, activation_key=None, email_template_name='registration/email_change_email.txt', form_template_name='registration/email_change_form.html', confirm_template_name='registration/email_change_done.html', extra_context=None): extra_context = extra_context or {} if activation_key: try: user = EmailChange.objects.change_email(activation_key) if request.user.is_authenticated() and request.user == user: msg = _(astakos_messages.EMAIL_CHANGED) messages.success(request, msg) auth_logout(request) response = prepare_response(request, user) transaction.commit() return HttpResponseRedirect(reverse('edit_profile')) except ValueError, e: messages.error(request, e) transaction.rollback() return HttpResponseRedirect(reverse('index')) return render_response(confirm_template_name, modified_user=user if 'user' in locals() \ else None, context_instance=get_context(request, extra_context)) if not request.user.is_authenticated(): path = quote(request.get_full_path()) url = request.build_absolute_uri(reverse('index')) return HttpResponseRedirect(url + '?next=' + path) # clean up expired email changes if request.user.email_change_is_pending(): change = request.user.emailchanges.get() if change.activation_key_expired(): change.delete() transaction.commit() return HttpResponseRedirect(reverse('email_change')) form = EmailChangeForm(request.POST or None) if request.method == 'POST' and form.is_valid(): try: # delete pending email changes request.user.emailchanges.all().delete() ec = form.save(email_template_name, request) except SendMailError, e: msg = e messages.error(request, msg) transaction.rollback() return HttpResponseRedirect(reverse('edit_profile')) else: msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED) messages.success(request, msg) transaction.commit() return HttpResponseRedirect(reverse('edit_profile')) if request.user.email_change_is_pending(): messages.warning(request, astakos_messages.PENDING_EMAIL_CHANGE_REQUEST) return render_response( form_template_name, form=form, context_instance=get_context(request, extra_context) ) def send_activation(request, user_id, template_name='im/login.html', extra_context=None): if request.user.is_authenticated(): messages.error(request, _(astakos_messages.ALREADY_LOGGED_IN)) return HttpResponseRedirect(reverse('edit_profile')) if astakos_settings.MODERATION_ENABLED: raise PermissionDenied extra_context = extra_context or {} try: u = AstakosUser.objects.get(id=user_id) except AstakosUser.DoesNotExist: messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN)) else: try: send_activation_func(u) msg = _(astakos_messages.ACTIVATION_SENT) messages.success(request, msg) except SendMailError, e: messages.error(request, e) return render_response( template_name, login_form = LoginForm(request=request), context_instance = get_context( request, extra_context ) ) @require_http_methods(["GET"]) @valid_astakos_user_required def resource_usage(request): def with_class(entry): entry['load_class'] = 'red' max_value = float(entry['maxValue']) curr_value = float(entry['currValue']) entry['ratio_limited']= 0 if max_value > 0 : entry['ratio'] = (curr_value / max_value) * 100 else: entry['ratio'] = 0 if entry['ratio'] < 66: entry['load_class'] = 'yellow' if entry['ratio'] < 33: entry['load_class'] = 'green' if entry['ratio']<0: entry['ratio'] = 0 if entry['ratio']>100: entry['ratio_limited'] = 100 else: entry['ratio_limited'] = entry['ratio'] return entry def pluralize(entry): entry['plural'] = engine.plural(entry.get('name')) return entry resource_usage = None result = callpoint.get_user_usage(request.user.id) if result.is_success: resource_usage = result.data backenddata = map(with_class, result.data) backenddata = map(with_class, backenddata) else: messages.error(request, result.reason) return render_response('im/resource_usage.html', context_instance=get_context(request), resource_usage=backenddata, result=result) ##@require_http_methods(["GET"]) #@require_http_methods(["POST", "GET"]) #@signed_terms_required #@login_required #def billing(request): # # today = datetime.today() # month_last_day = calendar.monthrange(today.year, today.month)[1] # start = request.POST.get('datefrom', None) # if start: # today = datetime.fromtimestamp(int(start)) # month_last_day = calendar.monthrange(today.year, today.month)[1] # # start = datetime(today.year, today.month, 1).strftime("%s") # end = datetime(today.year, today.month, month_last_day).strftime("%s") # r = request_billing.apply(args=('pgerakios@grnet.gr', # int(start) * 1000, # int(end) * 1000)) # data = {} # # try: # status, data = r.result # data = _clear_billing_data(data) # if status != 200: # messages.error(request, _(astakos_messages.BILLING_ERROR) % status) # except: # messages.error(request, r.result) # # return render_response( # template='im/billing.html', # context_instance=get_context(request), # data=data, # zerodate=datetime(month=1, year=1970, day=1), # today=today, # start=int(start), # month_last_day=month_last_day) #def _clear_billing_data(data): # # # remove addcredits entries # def isnotcredit(e): # return e['serviceName'] != "addcredits" # # # separate services # def servicefilter(service_name): # service = service_name # # def fltr(e): # return e['serviceName'] == service # return fltr # # data['bill_nocredits'] = filter(isnotcredit, data['bill']) # data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill']) # data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill']) # data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill']) # # return data # #@require_http_methods(["GET"]) # @require_http_methods(["POST", "GET"]) # @signed_terms_required # @login_required # def timeline(request): # # data = {'entity':request.user.email} # timeline_body = () # timeline_header = () # # form = TimelineForm(data) # form = TimelineForm() # if request.method == 'POST': # data = request.POST # form = TimelineForm(data) # if form.is_valid(): # data = form.cleaned_data # timeline_header = ('entity', 'resource', # 'event name', 'event date', # 'incremental cost', 'total cost') # timeline_body = timeline_charge( # data['entity'], data['resource'], # data['start_date'], data['end_date'], # data['details'], data['operation']) # # return render_response(template='im/timeline.html', # context_instance=get_context(request), # form=form, # timeline_header=timeline_header, # timeline_body=timeline_body) # return data # TODO: action only on POST and user should confirm the removal @require_http_methods(["GET", "POST"]) @login_required @signed_terms_required def remove_auth_provider(request, pk): try: provider = request.user.auth_providers.get(pk=pk) except AstakosUserAuthProvider.DoesNotExist: raise Http404 if provider.can_remove(): provider.delete() return HttpResponseRedirect(reverse('edit_profile')) else: raise PermissionDenied def how_it_works(request): return render_response( 'im/how_it_works.html', context_instance=get_context(request)) @transaction.commit_manually def _create_object(request, model=None, template_name=None, template_loader=template_loader, extra_context=None, post_save_redirect=None, login_required=False, context_processors=None, form_class=None): """ Based of django.views.generic.create_update.create_object which displays a summary page before creating the object. """ rollback = False response = None if extra_context is None: extra_context = {} if login_required and not request.user.is_authenticated(): return redirect_to_login(request.path) try: model, form_class = get_model_and_form_class(model, form_class) extra_context['edit'] = 0 if request.method == 'POST': form = form_class(request.POST, request.FILES) if form.is_valid(): verify = request.GET.get('verify') edit = request.GET.get('edit') if verify == '1': extra_context['show_form'] = False extra_context['form_data'] = form.cleaned_data elif edit == '1': extra_context['show_form'] = True else: new_object = form.save() msg = _("The %(verbose_name)s was created successfully.") %\ {"verbose_name": model._meta.verbose_name} messages.success(request, msg, fail_silently=True) response = redirect(post_save_redirect, new_object) else: form = form_class() except BaseException, e: logger.exception(e) messages.error(request, _(astakos_messages.GENERIC_ERROR)) rollback = True finally: if rollback: transaction.rollback() else: transaction.commit() if response == None: # Create the template, context, response if not template_name: template_name = "%s/%s_form.html" %\ (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) c = RequestContext(request, { 'form': form }, context_processors) apply_extra_context(extra_context, c) response = HttpResponse(t.render(c)) return response @transaction.commit_manually def _update_object(request, model=None, object_id=None, slug=None, slug_field='slug', template_name=None, template_loader=template_loader, extra_context=None, post_save_redirect=None, login_required=False, context_processors=None, template_object_name='object', form_class=None): """ Based of django.views.generic.create_update.update_object which displays a summary page before updating the object. """ rollback = False response = None if extra_context is None: extra_context = {} if login_required and not request.user.is_authenticated(): return redirect_to_login(request.path) try: model, form_class = get_model_and_form_class(model, form_class) obj = lookup_object(model, object_id, slug, slug_field) if request.method == 'POST': form = form_class(request.POST, request.FILES, instance=obj) if form.is_valid(): verify = request.GET.get('verify') edit = request.GET.get('edit') if verify == '1': extra_context['show_form'] = False extra_context['form_data'] = form.cleaned_data elif edit == '1': extra_context['show_form'] = True else: obj = form.save() msg = _("The %(verbose_name)s was updated successfully.") %\ {"verbose_name": model._meta.verbose_name} messages.success(request, msg, fail_silently=True) response = redirect(post_save_redirect, obj) else: form = form_class(instance=obj) except BaseException, e: logger.exception(e) messages.error(request, _(astakos_messages.GENERIC_ERROR)) rollback = True finally: if rollback: transaction.rollback() else: transaction.commit() if response == None: if not template_name: template_name = "%s/%s_form.html" %\ (model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) c = RequestContext(request, { 'form': form, template_object_name: obj, }, context_processors) apply_extra_context(extra_context, c) response = HttpResponse(t.render(c)) populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname)) return response @require_http_methods(["GET", "POST"]) @signed_terms_required @login_required def project_add(request): result = callpoint.list_resources() if not result.is_success: messages.error( request, 'Unable to retrieve system resources: %s' % result.reason ) else: resource_catalog = result.data extra_context = {'resource_catalog':resource_catalog, 'show_form':True} return _create_object(request, template_name='im/projects/projectapplication_form.html', extra_context=extra_context, post_save_redirect='/im/project/list/', form_class=ProjectApplicationForm) @require_http_methods(["GET"]) @signed_terms_required @login_required def project_list(request): q = ProjectApplication.objects.filter(owner=request.user) q |= ProjectApplication.objects.filter(applicant=request.user) q |= ProjectApplication.objects.filter( project__in=request.user.projectmembership_set.values_list('project', flat=True) ) q = q.select_related() sorting = 'name' sort_form = ProjectSortForm(request.GET) if sort_form.is_valid(): sorting = sort_form.cleaned_data.get('sorting') q = q.order_by(sorting) return object_list( request, q, paginate_by=PAGINATE_BY, page=request.GET.get('page') or 1, template_name='im/projects/project_list.html', extra_context={ 'is_search':False, 'sorting':sorting } ) @require_http_methods(["GET", "POST"]) @signed_terms_required @login_required def project_update(request, application_id): result = callpoint.list_resources() if not result.is_success: messages.error( request, 'Unable to retrieve system resources: %s' % result.reason ) else: resource_catalog = result.data extra_context = {'resource_catalog':resource_catalog, 'show_form':True} return _update_object( request, object_id=application_id, template_name='im/projects/projectapplication_form.html', extra_context=extra_context, post_save_redirect='/im/project/list/', form_class=ProjectApplicationForm) @require_http_methods(["GET", "POST"]) @signed_terms_required @login_required @transaction.commit_manually def project_detail(request, application_id): resource_catalog = None result = callpoint.list_resources() if not result.is_success: messages.error( request, 'Unable to retrieve system resources: %s' % result.reason ) else: resource_catalog = result.data addmembers_form = AddProjectMembersForm() if request.method == 'POST': addmembers_form = AddProjectMembersForm(request.POST) if addmembers_form.is_valid(): try: rollback = False application_id = int(application_id) map(lambda u: enroll_member( application_id, u, request_user=request.user), addmembers_form.valid_users) except (IOError, PermissionDenied), e: messages.error(request, e) except BaseException, e: rollback = True messages.error(request, e) finally: if rollback == True: transaction.rollback() else: transaction.commit() addmembers_form = AddProjectMembersForm() # validate sorting sorting = 'person__email' form = ProjectMembersSortForm(request.GET or request.POST) if form.is_valid(): sorting = form.cleaned_data.get('sorting') rollback = False try: return object_detail( request, queryset=ProjectApplication.objects.select_related(), object_id=application_id, template_name='im/projects/project_detail.html', extra_context={ 'resource_catalog':resource_catalog, 'sorting':sorting, 'addmembers_form':addmembers_form } ) except: rollback = True finally: if rollback == True: transaction.rollback() else: transaction.commit() @require_http_methods(["GET", "POST"]) @signed_terms_required @login_required def project_search(request): q = request.GET.get('q') queryset = ProjectApplication.objects if request.method == 'GET': form = ProjectSearchForm() q = q.strip() queryset = queryset.filter(~Q(project__last_approval_date__isnull=True)) queryset = queryset.filter(name__contains=q) else: form = ProjectSearchForm(request.POST) if form.is_valid(): q = form.cleaned_data['q'].strip() queryset = queryset.filter(~Q(project__last_approval_date__isnull=True)) queryset = queryset.filter(name__contains=q) else: queryset = queryset.none() sorting = 'name' # validate sorting sort_form = ProjectSortForm(request.GET) if sort_form.is_valid(): sorting = sort_form.cleaned_data.get('sorting') queryset = queryset.order_by(sorting) return object_list( request, queryset, paginate_by=PAGINATE_BY_ALL, page=request.GET.get('page') or 1, template_name='im/projects/project_list.html', extra_context=dict( form=form, is_search=True, sorting=sorting, q=q, ) ) @require_http_methods(["POST"]) @signed_terms_required @login_required @transaction.commit_manually def project_join(request, application_id): next = request.GET.get('next') if not next: return HttpResponseBadRequest( _(astakos_messages.MISSING_NEXT_PARAMETER)) rollback = False try: application_id = int(application_id) join_project(application_id, request.user) except (IOError, PermissionDenied), e: messages.error(request, e) except BaseException, e: logger.exception(e) messages.error(request, _(astakos_messages.GENERIC_ERROR)) rollback = True finally: if rollback: transaction.rollback() else: transaction.commit() next = restrict_next(next, domain=COOKIE_DOMAIN) return redirect(next) @require_http_methods(["POST"]) @signed_terms_required @login_required @transaction.commit_manually def project_leave(request, application_id): next = request.GET.get('next') if not next: return HttpResponseBadRequest( _(astakos_messages.MISSING_NEXT_PARAMETER)) rollback = False try: application_id = int(application_id) leave_project(application_id, request.user) except (IOError, PermissionDenied), e: messages.error(request, e) except BaseException, e: logger.exception(e) messages.error(request, _(astakos_messages.GENERIC_ERROR)) rollback = True finally: if rollback: transaction.rollback() else: transaction.commit() next = restrict_next(next, domain=COOKIE_DOMAIN) return redirect(next) @require_http_methods(["GET"]) @signed_terms_required @login_required @transaction.commit_manually def project_accept_member(request, application_id, user_id): rollback = False try: application_id = int(application_id) user_id = int(user_id) m = accept_membership(application_id, user_id, request.user) except (IOError, PermissionDenied), e: messages.error(request, e) except BaseException, e: logger.exception(e) messages.error(request, _(astakos_messages.GENERIC_ERROR)) rollback = True else: realname = m.person.realname msg = _(astakos_messages.USER_JOINED_PROJECT) % locals() messages.success(request, msg) finally: if rollback: transaction.rollback() else: transaction.commit() return redirect(reverse('project_detail', args=(application_id,))) @require_http_methods(["GET"]) @signed_terms_required @login_required @transaction.commit_manually def project_remove_member(request, application_id, user_id): rollback = False try: application_id = int(application_id) user_id = int(user_id) m = remove_membership(application_id, user_id, request.user) except (IOError, PermissionDenied), e: messages.error(request, e) except BaseException, e: logger.exception(e) messages.error(request, _(astakos_messages.GENERIC_ERROR)) rollback = True else: realname = m.person.realname msg = _(astakos_messages.USER_LEFT_PROJECT) % locals() messages.success(request, msg) finally: if rollback: transaction.rollback() else: transaction.commit() return redirect(reverse('project_detail', args=(application_id,))) @require_http_methods(["GET"]) @signed_terms_required @login_required @transaction.commit_manually def project_reject_member(request, application_id, user_id): rollback = False try: application_id = int(application_id) user_id = int(user_id) m = reject_membership(application_id, user_id, request.user) except (IOError, PermissionDenied), e: messages.error(request, e) except BaseException, e: logger.exception(e) messages.error(request, _(astakos_messages.GENERIC_ERROR)) rollback = True else: realname = m.person.realname msg = _(astakos_messages.USER_LEFT_PROJECT) % locals() messages.success(request, msg) finally: if rollback: transaction.rollback() else: transaction.commit() return redirect(reverse('project_detail', args=(application_id,)))