diff --git a/userena/contrib/umessages/views.py b/userena/contrib/umessages/views.py index ca23b56b..a63db1bf 100644 --- a/userena/contrib/umessages/views.py +++ b/userena/contrib/umessages/views.py @@ -10,7 +10,7 @@ from userena.contrib.umessages.models import Message, MessageRecipient, MessageContact from userena.contrib.umessages.forms import ComposeForm -from userena.utils import get_datetime_now, get_user_model +from userena.utils import get_datetime_now, get_user_model, get_request_field from userena import settings as userena_settings @@ -115,8 +115,6 @@ def message_compose(request, recipients=None, compose_form=ComposeForm, if request.method == "POST": form = compose_form(request.POST) if form.is_valid(): - requested_redirect = request.REQUEST.get("next", False) - message = form.save(request.user) recipients = form.cleaned_data['to'] @@ -124,8 +122,7 @@ def message_compose(request, recipients=None, compose_form=ComposeForm, messages.success(request, _('Message is sent.'), fail_silently=True) - requested_redirect = request.REQUEST.get(REDIRECT_FIELD_NAME, - False) + requested_redirect = get_request_field(request, REDIRECT_FIELD_NAME, False) # Redirect mechanism redirect_to = reverse('userena_umessages_list') @@ -163,7 +160,7 @@ def message_remove(request, undo=False): """ message_pks = request.POST.getlist('message_pks') - redirect_to = request.REQUEST.get('next', False) + redirect_to = get_request_field(request, "next", False) if message_pks: # Check that all values are integers. diff --git a/userena/runtests/urls_change.py b/userena/runtests/urls_change.py new file mode 100644 index 00000000..013ef757 --- /dev/null +++ b/userena/runtests/urls_change.py @@ -0,0 +1,14 @@ +from django.conf.urls import * + +from userena import views + +PROFILE = [ + # View profiles + url(r'^(?P(?!signout|signup|signin)[\@\.\w-]+)/$', + views.profile_detail, + name='userena_profile_detail'), +] + +urlpatterns = patterns('', + url(r'^accounts/', include(PROFILE)), +) diff --git a/userena/tests/__init__.py b/userena/tests/__init__.py index 5e1e18ec..9a0a4cab 100644 --- a/userena/tests/__init__.py +++ b/userena/tests/__init__.py @@ -10,4 +10,5 @@ from .tests_middleware import * from .tests_models import * from .tests_utils import * - from .tests_views import * \ No newline at end of file + from .tests_views import * + from .tests_views_with_new_urls import * diff --git a/userena/tests/tests_views_with_new_urls.py b/userena/tests/tests_views_with_new_urls.py new file mode 100644 index 00000000..d4cb9658 --- /dev/null +++ b/userena/tests/tests_views_with_new_urls.py @@ -0,0 +1,31 @@ +import re + +from datetime import datetime, timedelta +from django.core.urlresolvers import reverse +from django.core import mail +from django.contrib.auth.forms import PasswordChangeForm +from django.test import TestCase + +try: + from django.test import override_settings +except Exception, e: + from django.test.utils import override_settings + +from userena import forms +from userena import settings as userena_settings +from userena.utils import get_user_model, get_user_profile + +User = get_user_model() + + +@override_settings(ROOT_URLCONF='urls_change') +class UserenaViewsTests(TestCase): + """ Test the account views """ + fixtures = ['users', 'profiles'] + + def test_profile_detail_view(self): + """ A ``GET`` to the detailed view of a user """ + response = self.client.get(reverse('userena_profile_detail', + kwargs={'username': 'john'})) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'userena/profile_detail.html') diff --git a/userena/urls.py b/userena/urls.py index e682cc2a..a4e06234 100644 --- a/userena/urls.py +++ b/userena/urls.py @@ -16,10 +16,10 @@ def merged_dict(dict_a, dict_b): urlpatterns = patterns('', # Signup, signin and signout url(r'^signup/$', - userena_views.signup, + userena_views.SignupView.as_view(), name='userena_signup'), url(r'^signin/$', - userena_views.signin, + userena_views.SigninView.as_view(), name='userena_signin'), url(r'^signout/$', userena_views.signout, @@ -67,7 +67,7 @@ def merged_dict(dict_a, dict_b): # Change email and confirm it url(r'^(?P[\@\.\w-]+)/email/$', - userena_views.email_change, + userena_views.EmailChangeView.as_view(), name='userena_email_change'), url(r'^(?P[\@\.\w-]+)/email/complete/$', userena_views.direct_to_user_template, @@ -89,7 +89,7 @@ def merged_dict(dict_a, dict_b): # Change password url(r'^(?P[\@\.\w-]+)/password/$', - userena_views.password_change, + userena_views.PasswordChangeView.as_view(), name='userena_password_change'), url(r'^(?P[\@\.\w-]+)/password/complete/$', userena_views.direct_to_user_template, @@ -98,12 +98,12 @@ def merged_dict(dict_a, dict_b): # Edit profile url(r'^(?P[\@\.\w-]+)/edit/$', - userena_views.profile_edit, + userena_views.ProfileEditView.as_view(), name='userena_profile_edit'), # View profiles url(r'^(?P(?!signout|signup|signin)[\@\.\w-]+)/$', - userena_views.profile_detail, + userena_views.ProfileDetailView.as_view(), name='userena_profile_detail'), url(r'^page/(?P[0-9]+)/$', userena_views.ProfileListView.as_view(), diff --git a/userena/utils.py b/userena/utils.py index a9d8c745..c50cc903 100644 --- a/userena/utils.py +++ b/userena/utils.py @@ -193,3 +193,30 @@ def get_datetime_now(): except ImportError: def get_user_model(): return get_model(*user_model_label.rsplit('.', 1)) + + +def get_request_field(request, name, default=None): + + """ + This method try to replace request.REQUEST because was depreced + in django 1.7 + + Try to get a field in http (GET, POST) methods + + :param request: + request. + + :param name: + String defining the name of the field to get. + + :param default: + Var which contains the default value to return when the field is not in + the request. Defaults to ``None`` + """ + + for method_name in ("GET", "POST"): + method = getattr(request, method_name) + field = method.get(name, None) + if field: + break + return field if field else default diff --git a/userena/views.py b/userena/views.py deleted file mode 100644 index 95b4e9b6..00000000 --- a/userena/views.py +++ /dev/null @@ -1,801 +0,0 @@ -from django.core.urlresolvers import reverse -from django.shortcuts import redirect, get_object_or_404 -from django.contrib.auth import authenticate, login, logout, REDIRECT_FIELD_NAME -from django.contrib.auth.forms import PasswordChangeForm -from django.contrib.auth.views import logout as Signout -from django.views.generic import TemplateView -from django.views.generic.list import ListView -from django.contrib import messages -from django.core.exceptions import PermissionDenied -from django.utils.translation import ugettext as _ -from django.http import Http404, HttpResponseRedirect - -from userena.forms import (SignupForm, SignupFormOnlyEmail, AuthenticationForm, - ChangeEmailForm, EditProfileForm) -from userena.models import UserenaSignup -from userena.decorators import secure_required -from userena.utils import (signin_redirect, get_profile_model, get_user_model, - get_user_profile) -from userena import signals as userena_signals -from userena import settings as userena_settings - -from guardian.decorators import permission_required_or_403 - -import warnings - -class ExtraContextTemplateView(TemplateView): - """ Add extra context to a simple template view """ - extra_context = None - - def get_context_data(self, *args, **kwargs): - context = super(ExtraContextTemplateView, self).get_context_data(*args, **kwargs) - if self.extra_context: - context.update(self.extra_context) - return context - - # this view is used in POST requests, e.g. signup when the form is not valid - post = TemplateView.get - -class ProfileListView(ListView): - """ Lists all profiles """ - context_object_name='profile_list' - page=1 - paginate_by=50 - template_name=userena_settings.USERENA_PROFILE_LIST_TEMPLATE - extra_context=None - - def get_context_data(self, **kwargs): - # Call the base implementation first to get a context - context = super(ProfileListView, self).get_context_data(**kwargs) - try: - page = int(self.request.GET.get('page', None)) - except (TypeError, ValueError): - page = self.page - - if userena_settings.USERENA_DISABLE_PROFILE_LIST \ - and not self.request.user.is_staff: - raise Http404 - - if not self.extra_context: self.extra_context = dict() - - context['page'] = page - context['paginate_by'] = self.paginate_by - context['extra_context'] = self.extra_context - - return context - - def get_queryset(self): - profile_model = get_profile_model() - queryset = profile_model.objects.get_visible_profiles(self.request.user).select_related() - return queryset - -@secure_required -def signup(request, signup_form=SignupForm, - template_name='userena/signup_form.html', success_url=None, - extra_context=None): - """ - Signup of an account. - - Signup requiring a username, email and password. After signup a user gets - an email with an activation link used to activate their account. After - successful signup redirects to ``success_url``. - - :param signup_form: - Form that will be used to sign a user. Defaults to userena's - :class:`SignupForm`. - - :param template_name: - String containing the template name that will be used to display the - signup form. Defaults to ``userena/signup_form.html``. - - :param success_url: - String containing the URI which should be redirected to after a - successful signup. If not supplied will redirect to - ``userena_signup_complete`` view. - - :param extra_context: - Dictionary containing variables which are added to the template - context. Defaults to a dictionary with a ``form`` key containing the - ``signup_form``. - - **Context** - - ``form`` - Form supplied by ``signup_form``. - - """ - # If signup is disabled, return 403 - if userena_settings.USERENA_DISABLE_SIGNUP: - raise PermissionDenied - - # If no usernames are wanted and the default form is used, fallback to the - # default form that doesn't display to enter the username. - if userena_settings.USERENA_WITHOUT_USERNAMES and (signup_form == SignupForm): - signup_form = SignupFormOnlyEmail - - form = signup_form() - - if request.method == 'POST': - form = signup_form(request.POST, request.FILES) - if form.is_valid(): - user = form.save() - - # Send the signup complete signal - userena_signals.signup_complete.send(sender=None, - user=user) - - - if success_url: redirect_to = success_url - else: redirect_to = reverse('userena_signup_complete', - kwargs={'username': user.username}) - - # A new signed user should logout the old one. - if request.user.is_authenticated(): - logout(request) - - if (userena_settings.USERENA_SIGNIN_AFTER_SIGNUP and - not userena_settings.USERENA_ACTIVATION_REQUIRED): - user = authenticate(identification=user.email, check_password=False) - login(request, user) - - return redirect(redirect_to) - - if not extra_context: extra_context = dict() - extra_context['form'] = form - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - -@secure_required -def activate(request, activation_key, - template_name='userena/activate_fail.html', - retry_template_name='userena/activate_retry.html', - success_url=None, extra_context=None): - """ - Activate a user with an activation key. - - The key is a SHA1 string. When the SHA1 is found with an - :class:`UserenaSignup`, the :class:`User` of that account will be - activated. After a successful activation the view will redirect to - ``success_url``. If the SHA1 is not found, the user will be shown the - ``template_name`` template displaying a fail message. - If the SHA1 is found but expired, ``retry_template_name`` is used instead, - so the user can proceed to :func:`activate_retry` to get a new activation key. - - :param activation_key: - String of a SHA1 string of 40 characters long. A SHA1 is always 160bit - long, with 4 bits per character this makes it --160/4-- 40 characters - long. - - :param template_name: - String containing the template name that is used when the - ``activation_key`` is invalid and the activation fails. Defaults to - ``userena/activate_fail.html``. - - :param retry_template_name: - String containing the template name that is used when the - ``activation_key`` is expired. Defaults to - ``userena/activate_retry.html``. - - :param success_url: - String containing the URL where the user should be redirected to after - a successful activation. Will replace ``%(username)s`` with string - formatting if supplied. If ``success_url`` is left empty, will direct - to ``userena_profile_detail`` view. - - :param extra_context: - Dictionary containing variables which could be added to the template - context. Default to an empty dictionary. - - """ - try: - if (not UserenaSignup.objects.check_expired_activation(activation_key) - or not userena_settings.USERENA_ACTIVATION_RETRY): - user = UserenaSignup.objects.activate_user(activation_key) - if user: - # Sign the user in. - auth_user = authenticate(identification=user.email, - check_password=False) - login(request, auth_user) - - if userena_settings.USERENA_USE_MESSAGES: - messages.success(request, _('Your account has been activated and you have been signed in.'), - fail_silently=True) - - if success_url: redirect_to = success_url % {'username': user.username } - else: redirect_to = reverse('userena_profile_detail', - kwargs={'username': user.username}) - return redirect(redirect_to) - else: - if not extra_context: extra_context = dict() - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)( - request) - else: - if not extra_context: extra_context = dict() - extra_context['activation_key'] = activation_key - return ExtraContextTemplateView.as_view(template_name=retry_template_name, - extra_context=extra_context)(request) - except UserenaSignup.DoesNotExist: - if not extra_context: extra_context = dict() - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - -@secure_required -def activate_retry(request, activation_key, - template_name='userena/activate_retry_success.html', - extra_context=None): - """ - Reissue a new ``activation_key`` for the user with the expired - ``activation_key``. - - If ``activation_key`` does not exists, or ``USERENA_ACTIVATION_RETRY`` is - set to False and for any other error condition user is redirected to - :func:`activate` for error message display. - - :param activation_key: - String of a SHA1 string of 40 characters long. A SHA1 is always 160bit - long, with 4 bits per character this makes it --160/4-- 40 characters - long. - - :param template_name: - String containing the template name that is used when new - ``activation_key`` has been created. Defaults to - ``userena/activate_retry_success.html``. - - :param extra_context: - Dictionary containing variables which could be added to the template - context. Default to an empty dictionary. - - """ - if not userena_settings.USERENA_ACTIVATION_RETRY: - return redirect(reverse('userena_activate', args=(activation_key,))) - try: - if UserenaSignup.objects.check_expired_activation(activation_key): - new_key = UserenaSignup.objects.reissue_activation(activation_key) - if new_key: - if not extra_context: extra_context = dict() - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - else: - return redirect(reverse('userena_activate',args=(activation_key,))) - else: - return redirect(reverse('userena_activate',args=(activation_key,))) - except UserenaSignup.DoesNotExist: - return redirect(reverse('userena_activate',args=(activation_key,))) - -@secure_required -def email_confirm(request, confirmation_key, - template_name='userena/email_confirm_fail.html', - success_url=None, extra_context=None): - """ - Confirms an email address with a confirmation key. - - Confirms a new email address by running :func:`User.objects.confirm_email` - method. If the method returns an :class:`User` the user will have his new - e-mail address set and redirected to ``success_url``. If no ``User`` is - returned the user will be represented with a fail message from - ``template_name``. - - :param confirmation_key: - String with a SHA1 representing the confirmation key used to verify a - new email address. - - :param template_name: - String containing the template name which should be rendered when - confirmation fails. When confirmation is successful, no template is - needed because the user will be redirected to ``success_url``. - - :param success_url: - String containing the URL which is redirected to after a successful - confirmation. Supplied argument must be able to be rendered by - ``reverse`` function. - - :param extra_context: - Dictionary of variables that are passed on to the template supplied by - ``template_name``. - - """ - user = UserenaSignup.objects.confirm_email(confirmation_key) - if user: - if userena_settings.USERENA_USE_MESSAGES: - messages.success(request, _('Your email address has been changed.'), - fail_silently=True) - - if success_url: redirect_to = success_url - else: redirect_to = reverse('userena_email_confirm_complete', - kwargs={'username': user.username}) - return redirect(redirect_to) - else: - if not extra_context: extra_context = dict() - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - -def direct_to_user_template(request, username, template_name, - extra_context=None): - """ - Simple wrapper for Django's :func:`direct_to_template` view. - - This view is used when you want to show a template to a specific user. A - wrapper for :func:`direct_to_template` where the template also has access to - the user that is found with ``username``. For ex. used after signup, - activation and confirmation of a new e-mail. - - :param username: - String defining the username of the user that made the action. - - :param template_name: - String defining the name of the template to use. Defaults to - ``userena/signup_complete.html``. - - **Keyword arguments** - - ``extra_context`` - A dictionary containing extra variables that should be passed to the - rendered template. The ``account`` key is always the ``User`` - that completed the action. - - **Extra context** - - ``viewed_user`` - The currently :class:`User` that is viewed. - - """ - user = get_object_or_404(get_user_model(), username__iexact=username) - - if not extra_context: extra_context = dict() - extra_context['viewed_user'] = user - extra_context['profile'] = get_user_profile(user=user) - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - -def disabled_account(request, username, template_name, extra_context=None): - """ - Checks if the account is disabled, if so, returns the disabled account template. - - :param username: - String defining the username of the user that made the action. - - :param template_name: - String defining the name of the template to use. Defaults to - ``userena/signup_complete.html``. - - **Keyword arguments** - - ``extra_context`` - A dictionary containing extra variables that should be passed to the - rendered template. The ``account`` key is always the ``User`` - that completed the action. - - **Extra context** - - ``viewed_user`` - The currently :class:`User` that is viewed. - - ``profile`` - Profile of the viewed user. - - """ - user = get_object_or_404(get_user_model(), username__iexact=username) - - if user.is_active: - raise Http404 - - if not extra_context: extra_context = dict() - extra_context['viewed_user'] = user - extra_context['profile'] = get_user_profile(user=user) - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - -@secure_required -def signin(request, auth_form=AuthenticationForm, - template_name='userena/signin_form.html', - redirect_field_name=REDIRECT_FIELD_NAME, - redirect_signin_function=signin_redirect, extra_context=None): - """ - Signin using email or username with password. - - Signs a user in by combining email/username with password. If the - combination is correct and the user :func:`is_active` the - :func:`redirect_signin_function` is called with the arguments - ``REDIRECT_FIELD_NAME`` and an instance of the :class:`User` who is is - trying the login. The returned value of the function will be the URL that - is redirected to. - - A user can also select to be remembered for ``USERENA_REMEMBER_DAYS``. - - :param auth_form: - Form to use for signing the user in. Defaults to the - :class:`AuthenticationForm` supplied by userena. - - :param template_name: - String defining the name of the template to use. Defaults to - ``userena/signin_form.html``. - - :param redirect_field_name: - Form field name which contains the value for a redirect to the - succeeding page. Defaults to ``next`` and is set in - ``REDIRECT_FIELD_NAME`` setting. - - :param redirect_signin_function: - Function which handles the redirect. This functions gets the value of - ``REDIRECT_FIELD_NAME`` and the :class:`User` who has logged in. It - must return a string which specifies the URI to redirect to. - - :param extra_context: - A dictionary containing extra variables that should be passed to the - rendered template. The ``form`` key is always the ``auth_form``. - - **Context** - - ``form`` - Form used for authentication supplied by ``auth_form``. - - """ - form = auth_form() - - if request.method == 'POST': - form = auth_form(request.POST, request.FILES) - if form.is_valid(): - identification, password, remember_me = (form.cleaned_data['identification'], - form.cleaned_data['password'], - form.cleaned_data['remember_me']) - user = authenticate(identification=identification, - password=password) - if user.is_active: - login(request, user) - if remember_me: - request.session.set_expiry(userena_settings.USERENA_REMEMBER_ME_DAYS[1] * 86400) - else: request.session.set_expiry(0) - - if userena_settings.USERENA_USE_MESSAGES: - messages.success(request, _('You have been signed in.'), - fail_silently=True) - - #send a signal that a user has signed in - userena_signals.account_signin.send(sender=None, user=user) - # Whereto now? - redirect_to = redirect_signin_function( - request.REQUEST.get(redirect_field_name), user) - return HttpResponseRedirect(redirect_to) - else: - return redirect(reverse('userena_disabled', - kwargs={'username': user.username})) - - if not extra_context: extra_context = dict() - extra_context.update({ - 'form': form, - 'next': request.REQUEST.get(redirect_field_name), - }) - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - -@secure_required -def signout(request, next_page=userena_settings.USERENA_REDIRECT_ON_SIGNOUT, - template_name='userena/signout.html', *args, **kwargs): - """ - Signs out the user and adds a success message ``You have been signed - out.`` If next_page is defined you will be redirected to the URI. If - not the template in template_name is used. - - :param next_page: - A string which specifies the URI to redirect to. - - :param template_name: - String defining the name of the template to use. Defaults to - ``userena/signout.html``. - - """ - if request.user.is_authenticated() and userena_settings.USERENA_USE_MESSAGES: # pragma: no cover - messages.success(request, _('You have been signed out.'), fail_silently=True) - userena_signals.account_signout.send(sender=None, user=request.user) - return Signout(request, next_page, template_name, *args, **kwargs) - -@secure_required -@permission_required_or_403('change_user', (get_user_model(), 'username', 'username')) -def email_change(request, username, email_form=ChangeEmailForm, - template_name='userena/email_form.html', success_url=None, - extra_context=None): - """ - Change email address - - :param username: - String of the username which specifies the current account. - - :param email_form: - Form that will be used to change the email address. Defaults to - :class:`ChangeEmailForm` supplied by userena. - - :param template_name: - String containing the template to be used to display the email form. - Defaults to ``userena/email_form.html``. - - :param success_url: - Named URL where the user will get redirected to when successfully - changing their email address. When not supplied will redirect to - ``userena_email_complete`` URL. - - :param extra_context: - Dictionary containing extra variables that can be used to render the - template. The ``form`` key is always the form supplied by the keyword - argument ``form`` and the ``user`` key by the user whose email address - is being changed. - - **Context** - - ``form`` - Form that is used to change the email address supplied by ``form``. - - ``account`` - Instance of the ``Account`` whose email address is about to be changed. - - **Todo** - - Need to have per-object permissions, which enables users with the correct - permissions to alter the email address of others. - - """ - user = get_object_or_404(get_user_model(), username__iexact=username) - prev_email = user.email - form = email_form(user) - - if request.method == 'POST': - form = email_form(user, request.POST, request.FILES) - - if form.is_valid(): - form.save() - - if success_url: - # Send a signal that the email has changed - userena_signals.email_change.send(sender=None, - user=user, - prev_email=prev_email, - new_email=user.email) - redirect_to = success_url - else: redirect_to = reverse('userena_email_change_complete', - kwargs={'username': user.username}) - return redirect(redirect_to) - - if not extra_context: extra_context = dict() - extra_context['form'] = form - extra_context['profile'] = get_user_profile(user=user) - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - -@secure_required -@permission_required_or_403('change_user', (get_user_model(), 'username', 'username')) -def password_change(request, username, template_name='userena/password_form.html', - pass_form=PasswordChangeForm, success_url=None, extra_context=None): - """ Change password of user. - - This view is almost a mirror of the view supplied in - :func:`contrib.auth.views.password_change`, with the minor change that in - this view we also use the username to change the password. This was needed - to keep our URLs logical (and REST) across the entire application. And - that in a later stadium administrators can also change the users password - through the web application itself. - - :param username: - String supplying the username of the user who's password is about to be - changed. - - :param template_name: - String of the name of the template that is used to display the password - change form. Defaults to ``userena/password_form.html``. - - :param pass_form: - Form used to change password. Default is the form supplied by Django - itself named ``PasswordChangeForm``. - - :param success_url: - Named URL that is passed onto a :func:`reverse` function with - ``username`` of the active user. Defaults to the - ``userena_password_complete`` URL. - - :param extra_context: - Dictionary of extra variables that are passed on to the template. The - ``form`` key is always used by the form supplied by ``pass_form``. - - **Context** - - ``form`` - Form used to change the password. - - """ - user = get_object_or_404(get_user_model(), - username__iexact=username) - - form = pass_form(user=user) - - if request.method == "POST": - form = pass_form(user=user, data=request.POST) - if form.is_valid(): - form.save() - - # Send a signal that the password has changed - userena_signals.password_complete.send(sender=None, - user=user) - - if success_url: redirect_to = success_url - else: redirect_to = reverse('userena_password_change_complete', - kwargs={'username': user.username}) - return redirect(redirect_to) - - if not extra_context: extra_context = dict() - extra_context['form'] = form - extra_context['profile'] = get_user_profile(user=user) - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) -@secure_required -@permission_required_or_403('change_profile', (get_profile_model(), 'user__username', 'username')) -def profile_edit(request, username, edit_profile_form=EditProfileForm, - template_name='userena/profile_form.html', success_url=None, - extra_context=None, **kwargs): - """ - Edit profile. - - Edits a profile selected by the supplied username. First checks - permissions if the user is allowed to edit this profile, if denied will - show a 404. When the profile is successfully edited will redirect to - ``success_url``. - - :param username: - Username of the user which profile should be edited. - - :param edit_profile_form: - - Form that is used to edit the profile. The :func:`EditProfileForm.save` - method of this form will be called when the form - :func:`EditProfileForm.is_valid`. Defaults to :class:`EditProfileForm` - from userena. - - :param template_name: - String of the template that is used to render this view. Defaults to - ``userena/edit_profile_form.html``. - - :param success_url: - Named URL which will be passed on to a django ``reverse`` function after - the form is successfully saved. Defaults to the ``userena_detail`` url. - - :param extra_context: - Dictionary containing variables that are passed on to the - ``template_name`` template. ``form`` key will always be the form used - to edit the profile, and the ``profile`` key is always the edited - profile. - - **Context** - - ``form`` - Form that is used to alter the profile. - - ``profile`` - Instance of the ``Profile`` that is edited. - - """ - user = get_object_or_404(get_user_model(), username__iexact=username) - - profile = get_user_profile(user=user) - - user_initial = {'first_name': user.first_name, - 'last_name': user.last_name} - - form = edit_profile_form(instance=profile, initial=user_initial) - - if request.method == 'POST': - form = edit_profile_form(request.POST, request.FILES, instance=profile, - initial=user_initial) - - if form.is_valid(): - profile = form.save() - - if userena_settings.USERENA_USE_MESSAGES: - messages.success(request, _('Your profile has been updated.'), - fail_silently=True) - - if success_url: - # Send a signal that the profile has changed - userena_signals.profile_change.send(sender=None, - user=user) - redirect_to = success_url - else: redirect_to = reverse('userena_profile_detail', kwargs={'username': username}) - return redirect(redirect_to) - - if not extra_context: extra_context = dict() - extra_context['form'] = form - extra_context['profile'] = profile - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) -def profile_detail(request, username, - template_name=userena_settings.USERENA_PROFILE_DETAIL_TEMPLATE, - extra_context=None, **kwargs): - """ - Detailed view of an user. - - :param username: - String of the username of which the profile should be viewed. - - :param template_name: - String representing the template name that should be used to display - the profile. - - :param extra_context: - Dictionary of variables which should be supplied to the template. The - ``profile`` key is always the current profile. - - **Context** - - ``profile`` - Instance of the currently viewed ``Profile``. - - """ - user = get_object_or_404(get_user_model(), username__iexact=username) - profile = get_user_profile(user=user) - if not profile.can_view_profile(request.user): - raise PermissionDenied - if not extra_context: extra_context = dict() - extra_context['profile'] = profile - extra_context['hide_email'] = userena_settings.USERENA_HIDE_EMAIL - return ExtraContextTemplateView.as_view(template_name=template_name, - extra_context=extra_context)(request) - -def profile_list(request, page=1, template_name='userena/profile_list.html', - paginate_by=50, extra_context=None, **kwargs): # pragma: no cover - """ - Returns a list of all profiles that are public. - - It's possible to disable this by changing ``USERENA_DISABLE_PROFILE_LIST`` - to ``True`` in your settings. - - :param page: - Integer of the active page used for pagination. Defaults to the first - page. - - :param template_name: - String defining the name of the template that is used to render the - list of all users. Defaults to ``userena/list.html``. - - :param paginate_by: - Integer defining the amount of displayed profiles per page. Defaults to - 50 profiles per page. - - :param extra_context: - Dictionary of variables that are passed on to the ``template_name`` - template. - - **Context** - - ``profile_list`` - A list of profiles. - - ``is_paginated`` - A boolean representing whether the results are paginated. - - If the result is paginated. It will also contain the following variables. - - ``paginator`` - An instance of ``django.core.paginator.Paginator``. - - ``page_obj`` - An instance of ``django.core.paginator.Page``. - - """ - warnings.warn("views.profile_list is deprecated. Use ProfileListView instead", DeprecationWarning, stacklevel=2) - - try: - page = int(request.GET.get('page', None)) - except (TypeError, ValueError): - page = page - - if userena_settings.USERENA_DISABLE_PROFILE_LIST \ - and not request.user.is_staff: - raise Http404 - - profile_model = get_profile_model() - queryset = profile_model.objects.get_visible_profiles(request.user) - - if not extra_context: extra_context = dict() - return ProfileListView.as_view(queryset=queryset, - paginate_by=paginate_by, - page=page, - template_name=template_name, - extra_context=extra_context, - **kwargs)(request) diff --git a/userena/views/__init__.py b/userena/views/__init__.py new file mode 100644 index 00000000..f6395760 --- /dev/null +++ b/userena/views/__init__.py @@ -0,0 +1,8 @@ +from .email import EmailChangeView +from .password import PasswordChangeView +from .profile import ProfileDetailView, ProfileListView, ProfileEditView +from .profile import profile_detail +from .signup import SignupView +from .signin import SigninView +from .views import signout, activate, activate_retry, direct_to_user_template +from .views import email_confirm, disabled_account diff --git a/userena/views/email.py b/userena/views/email.py new file mode 100644 index 00000000..1a905597 --- /dev/null +++ b/userena/views/email.py @@ -0,0 +1,100 @@ +from django.core.urlresolvers import reverse +from django.utils.decorators import method_decorator +from django.views.generic import UpdateView + +from guardian.decorators import permission_required_or_403 + +from userena.decorators import secure_required +from userena.forms import ChangeEmailForm +from userena.utils import get_user_model +from userena import signals as userena_signals + +from .mixins import ExtraContextMixin + + +class EmailChangeView(ExtraContextMixin, UpdateView): + + """ + Change email address + + :param username: + String of the username which specifies the current account. + + :param class_form: + Form that will be used to change the email address. Defaults to + :class:`ChangeEmailForm` supplied by userena. + + :param template_name: + String containing the template to be used to display the email form. + Defaults to ``userena/email_form.html``. + + :param success_url: + Named URL where the user will get redirected to when successfully + changing their email address. When not supplied will redirect to + ``userena_email_complete`` URL. + + :param extra_context: + Dictionary containing extra variables that can be used to render the + template. The ``form`` key is always the form supplied by the keyword + argument ``form`` and the ``user`` key by the user whose email address + is being changed. + + **Context** + + ``form`` + Form that is used to change the email address supplied by ``form``. + + ``account`` + Instance of the ``Account`` whose email address is about to be changed. + + **Todo** + + Need to have per-object permissions, which enables users with the correct + permissions to alter the email address of others. + + """ + + model = get_user_model() + form_class = ChangeEmailForm + slug_field = "username__iexact" + slug_url_kwarg = 'username' + success_url = None + template_name = 'userena/email_form.html' + + @method_decorator(secure_required) + @method_decorator(permission_required_or_403('change_user', + (get_user_model(), + 'username', + 'username'))) + def dispatch(self, request, *args, **kwargs): + return super(EmailChangeView, self).dispatch(request, *args, **kwargs) + + def get_object(self, queryset=None): + obj = super(EmailChangeView, self).get_object() + self.prev_email = obj.email + return obj + + def get_form_kwargs(self): + """ + Returns the keyword arguments for instantiating the form. + """ + kwargs = super(EmailChangeView, self).get_form_kwargs() + if hasattr(self, 'object'): + self._user = kwargs.pop("instance") + kwargs.update({'user': self.object}) + return kwargs + + def get_success_url(self): + """ + Returns the supplied URL. + """ + if self.success_url: + userena_signals.email_change.send(sender=None, + user=self._user, + prev_email=self.prev_email, + new_email=self._user.email) + url = self.success_url + else: + url = reverse('userena_email_change_complete', + kwargs={'username': self._user.username}) + return url diff --git a/userena/views/mixins.py b/userena/views/mixins.py new file mode 100644 index 00000000..c58c2353 --- /dev/null +++ b/userena/views/mixins.py @@ -0,0 +1,18 @@ + + +class ExtraContextMixin(object): + + """ Add extra context to a View + :param extra_context: + Dictionary containing variables which are added to the template + context. + """ + + + extra_context = None + + def get_context_data(self, *args, **kwargs): + context = super(ExtraContextMixin, self).get_context_data(*args, **kwargs) + if self.extra_context: + context.update(self.extra_context) + return context diff --git a/userena/views/password.py b/userena/views/password.py new file mode 100644 index 00000000..07a3cb3b --- /dev/null +++ b/userena/views/password.py @@ -0,0 +1,92 @@ +from django.contrib.auth.forms import PasswordChangeForm +from django.core.urlresolvers import reverse +from django.utils.decorators import method_decorator +from django.views.generic import UpdateView + +from guardian.decorators import permission_required_or_403 + +from userena.decorators import secure_required +from userena.utils import (get_user_model) +from userena import signals as userena_signals + +from .mixins import ExtraContextMixin + + +class PasswordChangeView(ExtraContextMixin, UpdateView): + + """ Change password of user. + + This view is almost a mirror of the view supplied in + :func:`contrib.auth.views.password_change`, with the minor change that in + this view we also use the username to change the password. This was needed + to keep our URLs logical (and REST) across the entire application. And + that in a later stadium administrators can also change the users password + through the web application itself. + + :param username: + String supplying the username of the user who's password is about to be + changed. + + :param template_name: + String of the name of the template that is used to display the password + change form. Defaults to ``userena/password_form.html``. + + :param class_form: + Form used to change password. Default is the form supplied by Django + itself named ``PasswordChangeForm``. + + :param success_url: + Named URL that is passed onto a :func:`reverse` function with + ``username`` of the active user. Defaults to the + ``userena_password_complete`` URL. + + :param extra_context: + Dictionary of extra variables that are passed on to the template. The + ``form`` key is always used by the form supplied by ``pass_form``. + + **Context** + + ``form`` + Form used to change the password. + + """ + + model = get_user_model() + form_class = PasswordChangeForm + slug_url_kwarg = 'username' + slug_field = "username__iexact" + success_url = None + template_name = 'userena/password_form.html' + + @method_decorator(secure_required) + @method_decorator(permission_required_or_403('change_user', (get_user_model(), 'username', 'username'))) + def dispatch(self, request, *args, **kwargs): + return super(PasswordChangeView, self).dispatch(request, *args, **kwargs) + + def get_form_kwargs(self): + """ + Returns the keyword arguments for instantiating the form. + """ + kwargs = super(PasswordChangeView, self).get_form_kwargs() + if hasattr(self, 'object'): + self._user = kwargs.pop("instance") + kwargs.update({'user': self.object}) + return kwargs + + def get_success_url(self): + """ + Returns the supplied URL. + """ + if self.success_url: + # Send a signal that the profile has changed + return self.success_url + else: + return reverse('userena_password_change_complete', kwargs={'username': self._user.username}) + + def form_valid(self, form): + """ + If the form is valid, save the associated model. + """ + form_valid = super(PasswordChangeView, self).form_valid(form) + userena_signals.password_complete.send(sender=None, user=self.object) + return form_valid diff --git a/userena/views/profile.py b/userena/views/profile.py new file mode 100644 index 00000000..eaf0a629 --- /dev/null +++ b/userena/views/profile.py @@ -0,0 +1,207 @@ +from django.contrib import messages +from django.core.exceptions import PermissionDenied +from django.core.urlresolvers import reverse +from django.http import Http404 +from django.utils.decorators import method_decorator +from django.utils.translation import ugettext as _ +from django.views.generic import DetailView, ListView, UpdateView + +from guardian.decorators import permission_required_or_403 + +from userena.decorators import secure_required +from userena.forms import EditProfileForm +from userena.utils import (get_profile_model, get_user_model, get_user_profile) +from userena import signals as userena_signals +from userena import settings as userena_settings + +from .mixins import ExtraContextMixin + + +PROFILE_TEMPLATE = userena_settings.USERENA_PROFILE_DETAIL_TEMPLATE + + +class ProfileDetailView(ExtraContextMixin, DetailView): + + """ + Detailed view of an user. + + :param username: + String of the username of which the profile should be viewed. + + :param template_name: + String representing the template name that should be used to display + the profile. + + :param extra_context: + Dictionary of variables which should be supplied to the template. The + ``profile`` key is always the current profile. + + **Context** + + ``profile`` + Instance of the currently viewed ``Profile``. + + """ + + context_object_name = "profile" + extra_context = {'hide_email': userena_settings.USERENA_HIDE_EMAIL} + model = get_user_model() + template_name = PROFILE_TEMPLATE + slug_url_kwarg = 'username' + slug_field = "username__iexact" + + def get_object(self, queryset=None): + obj = super(ProfileDetailView, self).get_object() + profile = get_user_profile(user=obj) + if not profile.can_view_profile(self.request.user): + raise PermissionDenied + return profile + + +class ProfileEditView(ExtraContextMixin, UpdateView): + + """ + Edit profile. + + Edits a profile selected by the supplied username. First checks + permissions if the user is allowed to edit this profile, if denied will + show a 404. When the profile is successfully edited will redirect to + ``success_url``. + + :param username: + Username of the user which profile should be edited. + + :param form_class: + + Form that is used to edit the profile. The :func:`EditProfileForm.save` + method of this form will be called when the form + :func:`EditProfileForm.is_valid`. Defaults to :class:`EditProfileForm` + from userena. + + :param template_name: + String of the template that is used to render this view. Defaults to + ``userena/edit_profile_form.html``. + + :param success_url: + Named URL which will be passed on to a django ``reverse`` function after + the form is successfully saved. Defaults to the ``userena_detail`` url. + + :param extra_context: + Dictionary containing variables that are passed on to the + ``template_name`` template. ``form`` key will always be the form used + to edit the profile, and the ``profile`` key is always the edited + profile. + + **Context** + + ``form`` + Form that is used to alter the profile. + + ``profile`` + Instance of the ``Profile`` that is edited. + + """ + + form_class = EditProfileForm + model = get_user_model() + slug_url_kwarg = 'username' + slug_field = "username__iexact" + success_url = None + template_name = 'userena/profile_form.html' + extra_context = None + + @method_decorator(secure_required) + @method_decorator(permission_required_or_403('change_profile', (get_profile_model(), 'user__username', 'username'))) + def dispatch(self, request, *args, **kwargs): + return super(ProfileEditView, self).dispatch(request, *args, **kwargs) + + def get_initial(self): + """ + Returns the initial data to use for forms on this view. + """ + initial = super(ProfileEditView, self).get_initial() + initial.update({'first_name': self._user.first_name, 'last_name': self._user.last_name}) + return initial + + def get_object(self, queryset=None): + obj = super(ProfileEditView, self).get_object() + profile = get_user_profile(user=obj) + self._user = obj + return profile + + def get_success_url(self): + """ + Returns the supplied URL. + """ + if self.success_url: + # Send a signal that the profile has changed + userena_signals.profile_change.send(sender=None, user=self._user) + return self.success_url + else: + return reverse('userena_profile_detail', kwargs={'username': self._user.username}) + + def form_valid(self, form): + """ + If the form is valid, save the associated model. + """ + form_valid = super(ProfileEditView, self).form_valid(form) + if userena_settings.USERENA_USE_MESSAGES: + messages.success(self.request, _('Your profile has been updated.'), fail_silently=True) + return form_valid + + +class ProfileListView(ExtraContextMixin, ListView): + """ Lists all profiles """ + context_object_name = 'profile_list' + paginate_by = 50 + template_name = userena_settings.USERENA_PROFILE_LIST_TEMPLATE + + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + if userena_settings.USERENA_DISABLE_PROFILE_LIST and not self.request.user.is_staff: + raise Http404 + context = super(ProfileListView, self).get_context_data(**kwargs) + return context + + def get_queryset(self): + model = get_profile_model() + queryset = model.objects.get_visible_profiles(self.request.user) + queryset = queryset.select_related() + return queryset + + +def profile_detail(request, + template_name=PROFILE_TEMPLATE, + extra_context=None, + url_field='username', + field_method="iexact", + **kwargs): + """ + Detailed view of an user. + + :param template_name: + String representing the template name that should be used to display + the profile. + + :param extra_context: + Dictionary of variables which should be supplied to the template. The + ``profile`` key is always the current profile. + + :param url_field: + Field in model for view search the profile. + + :param url_field: + String that indicate the method for search in the model, must be exact, + like iexact, and must be a QuerySet method. + + **Context** + + ``profile`` + Instance of the currently viewed ``Profile``. + """ + + slug_field = "{0}__{1}".format(url_field, field_method) + return ProfileDetailView.as_view(template_name=template_name, + extra_context=extra_context, + slug_url_kwarg=url_field, + slug_field=slug_field)(request, **kwargs) diff --git a/userena/views/signin.py b/userena/views/signin.py new file mode 100644 index 00000000..eebf6b18 --- /dev/null +++ b/userena/views/signin.py @@ -0,0 +1,110 @@ +from django.contrib import messages +from django.contrib.auth import authenticate, login, REDIRECT_FIELD_NAME +from django.core.urlresolvers import reverse +from django.views.generic import FormView +from django.utils.decorators import method_decorator +from django.utils.translation import ugettext as _ + +from userena.decorators import secure_required +from userena.forms import (AuthenticationForm) +from userena.utils import (signin_redirect, get_request_field) +from userena import signals as userena_signals +from userena import settings as userena_settings + +from .mixins import ExtraContextMixin + + +class SigninView(ExtraContextMixin, FormView): + + """ + Signin using email or username with password. + + Signs a user in by combining email/username with password. If the + combination is correct and the user :func:`is_active` the + :func:`redirect_signin_function` is called with the arguments + ``REDIRECT_FIELD_NAME`` and an instance of the :class:`User` who is is + trying the login. The returned value of the function will be the URL that + is redirected to. + + A user can also select to be remembered for ``USERENA_REMEMBER_DAYS``. + + :param form_class: + Form to use for signing the user in. Defaults to the + :class:`AuthenticationForm` supplied by userena. + + :param template_name: + String defining the name of the template to use. Defaults to + ``userena/signin_form.html``. + + :param redirect_field_name: + Form field name which contains the value for a redirect to the + succeeding page. Defaults to ``next`` and is set in + ``REDIRECT_FIELD_NAME`` setting. + + :param redirect_signin_function: + Function which handles the redirect. This functions gets the value of + ``REDIRECT_FIELD_NAME`` and the :class:`User` who has logged in. It + must return a string which specifies the URI to redirect to. + + :param extra_context: + A dictionary containing extra variables that should be passed to the + rendered template. The ``form`` key is always the ``auth_form``. + + **Context** + + ``form`` + Form used for authentication supplied by ``auth_form``. + + """ + + extra_context = None + form_class = AuthenticationForm + redirect_field_name = REDIRECT_FIELD_NAME + redirect_signin_function = signin_redirect + template_name = 'userena/signin_form.html' + + @method_decorator(secure_required) + def dispatch(self, request, *args, **kwargs): + return super(SigninView, self).dispatch(request, *args, **kwargs) + + def form_valid(self, form): + """ + If the form is valid, save the associated model. + """ + identification, password, remember_me = (form.cleaned_data['identification'], + form.cleaned_data['password'], + form.cleaned_data['remember_me']) + user = authenticate(identification=identification, password=password) + self.object = user + if user.is_active: + login(self.request, user) + expiry = 0 if not remember_me else userena_settings.USERENA_REMEMBER_ME_DAYS[1] * 86400 + self.request.session.set_expiry(expiry) + + if userena_settings.USERENA_USE_MESSAGES: + messages.success(self.request, _('You have been signed in.'), fail_silently=True) + + # send a signal that a user has signed in + userena_signals.account_signin.send(sender=None, user=user) + + return super(SigninView, self).form_valid(form) + + def get_context_data(self, *args, **kwargs): + context = super(SigninView, self).get_context_data(*args, **kwargs) + context.update({ + 'next': self.redirect_field(), + }) + return context + + def get_success_url(self): + """ + Returns the supplied URL. + """ + if self.object.is_active: + return signin_redirect(redirect=self.redirect_field(), + user=self.object) + else: + return reverse('userena_disabled', kwargs={'username': self.object.username}) + + def redirect_field(self): + return get_request_field(self.request, self.redirect_field_name) diff --git a/userena/views/signup.py b/userena/views/signup.py new file mode 100644 index 00000000..30b66f42 --- /dev/null +++ b/userena/views/signup.py @@ -0,0 +1,98 @@ +from django.contrib.auth import logout, authenticate, login +from django.core.exceptions import PermissionDenied +from django.core.urlresolvers import reverse +from django.utils.decorators import method_decorator +from django.views.generic import FormView + +from userena.decorators import secure_required +from userena.forms import SignupForm, SignupFormOnlyEmail +from userena import signals as userena_signals +from userena import settings as userena_settings + +from .mixins import ExtraContextMixin + + +class SignupView(ExtraContextMixin, FormView): + + """ + Signup of an account. + + Signup requiring a username, email and password. After signup a user gets + an email with an activation link used to activate their account. After + successful signup redirects to ``success_url``. + + :param form_class: + Form that will be used to sign a user. Defaults to userena's + :class:`SignupForm`. + + :param template_name: + String containing the template name that will be used to display the + signup form. Defaults to ``userena/signup_form.html``. + + :param success_url: + String containing the URI which should be redirected to after a + successful signup. If not supplied will redirect to + ``userena_signup_complete`` view. + + :param extra_context: + Dictionary containing variables which are added to the template + context. Defaults to a dictionary with a ``form`` key containing the + ``signup_form``. + + **Context** + + ``form`` + Form supplied by ``form_class``. + + """ + + template_name = 'userena/signup_form.html' + form_class = SignupForm + + @method_decorator(secure_required) + def dispatch(self, request, *args, **kwargs): + + # If signup is disabled, return 403 + if userena_settings.USERENA_DISABLE_SIGNUP: + raise PermissionDenied + return super(SignupView, self).dispatch(request, *args, **kwargs) + + def form_valid(self, form): + """ + If the form is valid, save the associated model. + """ + # Send the signup complete signal + self.object = form.save() + userena_signals.signup_complete.send(sender=None, + user=self.object) + + # A new signed user should logout the old one. + if self.request.user.is_authenticated(): + logout(self.request) + + if (userena_settings.USERENA_SIGNIN_AFTER_SIGNUP and + not userena_settings.USERENA_ACTIVATION_REQUIRED): + user = authenticate(identification=self.object.email, check_password=False) + login(self.request, user) + + return super(SignupView, self).form_valid(form) + + def get_form_class(self): + """ + Returns the form class to use in this view + """ + # If no usernames are wanted and the default form is used, fallback to the + # default form that doesn't display to enter the username. + if userena_settings.USERENA_WITHOUT_USERNAMES and (self.form_class == SignupForm): + self.form_class = SignupFormOnlyEmail + return self.form_class + + def get_success_url(self): + """ + Returns the supplied URL. + """ + if self.success_url: + url = self.success_url + else: + url = reverse('userena_signup_complete', kwargs={'username': self.object.username}) + return url diff --git a/userena/views/views.py b/userena/views/views.py new file mode 100644 index 00000000..a6618575 --- /dev/null +++ b/userena/views/views.py @@ -0,0 +1,285 @@ +from django.contrib import messages +from django.contrib.auth import authenticate, login +from django.contrib.auth.views import logout as Signout +from django.core.urlresolvers import reverse +from django.http import Http404 +from django.shortcuts import redirect, get_object_or_404 +from django.views.generic import TemplateView +from django.utils.translation import ugettext as _ + +from userena.models import UserenaSignup +from userena.decorators import secure_required +from userena.utils import (get_user_model, get_user_profile) +from userena import signals as userena_signals +from userena import settings as userena_settings + +from .mixins import ExtraContextMixin + + +class ExtraContextTemplateView(ExtraContextMixin, TemplateView): + + # this view is used in POST requests, e.g. signup when the form is not valid + post = TemplateView.get + + +@secure_required +def activate(request, activation_key, + template_name='userena/activate_fail.html', + retry_template_name='userena/activate_retry.html', + success_url=None, extra_context=None): + """ + Activate a user with an activation key. + + The key is a SHA1 string. When the SHA1 is found with an + :class:`UserenaSignup`, the :class:`User` of that account will be + activated. After a successful activation the view will redirect to + ``success_url``. If the SHA1 is not found, the user will be shown the + ``template_name`` template displaying a fail message. + If the SHA1 is found but expired, ``retry_template_name`` is used instead, + so the user can proceed to :func:`activate_retry` to get a new activation key. + + :param activation_key: + String of a SHA1 string of 40 characters long. A SHA1 is always 160bit + long, with 4 bits per character this makes it --160/4-- 40 characters + long. + + :param template_name: + String containing the template name that is used when the + ``activation_key`` is invalid and the activation fails. Defaults to + ``userena/activate_fail.html``. + + :param retry_template_name: + String containing the template name that is used when the + ``activation_key`` is expired. Defaults to + ``userena/activate_retry.html``. + + :param success_url: + String containing the URL where the user should be redirected to after + a successful activation. Will replace ``%(username)s`` with string + formatting if supplied. If ``success_url`` is left empty, will direct + to ``userena_profile_detail`` view. + + :param extra_context: + Dictionary containing variables which could be added to the template + context. Default to an empty dictionary. + + """ + try: + if (not UserenaSignup.objects.check_expired_activation(activation_key) + or not userena_settings.USERENA_ACTIVATION_RETRY): + user = UserenaSignup.objects.activate_user(activation_key) + if user: + # Sign the user in. + auth_user = authenticate(identification=user.email, + check_password=False) + login(request, auth_user) + + if userena_settings.USERENA_USE_MESSAGES: + messages.success(request, _('Your account has been activated and you have been signed in.'), + fail_silently=True) + + if success_url: redirect_to = success_url % {'username': user.username } + else: redirect_to = reverse('userena_profile_detail', + kwargs={'username': user.username}) + return redirect(redirect_to) + else: + if not extra_context: extra_context = dict() + return ExtraContextTemplateView.as_view(template_name=template_name, + extra_context=extra_context)( + request) + else: + if not extra_context: extra_context = dict() + extra_context['activation_key'] = activation_key + return ExtraContextTemplateView.as_view(template_name=retry_template_name, + extra_context=extra_context)(request) + except UserenaSignup.DoesNotExist: + if not extra_context: extra_context = dict() + return ExtraContextTemplateView.as_view(template_name=template_name, + extra_context=extra_context)(request) + +@secure_required +def activate_retry(request, activation_key, + template_name='userena/activate_retry_success.html', + extra_context=None): + """ + Reissue a new ``activation_key`` for the user with the expired + ``activation_key``. + + If ``activation_key`` does not exists, or ``USERENA_ACTIVATION_RETRY`` is + set to False and for any other error condition user is redirected to + :func:`activate` for error message display. + + :param activation_key: + String of a SHA1 string of 40 characters long. A SHA1 is always 160bit + long, with 4 bits per character this makes it --160/4-- 40 characters + long. + + :param template_name: + String containing the template name that is used when new + ``activation_key`` has been created. Defaults to + ``userena/activate_retry_success.html``. + + :param extra_context: + Dictionary containing variables which could be added to the template + context. Default to an empty dictionary. + + """ + if not userena_settings.USERENA_ACTIVATION_RETRY: + return redirect(reverse('userena_activate', args=(activation_key,))) + try: + if UserenaSignup.objects.check_expired_activation(activation_key): + new_key = UserenaSignup.objects.reissue_activation(activation_key) + if new_key: + if not extra_context: extra_context = dict() + return ExtraContextTemplateView.as_view(template_name=template_name, + extra_context=extra_context)(request) + else: + return redirect(reverse('userena_activate',args=(activation_key,))) + else: + return redirect(reverse('userena_activate',args=(activation_key,))) + except UserenaSignup.DoesNotExist: + return redirect(reverse('userena_activate',args=(activation_key,))) + +@secure_required +def email_confirm(request, confirmation_key, + template_name='userena/email_confirm_fail.html', + success_url=None, extra_context=None): + """ + Confirms an email address with a confirmation key. + + Confirms a new email address by running :func:`User.objects.confirm_email` + method. If the method returns an :class:`User` the user will have his new + e-mail address set and redirected to ``success_url``. If no ``User`` is + returned the user will be represented with a fail message from + ``template_name``. + + :param confirmation_key: + String with a SHA1 representing the confirmation key used to verify a + new email address. + + :param template_name: + String containing the template name which should be rendered when + confirmation fails. When confirmation is successful, no template is + needed because the user will be redirected to ``success_url``. + + :param success_url: + String containing the URL which is redirected to after a successful + confirmation. Supplied argument must be able to be rendered by + ``reverse`` function. + + :param extra_context: + Dictionary of variables that are passed on to the template supplied by + ``template_name``. + + """ + user = UserenaSignup.objects.confirm_email(confirmation_key) + if user: + if userena_settings.USERENA_USE_MESSAGES: + messages.success(request, _('Your email address has been changed.'), + fail_silently=True) + + if success_url: redirect_to = success_url + else: redirect_to = reverse('userena_email_confirm_complete', + kwargs={'username': user.username}) + return redirect(redirect_to) + else: + if not extra_context: extra_context = dict() + return ExtraContextTemplateView.as_view(template_name=template_name, + extra_context=extra_context)(request) + +def direct_to_user_template(request, username, template_name, + extra_context=None): + """ + Simple wrapper for Django's :func:`direct_to_template` view. + + This view is used when you want to show a template to a specific user. A + wrapper for :func:`direct_to_template` where the template also has access to + the user that is found with ``username``. For ex. used after signup, + activation and confirmation of a new e-mail. + + :param username: + String defining the username of the user that made the action. + + :param template_name: + String defining the name of the template to use. Defaults to + ``userena/signup_complete.html``. + + **Keyword arguments** + + ``extra_context`` + A dictionary containing extra variables that should be passed to the + rendered template. The ``account`` key is always the ``User`` + that completed the action. + + **Extra context** + + ``viewed_user`` + The currently :class:`User` that is viewed. + + """ + user = get_object_or_404(get_user_model(), username__iexact=username) + + if not extra_context: extra_context = dict() + extra_context['viewed_user'] = user + extra_context['profile'] = get_user_profile(user=user) + return ExtraContextTemplateView.as_view(template_name=template_name, + extra_context=extra_context)(request) + +def disabled_account(request, username, template_name, extra_context=None): + """ + Checks if the account is disabled, if so, returns the disabled account template. + + :param username: + String defining the username of the user that made the action. + + :param template_name: + String defining the name of the template to use. Defaults to + ``userena/signup_complete.html``. + + **Keyword arguments** + + ``extra_context`` + A dictionary containing extra variables that should be passed to the + rendered template. The ``account`` key is always the ``User`` + that completed the action. + + **Extra context** + + ``viewed_user`` + The currently :class:`User` that is viewed. + + ``profile`` + Profile of the viewed user. + + """ + user = get_object_or_404(get_user_model(), username__iexact=username) + + if user.is_active: + raise Http404 + + if not extra_context: extra_context = dict() + extra_context['viewed_user'] = user + extra_context['profile'] = get_user_profile(user=user) + return ExtraContextTemplateView.as_view(template_name=template_name, + extra_context=extra_context)(request) + +@secure_required +def signout(request, next_page=userena_settings.USERENA_REDIRECT_ON_SIGNOUT, + template_name='userena/signout.html', *args, **kwargs): + """ + Signs out the user and adds a success message ``You have been signed + out.`` If next_page is defined you will be redirected to the URI. If + not the template in template_name is used. + + :param next_page: + A string which specifies the URI to redirect to. + + :param template_name: + String defining the name of the template to use. Defaults to + ``userena/signout.html``. + + """ + if request.user.is_authenticated() and userena_settings.USERENA_USE_MESSAGES: # pragma: no cover + messages.success(request, _('You have been signed out.'), fail_silently=True) + userena_signals.account_signout.send(sender=None, user=request.user) + return Signout(request, next_page, template_name, *args, **kwargs)