# -*- coding: utf-8 -*- # # portal/models.py: models for the portal application # This file is part of the Manifold project. # # Authors: # Jordan Augé # Copyright 2013, UPMC Sorbonne Universités / LIP6 # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 3, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; see the file COPYING. If not, write to the Free Software # Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 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. 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() 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) number_of_nodes = models.TextField(default=0) type_of_nodes = models.TextField(default='NA') purpose = models.TextField(default='NA')