X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=portal%2Fmodels.py;h=a16e3307a6b724fa61bdd7f47e08e7c6376a3d8b;hb=d68dcefd28c832608cdb359a07a8b871cbe612ae;hp=79cb25d307ce500d7ef31028f3be33969b8c967e;hpb=4ee78b17e6c8bb54fc94bcddd6b0abb3d8c1a0fc;p=myslice.git diff --git a/portal/models.py b/portal/models.py index 79cb25d3..a16e3307 100644 --- a/portal/models.py +++ b/portal/models.py @@ -20,7 +20,32 @@ # this program; see the file COPYING. If not, write to the Free Software # Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -from django.db import models +import datetime +import hashlib +import random +import re + +from django.conf import settings +from django.core.mail import send_mail +from django.db import models +from django.db import transaction +from django.utils.translation import ugettext_lazy as _ +from django.template.loader import render_to_string + +#from django.core.validators import validate_email + +try: + from django.contrib.auth import get_user_model + User = get_user_model() +except ImportError: + from django.contrib.auth.models import User + +try: + from django.utils.timezone import now as datetime_now +except ImportError: + datetime_now = datetime.datetime.now + +SHA1_RE = re.compile('^[a-f0-9]{40}$') # Create your models here. @@ -28,15 +53,261 @@ class Institution(models.Model): name = models.TextField() # list of associated email domains +# code borrowed from django-registration +# https://bitbucket.org/ubernostrum/django-registration/ + +class RegistrationManager(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. + + """ + def activate_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. + if SHA1_RE.search(activation_key): + try: + profile = self.get(activation_key=activation_key) + except self.model.DoesNotExist: + return False + if not profile.activation_key_expired(): + user = profile.user + user.is_active = True + user.save() + profile.activation_key = self.model.ACTIVATED + profile.save() + return user + return False + + def create_user(self, first_name, last_name, email, password): + pending_user = self.create(first_name=first_name, last_name=last_name, email=email, password=password) + return pending_user + + def create_inactive_user(self, first_name, last_name, email, password, site, + 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``. + + """ + salt = hashlib.sha1(str(random.random())).hexdigest()[:5] + if isinstance(email, unicode): + email = email.encode('utf-8') + activation_key = hashlib.sha1(salt+email).hexdigest() + + new_user = PendingUser.objects.create_user(first_name, last_name, email, password) + new_user.is_active = False + new_user.activation_key=activation_key + new_user.save() + + # We might not need this + #registration_profile = self.create_profile(new_user) + + if send_email: + new_user.send_activation_email(site) + #registration_profile.send_activation_email(site) + + return new_user + create_inactive_user = transaction.commit_on_success(create_inactive_user) + + def create_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.username + if isinstance(username, unicode): + username = username.encode('utf-8') + activation_key = hashlib.sha1(salt+username).hexdigest() + return self.create(user=user, + activation_key=activation_key) + + def delete_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. + + """ + for profile in self.all(): + try: + if profile.activation_key_expired(): + user = profile.user + if not user.is_active: + user.delete() + profile.delete() + except User.DoesNotExist: + profile.delete() + + class PendingUser(models.Model): # NOTE We might consider migrating the fields to CharField, which would # simplify form creation in forms.py first_name = models.TextField() last_name = models.TextField() - email = models.TextField() + affiliation = models.TextField() + email = models.EmailField() #validators=[validate_email]) password = models.TextField() keypair = models.TextField() # institution + authority_hrn = models.TextField() + # models.ForeignKey(Institution) + + objects = RegistrationManager() + + class Meta: + verbose_name = _('registration profile') + verbose_name_plural = _('registration profiles') + + def __unicode__(self): + return u"Registration information for %s" % self.email + + def activation_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) + return self.activation_key == self.ACTIVATED or \ + (self.user.date_joined + expiration_date <= datetime_now()) + activation_key_expired.boolean = True + + def send_activation_email(self, site): + """ + Send an activation email to the user associated with this + ``RegistrationProfile``. + + The activation email will make use of two templates: + + ``user_register_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. + + ``user_register_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} + subject = render_to_string('user_register_email_subject.txt', + ctx_dict) + # Email subject *must not* contain newlines + subject = ''.join(subject.splitlines()) + + message = render_to_string('user_register_email.txt', + ctx_dict) + + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [self.email]) + + + class PendingSlice(models.Model): slice_name = models.TextField() + authority_hrn = models.TextField(null=True)