1 # -*- coding: utf-8 -*-
3 # portal/models.py: models for the portal application
4 # This file is part of the Manifold project.
7 # Jordan Augé <jordan.auge@lip6.fr>
8 # Copyright 2013, UPMC Sorbonne Universités / LIP6
10 # This program is free software; you can redistribute it and/or modify it under
11 # the terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 3, or (at your option) any later version.
14 # This program is distributed in the hope that it will be useful, but WITHOUT
15 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19 # You should have received a copy of the GNU General Public License along with
20 # this program; see the file COPYING. If not, write to the Free Software
21 # Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 from django.conf import settings
29 from django.core.mail import send_mail
30 from django.db import models
31 from django.db import transaction
32 from django.utils.translation import ugettext_lazy as _
33 from django.template.loader import render_to_string
35 #from django.core.validators import validate_email
38 from django.contrib.auth import get_user_model
39 User = get_user_model()
41 from django.contrib.auth.models import User
44 from django.utils.timezone import now as datetime_now
46 datetime_now = datetime.datetime.now
48 SHA1_RE = re.compile('^[a-f0-9]{40}$')
50 # Create your models here.
52 class Institution(models.Model):
53 name = models.TextField()
54 # list of associated email domains
56 # code borrowed from django-registration
57 # https://bitbucket.org/ubernostrum/django-registration/
59 class RegistrationManager(models.Manager):
61 Custom manager for the ``RegistrationProfile`` model.
63 The methods defined here provide shortcuts for account creation
64 and activation (including generation and emailing of activation
65 keys), and for cleaning out expired inactive accounts.
68 def activate_user(self, activation_key):
70 Validate an activation key and activate the corresponding
73 If the key is valid and has not expired, return the ``User``
76 If the key is not valid or has expired, return ``False``.
78 If the key is valid but the ``User`` is already active,
81 To prevent reactivation of an account which has been
82 deactivated by site administrators, the activation key is
83 reset to the string constant ``RegistrationProfile.ACTIVATED``
84 after successful activation.
87 # Make sure the key we're trying conforms to the pattern of a
88 # SHA1 hash; if it doesn't, no point trying to look it up in
90 if SHA1_RE.search(activation_key):
92 profile = self.get(activation_key=activation_key)
93 except self.model.DoesNotExist:
95 if not profile.activation_key_expired():
99 profile.activation_key = self.model.ACTIVATED
104 def create_user(self, first_name, last_name, email, password):
105 pending_user = self.create(first_name=first_name, last_name=last_name, email=email, password=password)
108 def create_inactive_user(self, first_name, last_name, email, password, site,
111 Create a new, inactive ``User``, generate a
112 ``RegistrationProfile`` and email its activation key to the
113 ``User``, returning the new ``User``.
115 By default, an activation email will be sent to the new
116 user. To disable this, pass ``send_email=False``.
119 salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
120 if isinstance(email, unicode):
121 email = email.encode('utf-8')
122 activation_key = hashlib.sha1(salt+email).hexdigest()
124 new_user = PendingUser.objects.create_user(first_name, last_name, email, password)
125 new_user.is_active = False
126 new_user.activation_key=activation_key
129 # We might not need this
130 #registration_profile = self.create_profile(new_user)
133 new_user.send_activation_email(site)
134 #registration_profile.send_activation_email(site)
137 create_inactive_user = transaction.commit_on_success(create_inactive_user)
139 def create_profile(self, user):
141 Create a ``RegistrationProfile`` for a given
142 ``User``, and return the ``RegistrationProfile``.
144 The activation key for the ``RegistrationProfile`` will be a
145 SHA1 hash, generated from a combination of the ``User``'s
146 username and a random salt.
149 salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
150 username = user.username
151 if isinstance(username, unicode):
152 username = username.encode('utf-8')
153 activation_key = hashlib.sha1(salt+username).hexdigest()
154 return self.create(user=user,
155 activation_key=activation_key)
157 def delete_expired_users(self):
159 Remove expired instances of ``RegistrationProfile`` and their
160 associated ``User``s.
162 Accounts to be deleted are identified by searching for
163 instances of ``RegistrationProfile`` with expired activation
164 keys, and then checking to see if their associated ``User``
165 instances have the field ``is_active`` set to ``False``; any
166 ``User`` who is both inactive and has an expired activation
169 It is recommended that this method be executed regularly as
170 part of your routine site maintenance; this application
171 provides a custom management command which will call this
172 method, accessible as ``manage.py cleanupregistration``.
174 Regularly clearing out accounts which have never been
175 activated serves two useful purposes:
177 1. It alleviates the ocasional need to reset a
178 ``RegistrationProfile`` and/or re-send an activation email
179 when a user does not receive or does not act upon the
180 initial activation email; since the account will be
181 deleted, the user will be able to simply re-register and
182 receive a new activation key.
184 2. It prevents the possibility of a malicious user registering
185 one or more accounts and never activating them (thus
186 denying the use of those usernames to anyone else); since
187 those accounts will be deleted, the usernames will become
188 available for use again.
190 If you have a troublesome ``User`` and wish to disable their
191 account while keeping it in the database, simply delete the
192 associated ``RegistrationProfile``; an inactive ``User`` which
193 does not have an associated ``RegistrationProfile`` will not
197 for profile in self.all():
199 if profile.activation_key_expired():
201 if not user.is_active:
204 except User.DoesNotExist:
208 class PendingUser(models.Model):
209 # NOTE We might consider migrating the fields to CharField, which would
210 # simplify form creation in forms.py
211 first_name = models.TextField()
212 last_name = models.TextField()
213 email = models.EmailField() #validators=[validate_email])
214 password = models.TextField()
215 keypair = models.TextField()
218 objects = RegistrationManager()
221 verbose_name = _('registration profile')
222 verbose_name_plural = _('registration profiles')
224 def __unicode__(self):
225 return u"Registration information for %s" % self.user
227 def activation_key_expired(self):
229 Determine whether this ``RegistrationProfile``'s activation
230 key has expired, returning a boolean -- ``True`` if the key
233 Key expiration is determined by a two-step process:
235 1. If the user has already activated, the key will have been
236 reset to the string constant ``ACTIVATED``. Re-activating
237 is not permitted, and so this method returns ``True`` in
240 2. Otherwise, the date the user signed up is incremented by
241 the number of days specified in the setting
242 ``ACCOUNT_ACTIVATION_DAYS`` (which should be the number of
243 days after signup during which a user is allowed to
244 activate their account); if the result is less than or
245 equal to the current date, the key has expired and this
246 method returns ``True``.
249 expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
250 return self.activation_key == self.ACTIVATED or \
251 (self.user.date_joined + expiration_date <= datetime_now())
252 activation_key_expired.boolean = True
254 def send_activation_email(self, site):
256 Send an activation email to the user associated with this
257 ``RegistrationProfile``.
259 The activation email will make use of two templates:
261 ``user_register_email_subject.txt``
262 This template will be used for the subject line of the
263 email. Because it is used as the subject line of an email,
264 this template's output **must** be only a single line of
265 text; output longer than one line will be forcibly joined
266 into only a single line.
268 ``user_register_email.txt``
269 This template will be used for the body of the email.
271 These templates will each receive the following context
275 The activation key for the new account.
278 The number of days remaining during which the account may
282 An object representing the site on which the user
283 registered; depending on whether ``django.contrib.sites``
284 is installed, this may be an instance of either
285 ``django.contrib.sites.models.Site`` (if the sites
286 application is installed) or
287 ``django.contrib.sites.models.RequestSite`` (if
288 not). Consult the documentation for the Django sites
289 framework for details regarding these objects' interfaces.
292 ctx_dict = {'activation_key': self.activation_key,
293 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
295 subject = render_to_string('user_register_email_subject.txt',
297 # Email subject *must not* contain newlines
298 subject = ''.join(subject.splitlines())
300 message = render_to_string('user_register_email.txt',
303 send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [self.email])
308 class PendingSlice(models.Model):
309 slice_name = models.TextField()