django-registration-plus / registration / backends / invitational / __init__.py

from django.conf import settings
from django.contrib.auth import authenticate, login

from registration import signals
from registration.forms import InvitationForm
from registration.forms import RegistrationInvitationalForm
from registration.models import EmailValidation
from registration.models import RegistrationInvitation
from registration.future import get_current_site


class InvitationBackend(object):
    """
    An invitation-registration backend which follows a simple workflow:
    
    1. A registered user receives a ``RegistrationInvitation``
    
    2. The user invites a friend by entering their email.
    
    3. The friend accepts the invitation, clicks the link and lands on the 
    registration view.
    
    4. User signs up, an active account is created.
    
    5. An ``EmailValidation`` is created. If the user email is the same as 
    the invitation email the ``EmailValidation`` is immediately validated, 
    otherwise an email is sent to the user to validate it.
    
    6. User clicks validation link, email is validated.
    
    Using this backend requires:
    
    * ``registration`` be listed in the ``INSTALLED_APPS`` setting (since this 
      backend makes use of models defined in this application).
    
    * The setting ``EMAIL_INVITATION_DAYS`` be supplied, specifying (as an 
      integer) the number of days from when invitation is sent during which a 
      user may register (after that period expires, the invite is obsolete).
    
    * The setting ``EMAIL_VALIDATION_DAYS`` be supplied, specifying (as an 
      integer) the number of days from registration during which a user may 
      validate their email (after that period expires, they will need to 
      resend the validation email).
    
    * The creation of the invitation email templates
      ``registration/invitation_email_subject.txt``,
      ``registration/invitation_email.txt`` and optionally, 
      ``registration/invitation_email.html`` and the validation email templates
      ``registration/validation_email_subject.txt``,
      ``registration/validation_email.txt`` and optionally, 
      ``registration/validation_email.html``. See the notes for this backends 
      ``invite`` and ``register`` methods for details regarding these templates.
    
    * The creation of the templates ``registration/invitation_form.html``,
      ``registration/invitation_complete.html``,
      ``registration/registration_form.html``,
      ``registration/registration_complete.html``,
      ``registration/validation_error.html`` and
      ``registration/validation_complete.html`` for the views if you use the
      provided ``urls.py``.
    
    Optional tasks for using this backend:
    
    * Adding the ``REGISTRATION_OPEN`` setting and setting it to  ``False`` if 
      you want to close the registration. Omitting this setting, 
      or setting it to ``True``, will be interpreted as meaning that 
      registration is currently open and permitted.
    
    * Creation of the template ``registration/registration_closed.html`` for 
      when you close registration using the ``REGISTRATION_OPEN`` setting if 
      you use the provided ``urls.py``.
    
    Internally, the invitation is done by storing data in an instance of 
    ``registration.models.RegistrationInvitation`` and validation is done by 
    storing a validation key in an instance of 
    ``registration.models.EmailValidation``. See the model and its custom 
    managers for full documentation of its fields and supported operations. 
    
    If you want to add fields to ``registration.models.RegistrationInvitation`` 
    or ``registration.models.EmailValidation``, you can extend it and then 
    extend this backend with a new one that only specifies a different 
    ``registration_model_class`` and/or ``validation_model_class``.
    
    properties that allow simple modification of the backend:
    
    * ``required_validation`` tells the backend if the user is activated after
      email validation or immediately after registration.
    
    * ``login_after_activation`` tells the backend that it needs to log the 
      user in after registering. If ``required_validation`` is set to ``True`` 
      this setting has no effect.
    
    * ``validation_model_class`` defines the model that stores the validation 
      information. If you change it, you need to provide the same methods as 
      ``EmailValidation`` and its custom manager. 
    
    """
    required_validation = True
    login_after_activation = False
    invitation_model_class = RegistrationInvitation
    validation_model_class = EmailValidation
    
    def invite(self, request, **kwargs):
        """
        Given a user and an email, send an invitation to the email if the 
        user has any invitations left to use. Return None when invitation does 
        not succeed.
        
        """
        user, email = request.user, kwargs['email']
        if user.is_authenticated():
            site = get_current_site(request)
            return invitation_model_class.objects.send_invitation(user, 
                                                                  email, site)
        return None
    
    def get_invitation_form_class(self, request):
        """
        Return the default form class used for user registration.
        
        """
        return InvitationForm
    
    def post_invitation_redirect(self, request, user):
        """
        Return the name of the URL to redirect to after successful
        registration invitation.
        
        """
        return ('registration_invitation_complete', (), {})
    
    def register(self, request, **kwargs):
        """
        Given a username, email address and password, register a new
        user account, which will initially be active immediately.
        
        If the registration is not open also require an invitation_key.
        
        Along with the new ``User`` object, a new
        ``registration.models.EmailValidation`` will be created,
        tied to that ``User``, containing the validation key which
        will be used to validate the email. 
        
        If invitation_key is supplied and the user email matches the 
        invitation email, the validation is done automatically without 
        sending the validation email. 
        
        If needed, the email will be sent to the supplied email address; this
        email should contain a validation link. The email will be
        rendered using three templates (subject, text and html version). See 
        the documentation for ``EmailValidation.send_activation_email()`` for
        information about these templates and the contexts provided to
        them.
        
        After the ``User`` and ``EmailValidation`` are created and an optional
        validation email is sent, the signal
        ``registration.signals.user_registered`` will be sent, with
        the new ``User`` as the keyword argument ``user`` and the
        class of this backend as the sender.
        
        """
        username, email, password, invitation_key = kwargs['username'],  \
                                                    kwargs['email'],  \
                                                    kwargs['password1'],  \
                                                    kwargs['invitation_key']
        site = get_current_site(request)
        new_user = self.validation_model_class.objects.create_user(username, email, 
                                                       password, site, 
                                                       invitation_key=invitation_key,
                                                       active=(not self.required_validation))
        for k in kwargs:
            if hasattr(new_user, k):
                setattr(new_user, k, kwargs[k])
        new_user.save()
        signals.user_registered.send(sender=self.__class__,
                                     user=new_user,
                                     request=request)
        if not self.required_validation:
            if self.login_after_activation:
                # authenticate() always has to be called before login(), and
                # will return the user we just created.
                new_user = authenticate(username=username, password=password)
                login(request, new_user)
            signals.user_activated.send(sender=self.__class__,
                                        user=new_user,
                                        request=request)
        return new_user
    
    def validate(self, request, validation_key):
        """
        Given an an validation key, look up and validate the user account 
        corresponding to that key (if possible).
        
        After successful validation, the signal 
        ``registration.signals.user_activated`` will be sent, with the newly 
        activated ``User`` as the keyword argument ``user`` and the class of 
        this backend as the sender.
        
        """
        validated = self.validation_model_class.objects.validate_email(validation_key)
        if validated:
            if self.required_validation:
                validated.is_active = True
                validated.save()
                signals.user_activated.send(sender=self.__class__,
                                            user=validated,
                                            request=request)
            signals.email_validated.send(sender=self.__class__,
                                         user=validated,
                                         request=request)
        return validated
    
    def registration_allowed(self, request):
        """
        Indicate whether account registration is currently permitted, based on 
        the value of the setting ``REGISTRATION_OPEN``. This is determined as 
        follows:
        
        * If ``REGISTRATION_OPEN`` is not specified in settings, or is set to 
          ``True``, registration is permitted.

        * If ``REGISTRATION_OPEN`` is set to ``False``, registration is not 
          spermitted.
        
        """
        if not getattr(settings, 'REGISTRATION_OPEN', False):
            try:
                invite = invitation_model_class.objects.get(invitation_key=request.GET.get("invitation_key"))
                return True
            except invitation_model_class.DoesNotExist:
                return False
        return True
    
    def get_form_class(self, request):
        """
        Return the default form class used for user registration.
        
        """
        return RegistrationInvitationalForm
    
    def post_registration_redirect(self, request, user):
        """
        Return the name of the URL to redirect to after successful
        user registration.
        
        """
        return ('registration_complete', (), {})
    
    def post_validation_redirect(self, request, user):
        """
        Return the name of the URL to redirect to after successful
        account validation.
        
        """
        return ('registration_validation_complete', (), {})
    

class RequiredValidationBackend(InvitationBackend):
    """
    A slight modification to the invitation backend:
    
    4. User signs up, an inactive account is created.
    
    5. Email is sent to user with validation link.
    
    6. User clicks validation link, email is now validated and the user 
       activated.
    
    """
    required_validation = True
    

class AutoLoginBackend(InvitationBackend):
    """
    A slight modification to the registration backend:
    
    4. User signs up, an active account is created, user is logged in.
    
    """
    login_after_activation = True
    
    def post_registration_redirect(self, request, user):
        """
        After registration, redirect to the user's account page.
        
        """
        return (user.get_absolute_url(), (), {})
    
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.