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 affiliation = models.TextField()
214 email = models.EmailField() #validators=[validate_email])
215 password = models.TextField()
216 keypair = models.TextField()
218 authority_hrn = models.TextField()
219 # models.ForeignKey(Institution)
221 objects = RegistrationManager()
224 verbose_name = _('registration profile')
225 verbose_name_plural = _('registration profiles')
227 def __unicode__(self):
228 return u"Registration information for %s" % self.email
230 def activation_key_expired(self):
232 Determine whether this ``RegistrationProfile``'s activation
233 key has expired, returning a boolean -- ``True`` if the key
236 Key expiration is determined by a two-step process:
238 1. If the user has already activated, the key will have been
239 reset to the string constant ``ACTIVATED``. Re-activating
240 is not permitted, and so this method returns ``True`` in
243 2. Otherwise, the date the user signed up is incremented by
244 the number of days specified in the setting
245 ``ACCOUNT_ACTIVATION_DAYS`` (which should be the number of
246 days after signup during which a user is allowed to
247 activate their account); if the result is less than or
248 equal to the current date, the key has expired and this
249 method returns ``True``.
252 expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
253 return self.activation_key == self.ACTIVATED or \
254 (self.user.date_joined + expiration_date <= datetime_now())
255 activation_key_expired.boolean = True
257 def send_activation_email(self, site):
259 Send an activation email to the user associated with this
260 ``RegistrationProfile``.
262 The activation email will make use of two templates:
264 ``user_register_email_subject.txt``
265 This template will be used for the subject line of the
266 email. Because it is used as the subject line of an email,
267 this template's output **must** be only a single line of
268 text; output longer than one line will be forcibly joined
269 into only a single line.
271 ``user_register_email.txt``
272 This template will be used for the body of the email.
274 These templates will each receive the following context
278 The activation key for the new account.
281 The number of days remaining during which the account may
285 An object representing the site on which the user
286 registered; depending on whether ``django.contrib.sites``
287 is installed, this may be an instance of either
288 ``django.contrib.sites.models.Site`` (if the sites
289 application is installed) or
290 ``django.contrib.sites.models.RequestSite`` (if
291 not). Consult the documentation for the Django sites
292 framework for details regarding these objects' interfaces.
295 ctx_dict = {'activation_key': self.activation_key,
296 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
298 subject = render_to_string('user_register_email_subject.txt',
300 # Email subject *must not* contain newlines
301 subject = ''.join(subject.splitlines())
303 message = render_to_string('user_register_email.txt',
306 send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [self.email])
311 class PendingSlice(models.Model):
312 slice_name = models.TextField()
313 authority_hrn = models.TextField(null=True)
314 number_of_nodes = models.TextField(default=0)
315 type_of_nodes = models.TextField(default='NA')
316 purpose = models.TextField(default='NA')