Required auth providers functionality
[astakos] / snf-astakos-app / astakos / im / views.py
1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 import logging
35 import calendar
36 import inflect
37
38 engine = inflect.engine()
39
40 from urllib import quote
41 from functools import wraps
42 from datetime import datetime
43
44 from django.shortcuts import get_object_or_404
45 from django.contrib import messages
46 from django.contrib.auth.decorators import login_required
47 from django.core.urlresolvers import reverse
48 from django.db import transaction
49 from django.db.utils import IntegrityError
50 from django.http import (HttpResponse, HttpResponseBadRequest,
51                          HttpResponseForbidden, HttpResponseRedirect,
52                          HttpResponseBadRequest, Http404)
53 from django.shortcuts import redirect
54 from django.template import RequestContext, loader as template_loader
55 from django.utils.http import urlencode
56 from django.utils.translation import ugettext as _
57 from django.views.generic.create_update import (delete_object,
58                                                 get_model_and_form_class)
59 from django.views.generic.list_detail import object_list
60 from django.core.xheaders import populate_xheaders
61 from django.core.exceptions import ValidationError, PermissionDenied
62 from django.template.loader import render_to_string
63 from django.views.decorators.http import require_http_methods
64 from django.db.models import Q
65
66 import astakos.im.messages as astakos_messages
67
68 from astakos.im.activation_backends import get_backend, SimpleBackend
69 from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
70                                EmailChange, GroupKind, Membership,
71                                RESOURCE_SEPARATOR, AstakosUserAuthProvider,
72                                PendingThirdPartyUser)
73 from astakos.im.util import get_context, prepare_response, get_query, restrict_next
74 from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
75                               FeedbackForm, SignApprovalTermsForm,
76                               EmailChangeForm,
77                               AstakosGroupCreationForm, AstakosGroupSearchForm,
78                               AstakosGroupUpdateForm, AddGroupMembersForm,
79                               MembersSortForm, AstakosGroupSortForm,
80                               TimelineForm, PickResourceForm,
81                               AstakosGroupCreationSummaryForm)
82 from astakos.im.functions import (send_feedback, SendMailError,
83                                   logout as auth_logout,
84                                   activate as activate_func,
85                                   send_activation as send_activation_func,
86                                   send_group_creation_notification,
87                                   SendNotificationError)
88 from astakos.im.endpoints.qh import timeline_charge
89 from astakos.im.settings import (COOKIE_DOMAIN, LOGOUT_NEXT,
90                                  LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL)
91 #from astakos.im.tasks import request_billing
92 from astakos.im.api.callpoint import AstakosCallpoint
93
94 from astakos.im import settings
95 from astakos.im import auth_providers
96
97 logger = logging.getLogger(__name__)
98
99 callpoint = AstakosCallpoint()
100
101 def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
102     """
103     Calls ``django.template.loader.render_to_string`` with an additional ``tab``
104     keyword argument and returns an ``django.http.HttpResponse`` with the
105     specified ``status``.
106     """
107     if tab is None:
108         tab = template.partition('_')[0].partition('.html')[0]
109     kwargs.setdefault('tab', tab)
110     html = template_loader.render_to_string(
111         template, kwargs, context_instance=context_instance)
112     response = HttpResponse(html, status=status)
113     return response
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                         #TODO: add session message
131                         return HttpResponseRedirect(reverse('login'))
132             return func(request, *args)
133         return wrapper
134     return decorator
135
136
137 def requires_anonymous(func):
138     """
139     Decorator checkes whether the request.user is not Anonymous and in that case
140     redirects to `logout`.
141     """
142     @wraps(func)
143     def wrapper(request, *args):
144         if not request.user.is_anonymous():
145             next = urlencode({'next': request.build_absolute_uri()})
146             logout_uri = reverse(logout) + '?' + next
147             return HttpResponseRedirect(logout_uri)
148         return func(request, *args)
149     return wrapper
150
151
152 def signed_terms_required(func):
153     """
154     Decorator checks whether the request.user is Anonymous and in that case
155     redirects to `logout`.
156     """
157     @wraps(func)
158     def wrapper(request, *args, **kwargs):
159         if request.user.is_authenticated() and not request.user.signed_terms:
160             params = urlencode({'next': request.build_absolute_uri(),
161                                 'show_form': ''})
162             terms_uri = reverse('latest_terms') + '?' + params
163             return HttpResponseRedirect(terms_uri)
164         return func(request, *args, **kwargs)
165     return wrapper
166
167
168 def required_auth_methods_assigned(only_warn=False):
169     """
170     Decorator that checks whether the request.user has all required auth providers
171     assigned.
172     """
173     required_providers = auth_providers.REQUIRED_PROVIDERS.keys()
174
175     def decorator(func):
176         if not required_providers:
177             return func
178
179         @wraps(func)
180         def wrapper(request, *args, **kwargs):
181             if request.user.is_authenticated():
182                 for required in required_providers:
183                     if not request.user.has_auth_provider(required):
184                         provider = auth_providers.get_provider(required)
185                         if only_warn:
186                             messages.error(request,
187                                            _(astakos_messages.AUTH_PROVIDER_REQUIRED  % {
188                                                'provider': provider.get_title_display}))
189                         else:
190                             return HttpResponseRedirect(reverse('edit_profile'))
191             return func(request, *args, **kwargs)
192         return wrapper
193     return decorator
194
195
196 def valid_astakos_user_required(func):
197     return signed_terms_required(required_auth_methods_assigned()(login_required(func)))
198
199
200 @require_http_methods(["GET", "POST"])
201 @signed_terms_required
202 def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
203     """
204     If there is logged on user renders the profile page otherwise renders login page.
205
206     **Arguments**
207
208     ``login_template_name``
209         A custom login template to use. This is optional; if not specified,
210         this will default to ``im/login.html``.
211
212     ``profile_template_name``
213         A custom profile template to use. This is optional; if not specified,
214         this will default to ``im/profile.html``.
215
216     ``extra_context``
217         An dictionary of variables to add to the template context.
218
219     **Template:**
220
221     im/profile.html or im/login.html or ``template_name`` keyword argument.
222
223     """
224     extra_context = extra_context or {}
225     template_name = login_template_name
226     if request.user.is_authenticated():
227         return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
228
229     return render_response(
230         template_name,
231         login_form = LoginForm(request=request),
232         context_instance = get_context(request, extra_context)
233     )
234
235
236 @require_http_methods(["GET", "POST"])
237 @valid_astakos_user_required
238 @transaction.commit_manually
239 def invite(request, template_name='im/invitations.html', extra_context=None):
240     """
241     Allows a user to invite somebody else.
242
243     In case of GET request renders a form for providing the invitee information.
244     In case of POST checks whether the user has not run out of invitations and then
245     sends an invitation email to singup to the service.
246
247     The view uses commit_manually decorator in order to ensure the number of the
248     user invitations is going to be updated only if the email has been successfully sent.
249
250     If the user isn't logged in, redirects to settings.LOGIN_URL.
251
252     **Arguments**
253
254     ``template_name``
255         A custom template to use. This is optional; if not specified,
256         this will default to ``im/invitations.html``.
257
258     ``extra_context``
259         An dictionary of variables to add to the template context.
260
261     **Template:**
262
263     im/invitations.html or ``template_name`` keyword argument.
264
265     **Settings:**
266
267     The view expectes the following settings are defined:
268
269     * LOGIN_URL: login uri
270     """
271     extra_context = extra_context or {}
272     status = None
273     message = None
274     form = InvitationForm()
275
276     inviter = request.user
277     if request.method == 'POST':
278         form = InvitationForm(request.POST)
279         if inviter.invitations > 0:
280             if form.is_valid():
281                 try:
282                     email = form.cleaned_data.get('username')
283                     realname = form.cleaned_data.get('realname')
284                     inviter.invite(email, realname)
285                     message = _(astakos_messages.INVITATION_SENT) % locals()
286                     messages.success(request, message)
287                 except SendMailError, e:
288                     message = e.message
289                     messages.error(request, message)
290                     transaction.rollback()
291                 except BaseException, e:
292                     message = _(astakos_messages.GENERIC_ERROR)
293                     messages.error(request, message)
294                     logger.exception(e)
295                     transaction.rollback()
296                 else:
297                     transaction.commit()
298         else:
299             message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
300             messages.error(request, message)
301
302     sent = [{'email': inv.username,
303              'realname': inv.realname,
304              'is_consumed': inv.is_consumed}
305             for inv in request.user.invitations_sent.all()]
306     kwargs = {'inviter': inviter,
307               'sent': sent}
308     context = get_context(request, extra_context, **kwargs)
309     return render_response(template_name,
310                            invitation_form=form,
311                            context_instance=context)
312
313
314 @require_http_methods(["GET", "POST"])
315 @required_auth_methods_assigned(only_warn=True)
316 @login_required
317 @signed_terms_required
318 def edit_profile(request, template_name='im/profile.html', extra_context=None):
319     """
320     Allows a user to edit his/her profile.
321
322     In case of GET request renders a form for displaying the user information.
323     In case of POST updates the user informantion and redirects to ``next``
324     url parameter if exists.
325
326     If the user isn't logged in, redirects to settings.LOGIN_URL.
327
328     **Arguments**
329
330     ``template_name``
331         A custom template to use. This is optional; if not specified,
332         this will default to ``im/profile.html``.
333
334     ``extra_context``
335         An dictionary of variables to add to the template context.
336
337     **Template:**
338
339     im/profile.html or ``template_name`` keyword argument.
340
341     **Settings:**
342
343     The view expectes the following settings are defined:
344
345     * LOGIN_URL: login uri
346     """
347     extra_context = extra_context or {}
348     form = ProfileForm(
349         instance=request.user,
350         session_key=request.session.session_key
351     )
352     extra_context['next'] = request.GET.get('next')
353     if request.method == 'POST':
354         form = ProfileForm(
355             request.POST,
356             instance=request.user,
357             session_key=request.session.session_key
358         )
359         if form.is_valid():
360             try:
361                 prev_token = request.user.auth_token
362                 user = form.save()
363                 form = ProfileForm(
364                     instance=user,
365                     session_key=request.session.session_key
366                 )
367                 next = restrict_next(
368                     request.POST.get('next'),
369                     domain=COOKIE_DOMAIN
370                 )
371                 if next:
372                     return redirect(next)
373                 msg = _(astakos_messages.PROFILE_UPDATED)
374                 messages.success(request, msg)
375             except ValueError, ve:
376                 messages.success(request, ve)
377     elif request.method == "GET":
378         request.user.is_verified = True
379         request.user.save()
380
381     # existing providers
382     user_providers = request.user.get_active_auth_providers()
383
384     # providers that user can add
385     user_available_providers = request.user.get_available_auth_providers()
386
387     return render_response(template_name,
388                            profile_form = form,
389                            user_providers = user_providers,
390                            user_available_providers = user_available_providers,
391                            context_instance = get_context(request,
392                                                           extra_context))
393
394
395 @transaction.commit_manually
396 @require_http_methods(["GET", "POST"])
397 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
398     """
399     Allows a user to create a local account.
400
401     In case of GET request renders a form for entering the user information.
402     In case of POST handles the signup.
403
404     The user activation will be delegated to the backend specified by the ``backend`` keyword argument
405     if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
406     if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
407     (see activation_backends);
408
409     Upon successful user creation, if ``next`` url parameter is present the user is redirected there
410     otherwise renders the same page with a success message.
411
412     On unsuccessful creation, renders ``template_name`` with an error message.
413
414     **Arguments**
415
416     ``template_name``
417         A custom template to render. This is optional;
418         if not specified, this will default to ``im/signup.html``.
419
420     ``on_success``
421         A custom template to render in case of success. This is optional;
422         if not specified, this will default to ``im/signup_complete.html``.
423
424     ``extra_context``
425         An dictionary of variables to add to the template context.
426
427     **Template:**
428
429     im/signup.html or ``template_name`` keyword argument.
430     im/signup_complete.html or ``on_success`` keyword argument.
431     """
432     extra_context = extra_context or {}
433     if request.user.is_authenticated():
434         return HttpResponseRedirect(reverse('edit_profile'))
435
436     provider = get_query(request).get('provider', 'local')
437     if not auth_providers.get_provider(provider).is_available_for_create():
438         raise PermissionDenied
439
440     id = get_query(request).get('id')
441     try:
442         instance = AstakosUser.objects.get(id=id) if id else None
443     except AstakosUser.DoesNotExist:
444         instance = None
445
446     third_party_token = request.REQUEST.get('third_party_token', None)
447     if third_party_token:
448         pending = get_object_or_404(PendingThirdPartyUser,
449                                     token=third_party_token)
450         provider = pending.provider
451         instance = pending.get_user_instance()
452
453     try:
454         if not backend:
455             backend = get_backend(request)
456         form = backend.get_signup_form(provider, instance)
457     except Exception, e:
458         form = SimpleBackend(request).get_signup_form(provider)
459         messages.error(request, e)
460     if request.method == 'POST':
461         if form.is_valid():
462             user = form.save(commit=False)
463             try:
464                 result = backend.handle_activation(user)
465                 status = messages.SUCCESS
466                 message = result.message
467
468                 form.store_user(user, request)
469
470                 if 'additional_email' in form.cleaned_data:
471                     additional_email = form.cleaned_data['additional_email']
472                     if additional_email != user.email:
473                         user.additionalmail_set.create(email=additional_email)
474                         msg = 'Additional email: %s saved for user %s.' % (
475                             additional_email,
476                             user.email
477                         )
478                         logger._log(LOGGING_LEVEL, msg, [])
479                 if user and user.is_active:
480                     next = request.POST.get('next', '')
481                     response = prepare_response(request, user, next=next)
482                     transaction.commit()
483                     return response
484                 messages.add_message(request, status, message)
485                 transaction.commit()
486                 return render_response(
487                     on_success,
488                     context_instance=get_context(
489                         request,
490                         extra_context
491                     )
492                 )
493             except SendMailError, e:
494                 logger.exception(e)
495                 status = messages.ERROR
496                 message = e.message
497                 messages.error(request, message)
498                 transaction.rollback()
499             except BaseException, e:
500                 logger.exception(e)
501                 message = _(astakos_messages.GENERIC_ERROR)
502                 messages.error(request, message)
503                 logger.exception(e)
504                 transaction.rollback()
505     return render_response(template_name,
506                            signup_form=form,
507                            third_party_token=third_party_token,
508                            provider=provider,
509                            context_instance=get_context(request, extra_context))
510
511
512 @require_http_methods(["GET", "POST"])
513 @required_auth_methods_assigned(only_warn=True)
514 @login_required
515 @signed_terms_required
516 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
517     """
518     Allows a user to send feedback.
519
520     In case of GET request renders a form for providing the feedback information.
521     In case of POST sends an email to support team.
522
523     If the user isn't logged in, redirects to settings.LOGIN_URL.
524
525     **Arguments**
526
527     ``template_name``
528         A custom template to use. This is optional; if not specified,
529         this will default to ``im/feedback.html``.
530
531     ``extra_context``
532         An dictionary of variables to add to the template context.
533
534     **Template:**
535
536     im/signup.html or ``template_name`` keyword argument.
537
538     **Settings:**
539
540     * LOGIN_URL: login uri
541     """
542     extra_context = extra_context or {}
543     if request.method == 'GET':
544         form = FeedbackForm()
545     if request.method == 'POST':
546         if not request.user:
547             return HttpResponse('Unauthorized', status=401)
548
549         form = FeedbackForm(request.POST)
550         if form.is_valid():
551             msg = form.cleaned_data['feedback_msg']
552             data = form.cleaned_data['feedback_data']
553             try:
554                 send_feedback(msg, data, request.user, email_template_name)
555             except SendMailError, e:
556                 messages.error(request, message)
557             else:
558                 message = _(astakos_messages.FEEDBACK_SENT)
559                 messages.success(request, message)
560     return render_response(template_name,
561                            feedback_form=form,
562                            context_instance=get_context(request, extra_context))
563
564
565 @require_http_methods(["GET"])
566 @signed_terms_required
567 def logout(request, template='registration/logged_out.html', extra_context=None):
568     """
569     Wraps `django.contrib.auth.logout`.
570     """
571     extra_context = extra_context or {}
572     response = HttpResponse()
573     if request.user.is_authenticated():
574         email = request.user.email
575         auth_logout(request)
576     else:
577         response['Location'] = reverse('index')
578         response.status_code = 301
579         return response
580
581     next = restrict_next(
582         request.GET.get('next'),
583         domain=COOKIE_DOMAIN
584     )
585
586     if next:
587         response['Location'] = next
588         response.status_code = 302
589     elif LOGOUT_NEXT:
590         response['Location'] = LOGOUT_NEXT
591         response.status_code = 301
592     else:
593         messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
594         response['Location'] = reverse('index')
595         response.status_code = 301
596     return response
597
598
599 @require_http_methods(["GET", "POST"])
600 @transaction.commit_manually
601 def activate(request, greeting_email_template_name='im/welcome_email.txt',
602              helpdesk_email_template_name='im/helpdesk_notification.txt'):
603     """
604     Activates the user identified by the ``auth`` request parameter, sends a welcome email
605     and renews the user token.
606
607     The view uses commit_manually decorator in order to ensure the user state will be updated
608     only if the email will be send successfully.
609     """
610     token = request.GET.get('auth')
611     next = request.GET.get('next')
612     try:
613         user = AstakosUser.objects.get(auth_token=token)
614     except AstakosUser.DoesNotExist:
615         return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
616
617     if user.is_active:
618         message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
619         messages.error(request, message)
620         return index(request)
621
622     try:
623         activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
624         response = prepare_response(request, user, next, renew=True)
625         transaction.commit()
626         return response
627     except SendMailError, e:
628         message = e.message
629         messages.add_message(request, messages.ERROR, message)
630         transaction.rollback()
631         return index(request)
632     except BaseException, e:
633         status = messages.ERROR
634         message = _(astakos_messages.GENERIC_ERROR)
635         messages.add_message(request, messages.ERROR, message)
636         logger.exception(e)
637         transaction.rollback()
638         return index(request)
639
640
641 @require_http_methods(["GET", "POST"])
642 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
643     extra_context = extra_context or {}
644     term = None
645     terms = None
646     if not term_id:
647         try:
648             term = ApprovalTerms.objects.order_by('-id')[0]
649         except IndexError:
650             pass
651     else:
652         try:
653             term = ApprovalTerms.objects.get(id=term_id)
654         except ApprovalTerms.DoesNotExist, e:
655             pass
656
657     if not term:
658         messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
659         return HttpResponseRedirect(reverse('index'))
660     f = open(term.location, 'r')
661     terms = f.read()
662
663     if request.method == 'POST':
664         next = restrict_next(
665             request.POST.get('next'),
666             domain=COOKIE_DOMAIN
667         )
668         if not next:
669             next = reverse('index')
670         form = SignApprovalTermsForm(request.POST, instance=request.user)
671         if not form.is_valid():
672             return render_response(template_name,
673                                    terms=terms,
674                                    approval_terms_form=form,
675                                    context_instance=get_context(request, extra_context))
676         user = form.save()
677         return HttpResponseRedirect(next)
678     else:
679         form = None
680         if request.user.is_authenticated() and not request.user.signed_terms:
681             form = SignApprovalTermsForm(instance=request.user)
682         return render_response(template_name,
683                                terms=terms,
684                                approval_terms_form=form,
685                                context_instance=get_context(request, extra_context))
686
687
688 @require_http_methods(["GET", "POST"])
689 @valid_astakos_user_required
690 @transaction.commit_manually
691 def change_email(request, activation_key=None,
692                  email_template_name='registration/email_change_email.txt',
693                  form_template_name='registration/email_change_form.html',
694                  confirm_template_name='registration/email_change_done.html',
695                  extra_context=None):
696     extra_context = extra_context or {}
697     if activation_key:
698         try:
699             user = EmailChange.objects.change_email(activation_key)
700             if request.user.is_authenticated() and request.user == user:
701                 msg = _(astakos_messages.EMAIL_CHANGED)
702                 messages.success(request, msg)
703                 auth_logout(request)
704                 response = prepare_response(request, user)
705                 transaction.commit()
706                 return response
707         except ValueError, e:
708             messages.error(request, e)
709         return render_response(confirm_template_name,
710                                modified_user=user if 'user' in locals(
711                                ) else None,
712                                context_instance=get_context(request,
713                                                             extra_context))
714
715     if not request.user.is_authenticated():
716         path = quote(request.get_full_path())
717         url = request.build_absolute_uri(reverse('index'))
718         return HttpResponseRedirect(url + '?next=' + path)
719     form = EmailChangeForm(request.POST or None)
720     if request.method == 'POST' and form.is_valid():
721         try:
722             ec = form.save(email_template_name, request)
723         except SendMailError, e:
724             msg = e
725             messages.error(request, msg)
726             transaction.rollback()
727         except IntegrityError, e:
728             msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
729             messages.error(request, msg)
730         else:
731             msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
732             messages.success(request, msg)
733             transaction.commit()
734     return render_response(
735         form_template_name,
736         form=form,
737         context_instance=get_context(request, extra_context)
738     )
739
740
741 def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
742
743     if request.user.is_authenticated():
744         messages.error(request, _(astakos_messages.ALREADY_LOGGED_IN))
745         return HttpResponseRedirect(reverse('edit_profile'))
746
747     if settings.MODERATION_ENABLED:
748         raise PermissionDenied
749
750     extra_context = extra_context or {}
751     try:
752         u = AstakosUser.objects.get(id=user_id)
753     except AstakosUser.DoesNotExist:
754         messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
755     else:
756         try:
757             send_activation_func(u)
758             msg = _(astakos_messages.ACTIVATION_SENT)
759             messages.success(request, msg)
760         except SendMailError, e:
761             messages.error(request, e)
762     return render_response(
763         template_name,
764         login_form = LoginForm(request=request),
765         context_instance = get_context(
766             request,
767             extra_context
768         )
769     )
770
771 class ResourcePresentation():
772
773     def __init__(self, data):
774         self.data = data
775
776     def update_from_result(self, result):
777         if result.is_success:
778             for r in result.data:
779                 rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
780                 if not rname in self.data['resources']:
781                     self.data['resources'][rname] = {}
782
783                 self.data['resources'][rname].update(r)
784                 self.data['resources'][rname]['id'] = rname
785                 group = r.get('group')
786                 if not group in self.data['groups']:
787                     self.data['groups'][group] = {}
788
789                 self.data['groups'][r.get('group')].update({'name': r.get('group')})
790
791     def test(self, quota_dict):
792         for k, v in quota_dict.iteritems():
793             rname = k
794             value = v
795             if not rname in self.data['resources']:
796                 self.data['resources'][rname] = {}
797
798
799             self.data['resources'][rname]['value'] = value
800
801
802     def update_from_result_report(self, result):
803         if result.is_success:
804             for r in result.data:
805                 rname = r.get('name')
806                 if not rname in self.data['resources']:
807                     self.data['resources'][rname] = {}
808
809                 self.data['resources'][rname].update(r)
810                 self.data['resources'][rname]['id'] = rname
811                 group = r.get('group')
812                 if not group in self.data['groups']:
813                     self.data['groups'][group] = {}
814
815                 self.data['groups'][r.get('group')].update({'name': r.get('group')})
816
817     def get_group_resources(self, group):
818         return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
819
820     def get_groups_resources(self):
821         for g in self.data['groups']:
822             yield g, self.get_group_resources(g)
823
824     def get_quota(self, group_quotas):
825         for r, v in group_quotas.iteritems():
826             rname = str(r)
827             quota = self.data['resources'].get(rname)
828             quota['value'] = v
829             yield quota
830
831
832     def get_policies(self, policies_data):
833         for policy in policies_data:
834             rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
835             policy.update(self.data['resources'].get(rname))
836             yield policy
837
838     def __repr__(self):
839         return self.data.__repr__()
840
841     def __iter__(self, *args, **kwargs):
842         return self.data.__iter__(*args, **kwargs)
843
844     def __getitem__(self, *args, **kwargs):
845         return self.data.__getitem__(*args, **kwargs)
846
847     def get(self, *args, **kwargs):
848         return self.data.get(*args, **kwargs)
849
850
851
852 @require_http_methods(["GET", "POST"])
853 @valid_astakos_user_required
854 def group_add(request, kind_name='default'):
855
856     result = callpoint.list_resources()
857     resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
858     resource_catalog.update_from_result(result)
859
860     if not result.is_success:
861         messages.error(
862             request,
863             'Unable to retrieve system resources: %s' % result.reason
864     )
865
866     try:
867         kind = GroupKind.objects.get(name=kind_name)
868     except:
869         return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
870
871
872
873     post_save_redirect = '/im/group/%(id)s/'
874     context_processors = None
875     model, form_class = get_model_and_form_class(
876         model=None,
877         form_class=AstakosGroupCreationForm
878     )
879
880     if request.method == 'POST':
881         form = form_class(request.POST, request.FILES)
882         if form.is_valid():
883             policies = form.policies()
884             return render_response(
885                 template='im/astakosgroup_form_summary.html',
886                 context_instance=get_context(request),
887                 form=AstakosGroupCreationSummaryForm(form.cleaned_data),
888                 policies=resource_catalog.get_policies(policies)
889             )
890     else:
891         now = datetime.now()
892         data = {
893             'kind': kind,
894         }
895         for group, resources in resource_catalog.get_groups_resources():
896             data['is_selected_%s' % group] = False
897             for resource in resources:
898                 data['%s_uplimit' % resource] = ''
899
900         form = form_class(data)
901
902     # Create the template, context, response
903     template_name = "%s/%s_form.html" % (
904         model._meta.app_label,
905         model._meta.object_name.lower()
906     )
907     t = template_loader.get_template(template_name)
908     c = RequestContext(request, {
909         'form': form,
910         'kind': kind,
911         'resource_catalog':resource_catalog,
912     }, context_processors)
913     return HttpResponse(t.render(c))
914
915
916 #@require_http_methods(["POST"])
917 @require_http_methods(["GET", "POST"])
918 @valid_astakos_user_required
919 def group_add_complete(request):
920     model = AstakosGroup
921     form = AstakosGroupCreationSummaryForm(request.POST)
922     if form.is_valid():
923         d = form.cleaned_data
924         d['owners'] = [request.user]
925         result = callpoint.create_groups((d,)).next()
926         if result.is_success:
927             new_object = result.data[0]
928             msg = _(astakos_messages.OBJECT_CREATED) %\
929                 {"verbose_name": model._meta.verbose_name}
930             messages.success(request, msg, fail_silently=True)
931
932             # send notification
933             try:
934                 send_group_creation_notification(
935                     template_name='im/group_creation_notification.txt',
936                     dictionary={
937                         'group': new_object,
938                         'owner': request.user,
939                         'policies': d.get('policies', [])
940                     }
941                 )
942             except SendNotificationError, e:
943                 messages.error(request, e, fail_silently=True)
944             post_save_redirect = '/im/group/%(id)s/'
945             return HttpResponseRedirect(post_save_redirect % new_object)
946         else:
947             d = {"verbose_name": model._meta.verbose_name,
948                  "reason":result.reason}
949             msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
950             messages.error(request, msg, fail_silently=True)
951     return render_response(
952         template='im/astakosgroup_form_summary.html',
953         context_instance=get_context(request),
954         form=form,
955         policies=form.cleaned_data.get('policies')
956     )
957
958
959 #@require_http_methods(["GET"])
960 @require_http_methods(["GET", "POST"])
961 @valid_astakos_user_required
962 def group_list(request):
963     none = request.user.astakos_groups.none()
964     query = """
965         SELECT auth_group.id,
966         auth_group.name AS groupname,
967         im_groupkind.name AS kindname,
968         im_astakosgroup.*,
969         owner.email AS groupowner,
970         (SELECT COUNT(*) FROM im_membership
971             WHERE group_id = im_astakosgroup.group_ptr_id
972             AND date_joined IS NOT NULL) AS approved_members_num,
973         (SELECT CASE WHEN(
974                     SELECT date_joined FROM im_membership
975                     WHERE group_id = im_astakosgroup.group_ptr_id
976                     AND person_id = %(id)s) IS NULL
977                     THEN 0 ELSE 1 END) AS membership_status
978         FROM im_astakosgroup
979         INNER JOIN im_membership ON (
980             im_astakosgroup.group_ptr_id = im_membership.group_id)
981         INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
982         INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
983         LEFT JOIN im_astakosuser_owner ON (
984             im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
985         LEFT JOIN auth_user as owner ON (
986             im_astakosuser_owner.astakosuser_id = owner.id)
987         WHERE im_membership.person_id = %(id)s
988         AND im_groupkind.name != 'default'
989         """ % request.user.__dict__
990
991     # validate sorting
992     sorting = 'groupname'
993     ordering = ''
994     sort_form = AstakosGroupSortForm(request.GET)
995     if sort_form.is_valid():
996         sorting = sort_form.cleaned_data.get('sorting')
997         ordering = request.GET.get('ordering', 'ASC' )
998         
999     query = query+" ORDER BY %s %s" %(sorting, ordering)
1000
1001     q = AstakosGroup.objects.raw(query)
1002
1003     # Create the template, context, response
1004     template_name = "%s/%s_list.html" % (
1005         q.model._meta.app_label,
1006         q.model._meta.object_name.lower()
1007     )
1008     
1009     extra_context = dict(
1010         is_search=False,
1011         q=q,
1012         sorting=sorting,
1013         page=request.GET.get('page', 1),
1014         ordering=ordering,
1015     )
1016     return render_response(template_name,
1017                            context_instance=get_context(request, extra_context)
1018     )
1019
1020
1021 @require_http_methods(["GET", "POST"])
1022 @valid_astakos_user_required
1023 def group_detail(request, group_id):
1024     q = AstakosGroup.objects.select_related().filter(pk=group_id)
1025     q = q.extra(select={
1026         'is_member': """SELECT CASE WHEN EXISTS(
1027                             SELECT id FROM im_membership
1028                             WHERE group_id = im_astakosgroup.group_ptr_id
1029                             AND person_id = %s)
1030                         THEN 1 ELSE 0 END""" % request.user.id,
1031         'is_owner': """SELECT CASE WHEN EXISTS(
1032                         SELECT id FROM im_astakosuser_owner
1033                         WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1034                         AND astakosuser_id = %s)
1035                         THEN 1 ELSE 0 END""" % request.user.id,
1036         'is_active_member': """SELECT CASE WHEN(
1037                         SELECT date_joined FROM im_membership
1038                         WHERE group_id = im_astakosgroup.group_ptr_id
1039                         AND person_id = %s) IS NULL
1040                         THEN 0 ELSE 1 END""" % request.user.id,
1041         'kindname': """SELECT name FROM im_groupkind
1042                        WHERE id = im_astakosgroup.kind_id"""})
1043
1044     model = q.model
1045     context_processors = None
1046     mimetype = None
1047     try:
1048         obj = q.get()
1049     except AstakosGroup.DoesNotExist:
1050         raise Http404("No %s found matching the query" % (
1051             model._meta.verbose_name))
1052
1053     update_form = AstakosGroupUpdateForm(instance=obj)
1054     addmembers_form = AddGroupMembersForm()
1055     if request.method == 'POST':
1056         update_data = {}
1057         addmembers_data = {}
1058         for k, v in request.POST.iteritems():
1059             if k in update_form.fields:
1060                 update_data[k] = v
1061             if k in addmembers_form.fields:
1062                 addmembers_data[k] = v
1063         update_data = update_data or None
1064         addmembers_data = addmembers_data or None
1065         update_form = AstakosGroupUpdateForm(update_data, instance=obj)
1066         addmembers_form = AddGroupMembersForm(addmembers_data)
1067         if update_form.is_valid():
1068             update_form.save()
1069         if addmembers_form.is_valid():
1070             try:
1071                 map(obj.approve_member, addmembers_form.valid_users)
1072             except AssertionError:
1073                 msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1074                 messages.error(request, msg)
1075             addmembers_form = AddGroupMembersForm()
1076
1077     template_name = "%s/%s_detail.html" % (
1078         model._meta.app_label, model._meta.object_name.lower())
1079     t = template_loader.get_template(template_name)
1080     c = RequestContext(request, {
1081         'object': obj,
1082     }, context_processors)
1083
1084     # validate sorting
1085     sorting = 'person__email'
1086     form = MembersSortForm(request.GET)
1087     if form.is_valid():
1088         sorting = form.cleaned_data.get('sorting')
1089
1090     result = callpoint.list_resources()
1091     resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1092     resource_catalog.update_from_result(result)
1093
1094
1095     if not result.is_success:
1096         messages.error(
1097             request,
1098             'Unable to retrieve system resources: %s' % result.reason
1099     )
1100
1101     extra_context = {'update_form': update_form,
1102                      'addmembers_form': addmembers_form,
1103                      'page': request.GET.get('page', 1),
1104                      'sorting': sorting,
1105                      'resource_catalog':resource_catalog,
1106                      'quota':resource_catalog.get_quota(obj.quota)}
1107     for key, value in extra_context.items():
1108         if callable(value):
1109             c[key] = value()
1110         else:
1111             c[key] = value
1112     response = HttpResponse(t.render(c), mimetype=mimetype)
1113     populate_xheaders(
1114         request, response, model, getattr(obj, obj._meta.pk.name))
1115     return response
1116
1117
1118 @require_http_methods(["GET", "POST"])
1119 @valid_astakos_user_required
1120 def group_search(request, extra_context=None, **kwargs):
1121     q = request.GET.get('q')
1122     if request.method == 'GET':
1123         form = AstakosGroupSearchForm({'q': q} if q else None)
1124     else:
1125         form = AstakosGroupSearchForm(get_query(request))
1126         if form.is_valid():
1127             q = form.cleaned_data['q'].strip()
1128             
1129     sorting = 'groupname'
1130     order_dict = {'ASC':'', 'DESC':'-'  }
1131     ordering = ''
1132     sort_order = 'groupname' 
1133     if q:
1134         queryset = AstakosGroup.objects.select_related()
1135         queryset = queryset.filter(~Q(kind__name='default'))
1136         queryset = queryset.filter(name__contains=q)
1137         queryset = queryset.filter(approval_date__isnull=False)
1138         queryset = queryset.extra(select={
1139                                   'groupname': "auth_group.name",
1140                                   'kindname': "im_groupkind.name",
1141                                   'approved_members_num': """
1142                     SELECT COUNT(*) FROM im_membership
1143                     WHERE group_id = im_astakosgroup.group_ptr_id
1144                     AND date_joined IS NOT NULL""",
1145                                   'membership_approval_date': """
1146                     SELECT date_joined FROM im_membership
1147                     WHERE group_id = im_astakosgroup.group_ptr_id
1148                     AND person_id = %s""" % request.user.id,
1149                                   'is_member': """
1150                     SELECT CASE WHEN EXISTS(
1151                     SELECT date_joined FROM im_membership
1152                     WHERE group_id = im_astakosgroup.group_ptr_id
1153                     AND person_id = %s)
1154                     THEN 1 ELSE 0 END""" % request.user.id,
1155                                   'is_owner': """
1156                     SELECT CASE WHEN EXISTS(
1157                     SELECT id FROM im_astakosuser_owner
1158                     WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1159                     AND astakosuser_id = %s)
1160                     THEN 1 ELSE 0 END""" % request.user.id,
1161                     'is_owner': """SELECT CASE WHEN EXISTS(
1162                         SELECT id FROM im_astakosuser_owner
1163                         WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1164                         AND astakosuser_id = %s)
1165                         THEN 1 ELSE 0 END""" % request.user.id,
1166                     })
1167
1168         # validate sorting
1169         
1170         
1171         sort_form = AstakosGroupSortForm(request.GET)
1172         if sort_form.is_valid():
1173             sorting = sort_form.cleaned_data.get('sorting')
1174             ordering = request.GET.get('ordering','ASC')
1175             sort_order = order_dict[ordering]+sorting
1176         queryset = queryset.order_by(sort_order)
1177
1178     else:
1179         queryset = AstakosGroup.objects.none()
1180     return object_list(
1181         request,
1182         queryset,
1183         paginate_by=PAGINATE_BY_ALL,
1184         page=request.GET.get('page') or 1,
1185         template_name='im/astakosgroup_list.html',
1186         extra_context=dict(form=form,
1187                            is_search=True,
1188                            q=q,
1189                            sorting=sorting,
1190                            ordering=ordering))
1191
1192
1193 @require_http_methods(["GET", "POST"])
1194 @valid_astakos_user_required
1195 def group_all(request, extra_context=None, **kwargs):
1196     q = AstakosGroup.objects.select_related()
1197     q = q.filter(~Q(kind__name='default'))
1198     q = q.filter(approval_date__isnull=False)
1199     q = q.extra(select={
1200                 'groupname': "auth_group.name",
1201                 'kindname': "im_groupkind.name",
1202                 'approved_members_num': """
1203                     SELECT COUNT(*) FROM im_membership
1204                     WHERE group_id = im_astakosgroup.group_ptr_id
1205                     AND date_joined IS NOT NULL""",
1206                 'membership_approval_date': """
1207                     SELECT date_joined FROM im_membership
1208                     WHERE group_id = im_astakosgroup.group_ptr_id
1209                     AND person_id = %s""" % request.user.id,
1210                 'is_member': """
1211                     SELECT CASE WHEN EXISTS(
1212                     SELECT date_joined FROM im_membership
1213                     WHERE group_id = im_astakosgroup.group_ptr_id
1214                     AND person_id = %s)
1215                     THEN 1 ELSE 0 END""" % request.user.id,
1216                  'is_owner': """SELECT CASE WHEN EXISTS(
1217                         SELECT id FROM im_astakosuser_owner
1218                         WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1219                         AND astakosuser_id = %s)
1220                         THEN 1 ELSE 0 END""" % request.user.id,   })
1221
1222     # validate sorting
1223     sorting = 'groupname'
1224     order_dict = {'ASC':'', 'DESC':'-'  }
1225     ordering = ''
1226     sort_order = 'groupname'
1227     sort_form = AstakosGroupSortForm(request.GET)
1228     
1229     if sort_form.is_valid():
1230         sorting = sort_form.cleaned_data.get('sorting')
1231         ordering = request.GET.get('ordering','ASC')
1232         sort_order = order_dict[ordering]+sorting
1233     q = q.order_by(sort_order)
1234     
1235     return object_list(
1236         request,
1237         q,
1238         paginate_by=PAGINATE_BY_ALL,
1239         page=request.GET.get('page') or 1,
1240         template_name='im/astakosgroup_list.html',
1241         extra_context=dict(form=AstakosGroupSearchForm(),
1242                            is_search=True,
1243                            sorting=sorting,
1244                            ordering=ordering))
1245
1246
1247 #@require_http_methods(["POST"])
1248 @require_http_methods(["POST", "GET"])
1249 @valid_astakos_user_required
1250 def group_join(request, group_id):
1251     m = Membership(group_id=group_id,
1252                    person=request.user,
1253                    date_requested=datetime.now())
1254     try:
1255         m.save()
1256         post_save_redirect = reverse(
1257             'group_detail',
1258             kwargs=dict(group_id=group_id))
1259         return HttpResponseRedirect(post_save_redirect)
1260     except IntegrityError, e:
1261         logger.exception(e)
1262         msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1263         messages.error(request, msg)
1264         return group_search(request)
1265
1266
1267 @require_http_methods(["POST"])
1268 @valid_astakos_user_required
1269 def group_leave(request, group_id):
1270     try:
1271         m = Membership.objects.select_related().get(
1272             group__id=group_id,
1273             person=request.user)
1274     except Membership.DoesNotExist:
1275         return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1276     if request.user in m.group.owner.all():
1277         return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1278     return delete_object(
1279         request,
1280         model=Membership,
1281         object_id=m.id,
1282         template_name='im/astakosgroup_list.html',
1283         post_delete_redirect=reverse(
1284             'group_detail',
1285             kwargs=dict(group_id=group_id)))
1286
1287
1288 def handle_membership(func):
1289     @wraps(func)
1290     def wrapper(request, group_id, user_id):
1291         try:
1292             m = Membership.objects.select_related().get(
1293                 group__id=group_id,
1294                 person__id=user_id)
1295         except Membership.DoesNotExist:
1296             return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1297         else:
1298             if request.user not in m.group.owner.all():
1299                 return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1300             func(request, m)
1301             return group_detail(request, group_id)
1302     return wrapper
1303
1304
1305 #@require_http_methods(["POST"])
1306 @require_http_methods(["POST", "GET"])
1307 @valid_astakos_user_required
1308 @handle_membership
1309 def approve_member(request, membership):
1310     try:
1311         membership.approve()
1312         realname = membership.person.realname
1313         msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1314         messages.success(request, msg)
1315     except AssertionError:
1316         msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1317         messages.error(request, msg)
1318     except BaseException, e:
1319         logger.exception(e)
1320         realname = membership.person.realname
1321         msg = _(astakos_messages.GENERIC_ERROR)
1322         messages.error(request, msg)
1323
1324
1325 @valid_astakos_user_required
1326 @handle_membership
1327 def disapprove_member(request, membership):
1328     try:
1329         membership.disapprove()
1330         realname = membership.person.realname
1331         msg = astakos_messages.MEMBER_REMOVED % locals()
1332         messages.success(request, msg)
1333     except BaseException, e:
1334         logger.exception(e)
1335         msg = _(astakos_messages.GENERIC_ERROR)
1336         messages.error(request, msg)
1337
1338
1339 #@require_http_methods(["GET"])
1340 @require_http_methods(["POST", "GET"])
1341 @valid_astakos_user_required
1342 def resource_usage(request):
1343     def with_class(entry):
1344         entry['load_class'] = 'red'
1345         max_value = float(entry['maxValue'])
1346         curr_value = float(entry['currValue'])
1347         entry['ratio_limited']= 0
1348         if max_value > 0 :
1349             entry['ratio'] = (curr_value / max_value) * 100
1350         else:
1351             entry['ratio'] = 0
1352         if entry['ratio'] < 66:
1353             entry['load_class'] = 'yellow'
1354         if entry['ratio'] < 33:
1355             entry['load_class'] = 'green'
1356         if entry['ratio']<0:
1357             entry['ratio'] = 0
1358         if entry['ratio']>100:
1359             entry['ratio_limited'] = 100
1360         else:
1361             entry['ratio_limited'] = entry['ratio']
1362
1363         return entry
1364
1365     def pluralize(entry):
1366         entry['plural'] = engine.plural(entry.get('name'))
1367         return entry
1368
1369     result = callpoint.get_user_usage(request.user.id)
1370     if result.is_success:
1371         backenddata = map(with_class, result.data)
1372         data = map(pluralize, result.data)
1373     else:
1374         data = None
1375         messages.error(request, result.reason)
1376     resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1377     resource_catalog.update_from_result_report(result)
1378     return render_response('im/resource_usage.html',
1379                            data=data,
1380                            context_instance=get_context(request),
1381                            resource_catalog=resource_catalog,
1382                            result=result)
1383
1384
1385 def group_create_list(request):
1386     form = PickResourceForm()
1387     return render_response(
1388         template='im/astakosgroup_create_list.html',
1389         context_instance=get_context(request),)
1390
1391
1392 ##@require_http_methods(["GET"])
1393 #@require_http_methods(["POST", "GET"])
1394 #@signed_terms_required
1395 #@login_required
1396 #def billing(request):
1397 #
1398 #    today = datetime.today()
1399 #    month_last_day = calendar.monthrange(today.year, today.month)[1]
1400 #    start = request.POST.get('datefrom', None)
1401 #    if start:
1402 #        today = datetime.fromtimestamp(int(start))
1403 #        month_last_day = calendar.monthrange(today.year, today.month)[1]
1404 #
1405 #    start = datetime(today.year, today.month, 1).strftime("%s")
1406 #    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1407 #    r = request_billing.apply(args=('pgerakios@grnet.gr',
1408 #                                    int(start) * 1000,
1409 #                                    int(end) * 1000))
1410 #    data = {}
1411 #
1412 #    try:
1413 #        status, data = r.result
1414 #        data = _clear_billing_data(data)
1415 #        if status != 200:
1416 #            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1417 #    except:
1418 #        messages.error(request, r.result)
1419 #
1420 #    return render_response(
1421 #        template='im/billing.html',
1422 #        context_instance=get_context(request),
1423 #        data=data,
1424 #        zerodate=datetime(month=1, year=1970, day=1),
1425 #        today=today,
1426 #        start=int(start),
1427 #        month_last_day=month_last_day)
1428
1429
1430 #def _clear_billing_data(data):
1431 #
1432 #    # remove addcredits entries
1433 #    def isnotcredit(e):
1434 #        return e['serviceName'] != "addcredits"
1435 #
1436 #    # separate services
1437 #    def servicefilter(service_name):
1438 #        service = service_name
1439 #
1440 #        def fltr(e):
1441 #            return e['serviceName'] == service
1442 #        return fltr
1443 #
1444 #    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1445 #    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1446 #    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1447 #    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1448 #
1449 #    return data
1450
1451
1452 #@require_http_methods(["GET"])
1453 @require_http_methods(["POST", "GET"])
1454 @valid_astakos_user_required
1455 def timeline(request):
1456 #    data = {'entity':request.user.email}
1457     timeline_body = ()
1458     timeline_header = ()
1459 #    form = TimelineForm(data)
1460     form = TimelineForm()
1461     if request.method == 'POST':
1462         data = request.POST
1463         form = TimelineForm(data)
1464         if form.is_valid():
1465             data = form.cleaned_data
1466             timeline_header = ('entity', 'resource',
1467                                'event name', 'event date',
1468                                'incremental cost', 'total cost')
1469             timeline_body = timeline_charge(
1470                 data['entity'], data['resource'],
1471                 data['start_date'], data['end_date'],
1472                 data['details'], data['operation'])
1473
1474     return render_response(template='im/timeline.html',
1475                            context_instance=get_context(request),
1476                            form=form,
1477                            timeline_header=timeline_header,
1478                            timeline_body=timeline_body)
1479     return data
1480
1481
1482 # TODO: action only on POST and user should confirm the removal
1483 @require_http_methods(["GET", "POST"])
1484 @login_required
1485 @signed_terms_required
1486 def remove_auth_provider(request, pk):
1487     try:
1488         provider = request.user.auth_providers.get(pk=pk)
1489     except AstakosUserAuthProvider.DoesNotExist:
1490         raise Http404
1491
1492     if provider.can_remove():
1493         provider.delete()
1494         return HttpResponseRedirect(reverse('edit_profile'))
1495     else:
1496         raise PermissionDenied
1497
1498
1499 def how_it_works(request):
1500     return render_response(
1501         template='im/how_it_works.html',
1502         context_instance=get_context(request),)
1503
1504