importdatetimeimporthashlibimportrandomimportrefromdjango.confimportsettingsfromdjango.contrib.auth.modelsimportUserfromdjango.dbimportmodelsfromdjango.dbimporttransactionfromdjango.template.loaderimportrender_to_stringfromdjango.utils.translationimportugettext_lazyas_try:fromdjango.utils.timezoneimportnowasdatetime_nowexceptImportError:datetime_now=datetime.datetime.nowSHA1_RE=re.compile('^[a-f0-9]{40}$')classRegistrationManager(models.Manager):""" Custom manager for the ``RegistrationProfile`` model. The methods defined here provide shortcuts for account creation and activation (including generation and emailing of activation keys), and for cleaning out expired inactive accounts. """defactivate_user(self,activation_key):""" Validate an activation key and activate the corresponding ``User`` if valid. If the key is valid and has not expired, return the ``User`` after activating. If the key is not valid or has expired, return ``False``. If the key is valid but the ``User`` is already active, return ``False``. To prevent reactivation of an account which has been deactivated by site administrators, the activation key is reset to the string constant ``RegistrationProfile.ACTIVATED`` after successful activation. """# Make sure the key we're trying conforms to the pattern of a# SHA1 hash; if it doesn't, no point trying to look it up in# the database.ifSHA1_RE.search(activation_key):try:profile=self.get(activation_key=activation_key)exceptself.model.DoesNotExist:returnFalseifnotprofile.activation_key_expired():user=profile.useruser.is_active=Trueuser.save()profile.activation_key=self.model.ACTIVATEDprofile.save()returnuserreturnFalsedefcreate_inactive_user(self,username,email,password,site,language,code,send_email=True):""" Create a new, inactive ``User``, generate a ``RegistrationProfile`` and email its activation key to the ``User``, returning the new ``User``. By default, an activation email will be sent to the new user. To disable this, pass ``send_email=False``. """new_user=User.objects.create_user(username,email,password)new_user.is_active=Falsenew_user.save()registration_profile=self.create_profile(new_user)ifsend_email:registration_profile.send_activation_email(site,language,code)returnnew_usercreate_inactive_user=transaction.commit_on_success(create_inactive_user)defcreate_profile(self,user):""" Create a ``RegistrationProfile`` for a given ``User``, and return the ``RegistrationProfile``. The activation key for the ``RegistrationProfile`` will be a SHA1 hash, generated from a combination of the ``User``'s username and a random salt. """salt=hashlib.sha1(str(random.random())).hexdigest()[:5]username=user.usernameifisinstance(username,unicode):username=username.encode('utf-8')activation_key=hashlib.sha1(salt+username).hexdigest()returnself.create(user=user,activation_key=activation_key)defdelete_expired_users(self):""" Remove expired instances of ``RegistrationProfile`` and their associated ``User``s. Accounts to be deleted are identified by searching for instances of ``RegistrationProfile`` with expired activation keys, and then checking to see if their associated ``User`` instances have the field ``is_active`` set to ``False``; any ``User`` who is both inactive and has an expired activation key will be deleted. It is recommended that this method be executed regularly as part of your routine site maintenance; this application provides a custom management command which will call this method, accessible as ``manage.py cleanupregistration``. Regularly clearing out accounts which have never been activated serves two useful purposes: 1. It alleviates the ocasional need to reset a ``RegistrationProfile`` and/or re-send an activation email when a user does not receive or does not act upon the initial activation email; since the account will be deleted, the user will be able to simply re-register and receive a new activation key. 2. It prevents the possibility of a malicious user registering one or more accounts and never activating them (thus denying the use of those usernames to anyone else); since those accounts will be deleted, the usernames will become available for use again. If you have a troublesome ``User`` and wish to disable their account while keeping it in the database, simply delete the associated ``RegistrationProfile``; an inactive ``User`` which does not have an associated ``RegistrationProfile`` will not be deleted. """forprofileinself.all():try:ifprofile.activation_key_expired():user=profile.userifnotuser.is_active:user.delete()profile.delete()exceptUser.DoesNotExist:profile.delete()classRegistrationProfile(models.Model):""" A simple profile which stores an activation key for use during user account registration. Generally, you will not want to interact directly with instances of this model; the provided manager includes methods for creating and activating new accounts, as well as for cleaning out accounts which have never been activated. While it is possible to use this model as the value of the ``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do so. This model's sole purpose is to store data temporarily during account registration and activation. """ACTIVATED=u"ALREADY_ACTIVATED"user=models.ForeignKey(User,unique=True,verbose_name=_('user'))activation_key=models.CharField(_('activation key'),max_length=40)objects=RegistrationManager()classMeta:verbose_name=_('registration profile')verbose_name_plural=_('registration profiles')def__unicode__(self):returnu"Registration information for %s"%self.userdefactivation_key_expired(self):""" Determine whether this ``RegistrationProfile``'s activation key has expired, returning a boolean -- ``True`` if the key has expired. Key expiration is determined by a two-step process: 1. If the user has already activated, the key will have been reset to the string constant ``ACTIVATED``. Re-activating is not permitted, and so this method returns ``True`` in this case. 2. Otherwise, the date the user signed up is incremented by the number of days specified in the setting ``ACCOUNT_ACTIVATION_DAYS`` (which should be the number of days after signup during which a user is allowed to activate their account); if the result is less than or equal to the current date, the key has expired and this method returns ``True``. """expiration_date=datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)returnself.activation_key==self.ACTIVATEDor \
(self.user.date_joined+expiration_date<=datetime_now())activation_key_expired.boolean=Truedefsend_activation_email(self,site,language,code):""" Send an activation email to the user associated with this ``RegistrationProfile``. The activation email will make use of two templates: ``registration/activation_email_subject.txt`` This template will be used for the subject line of the email. Because it is used as the subject line of an email, this template's output **must** be only a single line of text; output longer than one line will be forcibly joined into only a single line. ``registration/activation_email.txt`` This template will be used for the body of the email. These templates will each receive the following context variables: ``activation_key`` The activation key for the new account. ``expiration_days`` The number of days remaining during which the account may be activated. ``site`` An object representing the site on which the user registered; depending on whether ``django.contrib.sites`` is installed, this may be an instance of either ``django.contrib.sites.models.Site`` (if the sites application is installed) or ``django.contrib.sites.models.RequestSite`` (if not). Consult the documentation for the Django sites framework for details regarding these objects' interfaces. """ctx_dict={'activation_key':self.activation_key,'expiration_days':settings.ACCOUNT_ACTIVATION_DAYS,'site':site,'code':code,'language':language}subject=render_to_string('registration/activation_email_subject.txt',ctx_dict)# Email subject *must not* contain newlinessubject=''.join(subject.splitlines())message_text=render_to_string('registration/activation_email.txt',ctx_dict)fromdjango.core.mailimportEmailMultiAlternativesmessage_html=render_to_string('registration/activation_email.html',ctx_dict)message=EmailMultiAlternatives(subject,message_text,settings.DEFAULT_FROM_EMAIL,[self.user.email])message.attach_alternative(message_html,"text/html")message.send()#message = render_to_string('registration/activation_email.txt',ctx_dict)#self.user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)