Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ f0f92965

History | View | Annotate | Download (12.1 kB)

1 aba1e498 Antony Chazapis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 6c736ed7 Kostas Papadimitriou
#
3 64cd4730 Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 64cd4730 Antony Chazapis
# without modification, are permitted provided that the following
5 64cd4730 Antony Chazapis
# conditions are met:
6 6c736ed7 Kostas Papadimitriou
#
7 64cd4730 Antony Chazapis
#   1. Redistributions of source code must retain the above
8 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
9 64cd4730 Antony Chazapis
#      disclaimer.
10 6c736ed7 Kostas Papadimitriou
#
11 64cd4730 Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
13 64cd4730 Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 64cd4730 Antony Chazapis
#      provided with the distribution.
15 6c736ed7 Kostas Papadimitriou
#
16 64cd4730 Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 64cd4730 Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 64cd4730 Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 64cd4730 Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 64cd4730 Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 64cd4730 Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 64cd4730 Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 64cd4730 Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 64cd4730 Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 64cd4730 Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 64cd4730 Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 64cd4730 Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 6c736ed7 Kostas Papadimitriou
#
29 64cd4730 Antony Chazapis
# The views and conclusions contained in the software and
30 64cd4730 Antony Chazapis
# documentation are those of the authors and should not be
31 64cd4730 Antony Chazapis
# interpreted as representing official policies, either expressed
32 64cd4730 Antony Chazapis
# or implied, of GRNET S.A.
33 64cd4730 Antony Chazapis
34 64cd4730 Antony Chazapis
import hashlib
35 15efc749 Sofia Papagiannaki
import uuid
36 18ffbee1 Sofia Papagiannaki
import logging
37 0a569195 Sofia Papagiannaki
import json
38 64cd4730 Antony Chazapis
39 64cd4730 Antony Chazapis
from time import asctime
40 64cd4730 Antony Chazapis
from datetime import datetime, timedelta
41 64cd4730 Antony Chazapis
from base64 import b64encode
42 0a569195 Sofia Papagiannaki
from urlparse import urlparse, urlunparse
43 8f5a3a06 Sofia Papagiannaki
from random import randint
44 64cd4730 Antony Chazapis
45 49790d9d Sofia Papagiannaki
from django.db import models, IntegrityError
46 18ffbee1 Sofia Papagiannaki
from django.contrib.auth.models import User, UserManager, Group
47 0a569195 Sofia Papagiannaki
from django.utils.translation import ugettext as _
48 0a569195 Sofia Papagiannaki
from django.core.exceptions import ValidationError
49 49790d9d Sofia Papagiannaki
from django.template.loader import render_to_string
50 49790d9d Sofia Papagiannaki
from django.core.mail import send_mail
51 49790d9d Sofia Papagiannaki
from django.db import transaction
52 64cd4730 Antony Chazapis
53 49790d9d Sofia Papagiannaki
from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, \
54 49790d9d Sofia Papagiannaki
AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION, SITENAME, \
55 49790d9d Sofia Papagiannaki
EMAILCHANGE_ACTIVATION_DAYS
56 9c01d9d1 Sofia Papagiannaki
57 9c01d9d1 Sofia Papagiannaki
QUEUE_CLIENT_ID = 3 # Astakos.
58 64cd4730 Antony Chazapis
59 18ffbee1 Sofia Papagiannaki
logger = logging.getLogger(__name__)
60 18ffbee1 Sofia Papagiannaki
61 0905ccd2 Sofia Papagiannaki
class AstakosUser(User):
62 890b0eaf Sofia Papagiannaki
    """
63 890b0eaf Sofia Papagiannaki
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
64 890b0eaf Sofia Papagiannaki
    """
65 0905ccd2 Sofia Papagiannaki
    # Use UserManager to get the create_user method, etc.
66 0905ccd2 Sofia Papagiannaki
    objects = UserManager()
67 6c736ed7 Kostas Papadimitriou
68 5ed6816e Sofia Papagiannaki
    affiliation = models.CharField('Affiliation', max_length=255, blank=True)
69 5ed6816e Sofia Papagiannaki
    provider = models.CharField('Provider', max_length=255, blank=True)
70 6c736ed7 Kostas Papadimitriou
71 64cd4730 Antony Chazapis
    #for invitations
72 92defad4 Sofia Papagiannaki
    user_level = DEFAULT_USER_LEVEL
73 a196eb7e Sofia Papagiannaki
    level = models.IntegerField('Inviter level', default=user_level)
74 ebd369d0 Sofia Papagiannaki
    invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
75 6c736ed7 Kostas Papadimitriou
76 64cd4730 Antony Chazapis
    auth_token = models.CharField('Authentication Token', max_length=32,
77 0905ccd2 Sofia Papagiannaki
                                  null=True, blank=True)
78 0905ccd2 Sofia Papagiannaki
    auth_token_created = models.DateTimeField('Token creation date', null=True)
79 0905ccd2 Sofia Papagiannaki
    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
80 6c736ed7 Kostas Papadimitriou
81 64cd4730 Antony Chazapis
    updated = models.DateTimeField('Update date')
82 890b0eaf Sofia Papagiannaki
    is_verified = models.BooleanField('Is verified?', default=False)
83 6c736ed7 Kostas Papadimitriou
84 15efc749 Sofia Papagiannaki
    # ex. screen_name for twitter, eppn for shibboleth
85 15efc749 Sofia Papagiannaki
    third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
86 6c736ed7 Kostas Papadimitriou
87 ebd369d0 Sofia Papagiannaki
    email_verified = models.BooleanField('Email verified?', default=False)
88 6c736ed7 Kostas Papadimitriou
89 59f598f1 Sofia Papagiannaki
    has_credits = models.BooleanField('Has credits?', default=False)
90 270dd48d Sofia Papagiannaki
    has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
91 0a569195 Sofia Papagiannaki
    date_signed_terms = models.DateTimeField('Signed terms date', null=True, blank=True)
92 59f598f1 Sofia Papagiannaki
    
93 18ffbee1 Sofia Papagiannaki
    __has_signed_terms = False
94 18ffbee1 Sofia Papagiannaki
    __groupnames = []
95 18ffbee1 Sofia Papagiannaki
    
96 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
97 18ffbee1 Sofia Papagiannaki
        super(AstakosUser, self).__init__(*args, **kwargs)
98 18ffbee1 Sofia Papagiannaki
        self.__has_signed_terms = self.has_signed_terms
99 18ffbee1 Sofia Papagiannaki
        if self.id:
100 18ffbee1 Sofia Papagiannaki
            self.__groupnames = [g.name for g in self.groups.all()]
101 18ffbee1 Sofia Papagiannaki
        else:
102 18ffbee1 Sofia Papagiannaki
            self.is_active = False
103 18ffbee1 Sofia Papagiannaki
    
104 0905ccd2 Sofia Papagiannaki
    @property
105 0905ccd2 Sofia Papagiannaki
    def realname(self):
106 0905ccd2 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
107 6c736ed7 Kostas Papadimitriou
108 0905ccd2 Sofia Papagiannaki
    @realname.setter
109 0905ccd2 Sofia Papagiannaki
    def realname(self, value):
110 0905ccd2 Sofia Papagiannaki
        parts = value.split(' ')
111 0905ccd2 Sofia Papagiannaki
        if len(parts) == 2:
112 0905ccd2 Sofia Papagiannaki
            self.first_name = parts[0]
113 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[1]
114 0905ccd2 Sofia Papagiannaki
        else:
115 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[0]
116 6c736ed7 Kostas Papadimitriou
117 64cd4730 Antony Chazapis
    @property
118 64cd4730 Antony Chazapis
    def invitation(self):
119 64cd4730 Antony Chazapis
        try:
120 9fb8e808 Sofia Papagiannaki
            return Invitation.objects.get(username=self.email)
121 64cd4730 Antony Chazapis
        except Invitation.DoesNotExist:
122 64cd4730 Antony Chazapis
            return None
123 6c736ed7 Kostas Papadimitriou
124 64cd4730 Antony Chazapis
    def save(self, update_timestamps=True, **kwargs):
125 64cd4730 Antony Chazapis
        if update_timestamps:
126 64cd4730 Antony Chazapis
            if not self.id:
127 0905ccd2 Sofia Papagiannaki
                self.date_joined = datetime.now()
128 64cd4730 Antony Chazapis
            self.updated = datetime.now()
129 18ffbee1 Sofia Papagiannaki
        
130 18ffbee1 Sofia Papagiannaki
        # update date_signed_terms if necessary
131 18ffbee1 Sofia Papagiannaki
        if self.__has_signed_terms != self.has_signed_terms:
132 18ffbee1 Sofia Papagiannaki
            self.date_signed_terms = datetime.now()
133 18ffbee1 Sofia Papagiannaki
        
134 9c01d9d1 Sofia Papagiannaki
        if not self.id:
135 9c01d9d1 Sofia Papagiannaki
            # set username
136 9c01d9d1 Sofia Papagiannaki
            while not self.username:
137 9c01d9d1 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
138 9c01d9d1 Sofia Papagiannaki
                try:
139 9c01d9d1 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
140 9c01d9d1 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
141 9c01d9d1 Sofia Papagiannaki
                    self.username = username
142 9c01d9d1 Sofia Papagiannaki
            if not self.provider:
143 9c01d9d1 Sofia Papagiannaki
                self.provider = 'local'
144 9c01d9d1 Sofia Papagiannaki
        report_user_event(self)
145 591d0505 Sofia Papagiannaki
        self.validate_unique_email_isactive()
146 0905ccd2 Sofia Papagiannaki
        super(AstakosUser, self).save(**kwargs)
147 18ffbee1 Sofia Papagiannaki
        
148 18ffbee1 Sofia Papagiannaki
        # set group if does not exist
149 18ffbee1 Sofia Papagiannaki
        groupname = 'shibboleth' if self.provider == 'shibboleth' else 'default'
150 18ffbee1 Sofia Papagiannaki
        if groupname not in self.__groupnames:
151 18ffbee1 Sofia Papagiannaki
            try:
152 18ffbee1 Sofia Papagiannaki
                group = Group.objects.get(name = groupname)
153 18ffbee1 Sofia Papagiannaki
                self.groups.add(group)
154 18ffbee1 Sofia Papagiannaki
            except Group.DoesNotExist, e:
155 18ffbee1 Sofia Papagiannaki
                logger.exception(e)
156 64cd4730 Antony Chazapis
    
157 64cd4730 Antony Chazapis
    def renew_token(self):
158 64cd4730 Antony Chazapis
        md5 = hashlib.md5()
159 0905ccd2 Sofia Papagiannaki
        md5.update(self.username)
160 64cd4730 Antony Chazapis
        md5.update(self.realname.encode('ascii', 'ignore'))
161 64cd4730 Antony Chazapis
        md5.update(asctime())
162 6c736ed7 Kostas Papadimitriou
163 64cd4730 Antony Chazapis
        self.auth_token = b64encode(md5.digest())
164 64cd4730 Antony Chazapis
        self.auth_token_created = datetime.now()
165 64cd4730 Antony Chazapis
        self.auth_token_expires = self.auth_token_created + \
166 92defad4 Sofia Papagiannaki
                                  timedelta(hours=AUTH_TOKEN_DURATION)
167 6c736ed7 Kostas Papadimitriou
168 64cd4730 Antony Chazapis
    def __unicode__(self):
169 0905ccd2 Sofia Papagiannaki
        return self.username
170 0a569195 Sofia Papagiannaki
    
171 0a569195 Sofia Papagiannaki
    def conflicting_email(self):
172 0a569195 Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username = self.username)
173 0a569195 Sofia Papagiannaki
        q = q.filter(email = self.email)
174 0a569195 Sofia Papagiannaki
        if q.count() != 0:
175 0a569195 Sofia Papagiannaki
            return True
176 0a569195 Sofia Papagiannaki
        return False
177 0a569195 Sofia Papagiannaki
    
178 591d0505 Sofia Papagiannaki
    def validate_unique_email_isactive(self):
179 0a569195 Sofia Papagiannaki
        """
180 0a569195 Sofia Papagiannaki
        Implements a unique_together constraint for email and is_active fields.
181 0a569195 Sofia Papagiannaki
        """
182 0a569195 Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username = self.username)
183 0a569195 Sofia Papagiannaki
        q = q.filter(email = self.email)
184 0a569195 Sofia Papagiannaki
        q = q.filter(is_active = self.is_active)
185 0a569195 Sofia Papagiannaki
        if q.count() != 0:
186 0a569195 Sofia Papagiannaki
            raise ValidationError({'__all__':[_('Another account with the same email & is_active combination found.')]})
187 09e7393c Sofia Papagiannaki
    
188 09e7393c Sofia Papagiannaki
    def signed_terms(self):
189 09e7393c Sofia Papagiannaki
        term = get_latest_terms()
190 09e7393c Sofia Papagiannaki
        if not term:
191 09e7393c Sofia Papagiannaki
            return True
192 09e7393c Sofia Papagiannaki
        if not self.has_signed_terms:
193 09e7393c Sofia Papagiannaki
            return False
194 09e7393c Sofia Papagiannaki
        if not self.date_signed_terms:
195 09e7393c Sofia Papagiannaki
            return False
196 09e7393c Sofia Papagiannaki
        if self.date_signed_terms < term.date:
197 09e7393c Sofia Papagiannaki
            self.has_signed_terms = False
198 f0f92965 Sofia Papagiannaki
            self.date_signed_terms = None
199 09e7393c Sofia Papagiannaki
            self.save()
200 09e7393c Sofia Papagiannaki
            return False
201 09e7393c Sofia Papagiannaki
        return True
202 09e7393c Sofia Papagiannaki
203 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
204 270dd48d Sofia Papagiannaki
    """
205 270dd48d Sofia Papagiannaki
    Model for approval terms
206 270dd48d Sofia Papagiannaki
    """
207 6c736ed7 Kostas Papadimitriou
208 270dd48d Sofia Papagiannaki
    date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
209 270dd48d Sofia Papagiannaki
    location = models.CharField('Terms location', max_length=255)
210 270dd48d Sofia Papagiannaki
211 64cd4730 Antony Chazapis
class Invitation(models.Model):
212 890b0eaf Sofia Papagiannaki
    """
213 890b0eaf Sofia Papagiannaki
    Model for registring invitations
214 890b0eaf Sofia Papagiannaki
    """
215 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
216 64cd4730 Antony Chazapis
                                null=True)
217 64cd4730 Antony Chazapis
    realname = models.CharField('Real name', max_length=255)
218 ebd369d0 Sofia Papagiannaki
    username = models.CharField('Unique ID', max_length=255, unique=True)
219 64cd4730 Antony Chazapis
    code = models.BigIntegerField('Invitation code', db_index=True)
220 64cd4730 Antony Chazapis
    is_consumed = models.BooleanField('Consumed?', default=False)
221 64cd4730 Antony Chazapis
    created = models.DateTimeField('Creation date', auto_now_add=True)
222 64cd4730 Antony Chazapis
    consumed = models.DateTimeField('Consumption date', null=True, blank=True)
223 64cd4730 Antony Chazapis
    
224 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
225 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
226 8f5a3a06 Sofia Papagiannaki
        if not self.id:
227 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
228 8f5a3a06 Sofia Papagiannaki
    
229 64cd4730 Antony Chazapis
    def consume(self):
230 64cd4730 Antony Chazapis
        self.is_consumed = True
231 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
232 64cd4730 Antony Chazapis
        self.save()
233 6c736ed7 Kostas Papadimitriou
234 64cd4730 Antony Chazapis
    def __unicode__(self):
235 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
236 9c01d9d1 Sofia Papagiannaki
237 9c01d9d1 Sofia Papagiannaki
def report_user_event(user):
238 9c01d9d1 Sofia Papagiannaki
    def should_send(user):
239 9c01d9d1 Sofia Papagiannaki
        # report event incase of new user instance
240 9c01d9d1 Sofia Papagiannaki
        # or if specific fields are modified
241 9c01d9d1 Sofia Papagiannaki
        if not user.id:
242 9c01d9d1 Sofia Papagiannaki
            return True
243 9c01d9d1 Sofia Papagiannaki
        db_instance = AstakosUser.objects.get(id = user.id)
244 9c01d9d1 Sofia Papagiannaki
        for f in BILLING_FIELDS:
245 9c01d9d1 Sofia Papagiannaki
            if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
246 9c01d9d1 Sofia Papagiannaki
                return True
247 9c01d9d1 Sofia Papagiannaki
        return False
248 6c736ed7 Kostas Papadimitriou
249 3a9f4931 Sofia Papagiannaki
    if QUEUE_CONNECTION and should_send(user):
250 6c736ed7 Kostas Papadimitriou
251 6c736ed7 Kostas Papadimitriou
        from astakos.im.queue.userevent import UserEvent
252 6c736ed7 Kostas Papadimitriou
        from synnefo.lib.queue import exchange_connect, exchange_send, \
253 6c736ed7 Kostas Papadimitriou
                exchange_close
254 6c736ed7 Kostas Papadimitriou
255 59f598f1 Sofia Papagiannaki
        eventType = 'create' if not user.id else 'modify'
256 59f598f1 Sofia Papagiannaki
        body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
257 3a9f4931 Sofia Papagiannaki
        conn = exchange_connect(QUEUE_CONNECTION)
258 9e19989d Sofia Papagiannaki
        parts = urlparse(QUEUE_CONNECTION)
259 270dd48d Sofia Papagiannaki
        exchange = parts.path[1:]
260 270dd48d Sofia Papagiannaki
        routing_key = '%s.user' % exchange
261 9c01d9d1 Sofia Papagiannaki
        exchange_send(conn, routing_key, body)
262 68cb6899 Sofia Papagiannaki
        exchange_close(conn)
263 8f5a3a06 Sofia Papagiannaki
264 8f5a3a06 Sofia Papagiannaki
def _generate_invitation_code():
265 8f5a3a06 Sofia Papagiannaki
    while True:
266 8f5a3a06 Sofia Papagiannaki
        code = randint(1, 2L**63 - 1)
267 8f5a3a06 Sofia Papagiannaki
        try:
268 8f5a3a06 Sofia Papagiannaki
            Invitation.objects.get(code=code)
269 8f5a3a06 Sofia Papagiannaki
            # An invitation with this code already exists, try again
270 8f5a3a06 Sofia Papagiannaki
        except Invitation.DoesNotExist:
271 09e7393c Sofia Papagiannaki
            return code
272 09e7393c Sofia Papagiannaki
273 09e7393c Sofia Papagiannaki
def get_latest_terms():
274 09e7393c Sofia Papagiannaki
    try:
275 09e7393c Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
276 09e7393c Sofia Papagiannaki
        return term
277 09e7393c Sofia Papagiannaki
    except IndexError:
278 09e7393c Sofia Papagiannaki
        pass
279 49790d9d Sofia Papagiannaki
    return None
280 49790d9d Sofia Papagiannaki
281 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
282 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
283 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
284 49790d9d Sofia Papagiannaki
        """
285 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
286 49790d9d Sofia Papagiannaki
        ``User`` if valid.
287 49790d9d Sofia Papagiannaki

288 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
289 49790d9d Sofia Papagiannaki
        after activating.
290 49790d9d Sofia Papagiannaki

291 49790d9d Sofia Papagiannaki
        If the key is not valid or has expired, return ``None``.
292 49790d9d Sofia Papagiannaki

293 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
294 49790d9d Sofia Papagiannaki
        return ``None``.
295 49790d9d Sofia Papagiannaki

296 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
297 49790d9d Sofia Papagiannaki

298 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
299 49790d9d Sofia Papagiannaki
        """
300 49790d9d Sofia Papagiannaki
        try:
301 49790d9d Sofia Papagiannaki
            email_change = self.model.objects.get(activation_key=activation_key)
302 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
303 49790d9d Sofia Papagiannaki
                email_change.delete()
304 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
305 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
306 49790d9d Sofia Papagiannaki
            try:
307 49790d9d Sofia Papagiannaki
                AstakosUser.objects.get(email=email_change.new_email_address)
308 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
309 49790d9d Sofia Papagiannaki
                pass
310 49790d9d Sofia Papagiannaki
            else:
311 49790d9d Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
312 49790d9d Sofia Papagiannaki
            # update user
313 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
314 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
315 49790d9d Sofia Papagiannaki
            user.save()
316 49790d9d Sofia Papagiannaki
            email_change.delete()
317 49790d9d Sofia Papagiannaki
            return user
318 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
319 49790d9d Sofia Papagiannaki
            raise ValueError(_('Invalid activation key'))
320 49790d9d Sofia Papagiannaki
321 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
322 49790d9d Sofia Papagiannaki
    new_email_address = models.EmailField(_(u'new e-mail address'), help_text=_(u'Your old email address will be used until you verify your new one.'))
323 49790d9d Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, unique=True, related_name='emailchange_user')
324 49790d9d Sofia Papagiannaki
    requested_at = models.DateTimeField(default=datetime.now())
325 49790d9d Sofia Papagiannaki
    activation_key = models.CharField(max_length=40, unique=True, db_index=True)
326 49790d9d Sofia Papagiannaki
327 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
328 49790d9d Sofia Papagiannaki
329 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
330 49790d9d Sofia Papagiannaki
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
331 49790d9d Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()