GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

RegistrationManager.accept_registration()   B
last analyzed

Complexity

Conditions 5

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
c 1
b 0
f 0
dl 0
loc 36
rs 8.0894
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3
"""
4
Models of django-inspectional-registration
5
6
This is a modification of django-registration_ ``models.py``
7
The original code is written by James Bennett
8
9
.. _django-registration: https://bitbucket.org/ubernostrum/django-registration
10
11
12
Original License::
13
14
    Copyright (c) 2007-2011, James Bennett
15
    All rights reserved.
16
17
    Redistribution and use in source and binary forms, with or without
18
    modification, are permitted provided that the following conditions are
19
    met:
20
21
        * Redistributions of source code must retain the above copyright
22
        notice, this list of conditions and the following disclaimer.
23
        * Redistributions in binary form must reproduce the above
24
        copyright notice, this list of conditions and the following
25
        disclaimer in the documentation and/or other materials provided
26
        with the distribution.
27
        * Neither the name of the author nor the names of other
28
        contributors may be used to endorse or promote products derived
29
        from this software without specific prior written permission.
30
31
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34
    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35
    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36
    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37
    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41
    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42
43
"""
44
__author__ = 'Alisue <[email protected]>'
45
__all__ = (
46
    'ActivationForm', 'RegistrationForm',
47
    'RegistrationFormNoFreeEmail',
48
    'RegistrationFormTermsOfService',
49
    'RegistrationFormUniqueEmail',
50
)
51
import re
52
import sys
53
import datetime
54
55
from django.db import models
56
from django.template.loader import render_to_string
57
from django.core.exceptions import ObjectDoesNotExist
58
from django.utils.text import ugettext_lazy as _
59
from django.utils.encoding import python_2_unicode_compatible
60
61
from registration.conf import settings
62
from registration.compat import get_user_model
63
from registration.compat import user_model_label
64
from registration.compat import datetime_now
65
from registration.utils import generate_activation_key
66
from registration.utils import generate_random_password
67
from registration.utils import send_mail
68
from registration.supplements import get_supplement_class
69
from registration.compat import transaction_atomic
70
71
from logging import getLogger
72
logger = getLogger(__name__)
73
74
SHA1_RE = re.compile(r'^[a-f0-9]{40}$')
75
76
77
class RegistrationManager(models.Manager):
78
79
    """Custom manager for the ``RegistrationProfile`` model.
80
81
    The methods defined here provide shortcuts for account registration,
82
    registration acceptance, registration rejection and account activation
83
    (including generation and emailing of activation keys), and for cleaning out
84
    expired/rejected inactive accounts.
85
86
    """
87
    @transaction_atomic
88
    def register(self, username, email, site, send_email=True):
89
        """register new user with ``username`` and ``email``
90
91
        Create a new, inactive ``User``, generate a ``RegistrationProfile``
92
        and email notification to the ``User``, returning the new ``User``.
93
94
        By default, a registration email will be sent to the new user. To
95
        disable this, pass ``send_email=False``. A registration email will be
96
        generated by ``registration/registration_email.txt`` and
97
        ``registration/registration_email_subject.txt``.
98
99
        The user created by this method has no usable password and it will
100
        be set after activation.
101
102
        This method is transactional. Thus if some exception has occur in this
103
        method, the newly created user will be rollbacked.
104
105
        """
106
        User = get_user_model()
107
        new_user = User.objects.create_user(username, email, 'password')
108
        new_user.set_unusable_password()
109
        new_user.is_active = False
110
        new_user.save()
111
112
        profile = self.create(user=new_user)
113
114
        if send_email:
115
            profile.send_registration_email(site)
116
117
        return new_user
118
119
    @transaction_atomic
120
    def accept_registration(self, profile, site,
121
                            send_email=True, message=None, force=False):
122
        """accept account registration of ``profile``
123
124
        Accept account registration and email activation url to the ``User``,
125
        returning accepted ``User``. 
126
127
        By default, an acceptance email will be sent to the new user. To
128
        disable this, pass ``send_email=False``. An acceptance email will be
129
        generated by ``registration/acceptance_email.txt`` and
130
        ``registration/acceptance_email_subject.txt``.
131
132
        This method **DOES** works even after ``reject_registration`` has called
133
        (this mean the account registration has rejected previously) because 
134
        rejecting user by mistake may occur in this real world :-p If the account 
135
        registration has already accepted, returning will be ``None``
136
137
        The ``date_joined`` attribute of ``User`` updated to now in this
138
        method and ``activation_key`` of ``RegistrationProfile`` will
139
        be generated.
140
141
        """
142
        # rejected -> accepted is allowed
143
        if force or profile.status in ('untreated', 'rejected'):
144
            if force:
145
                # removing activation_key will force to create a new one
146
                profile.activation_key = None
147
            profile.status = 'accepted'
148
            profile.save()
149
150
            if send_email:
151
                profile.send_acceptance_email(site, message=message)
152
153
            return profile.user
154
        return None
155
156
    @transaction_atomic
157
    def reject_registration(self, profile, site, send_email=True, message=None):
158
        """reject account registration of ``profile``
159
160
        Reject account registration and email rejection to the ``User``,
161
        returning accepted ``User``. 
162
163
        By default, an rejection email will be sent to the new user. To
164
        disable this, pass ``send_email=False``. An rejection email will be
165
        generated by ``registration/rejection_email.txt`` and
166
        ``registration/rejection_email_subject.txt``.
167
168
        This method **DOES NOT** works after ``accept_registration`` has called
169
        (this mean the account registration has accepted previously).
170
        If the account registration has already accepted/rejected, returning 
171
        will be ``None``
172
173
        """
174
        # accepted -> rejected is not allowed
175
        if profile.status == 'untreated':
176
            profile.status = 'rejected'
177
            profile.save()
178
179
            if send_email:
180
                profile.send_rejection_email(site, message=message)
181
182
            return profile.user
183
        return None
184
185
    @transaction_atomic
186
    def activate_user(self, activation_key, site, password=None,
187
                      send_email=True, message=None, no_profile_delete=False):
188
        """activate account with ``activation_key`` and ``password``
189
190
        Activate account and email notification to the ``User``, returning 
191
        activated ``User``, ``password`` and ``is_generated``. 
192
193
        By default, an activation email will be sent to the new user. To
194
        disable this, pass ``send_email=False``. An activation email will be
195
        generated by ``registration/activation_email.txt`` and
196
        ``registration/activation_email_subject.txt``.
197
198
        This method **DOES NOT** works if the account registration has not been
199
        accepted. You must accept the account registration before activate the
200
        account. Returning will be ``None`` if the account registration has not
201
        accepted or activation key has expired.
202
203
        if passed ``password`` is ``None`` then random password will be generated
204
        and set to the ``User``. If the password is generated, ``is_generated``
205
        will be ``True``
206
207
        Use returning value like::
208
209
            activated = RegistrationProfile.objects.activate_user(activation_key)
210
211
            if activated:
212
                # Activation has success
213
                user, password, is_generated = activated
214
                # user -- a ``User`` instance of account
215
                # password -- a raw password of ``User``
216
                # is_generated -- ``True`` if the password is generated
217
218
        When activation has success, the ``RegistrationProfile`` of the ``User``
219
        will be deleted from database because the profile is no longer required.
220
221
        """
222
        try:
223
            profile = self.get(
224
                _status='accepted', activation_key=activation_key)
225
        except self.model.DoesNotExist:
226
            return None
227
        if not profile.activation_key_expired():
228
            is_generated = password is None
229
            password = password or generate_random_password(
230
                length=settings.REGISTRATION_DEFAULT_PASSWORD_LENGTH)
231
            user = profile.user
232
            user.set_password(password)
233
            user.is_active = True
234
            user.save()
235
236
            if send_email:
237
                profile.send_activation_email(site, password,
238
                                              is_generated, message=message)
239
240
            if not no_profile_delete:
241
                # the profile is no longer required
242
                profile.delete()
243
            return user, password, is_generated
244
        return None
245
246
    @transaction_atomic
247
    def delete_expired_users(self):
248
        """delete expired users from database
249
250
        Remove expired instance of ``RegistrationProfile`` and their associated
251
        ``User``.
252
253
        Accounts to be deleted are identified by searching for instance of
254
        ``RegistrationProfile`` with expired activation keys, and then checking
255
        to see if their associated ``User`` instance have the field ``is_active``
256
        set to ``False`` (it is for compatibility of django-registration); any
257
        ``User`` who is both inactive and has an expired activation key will be
258
        deleted.
259
260
        It is recommended that this method be executed regularly as part of your
261
        routine site maintenance; this application provides a custom management
262
        command which will call this method, accessible as 
263
        ``manage.py cleanupexpiredregistration`` (for just expired users) or
264
        ``manage.py cleanupregistration`` (for expired or rejected users).
265
266
        Reqularly clearing out accounts which have never been activated servers
267
        two useful purposes:
268
269
        1.  It alleviates the ocasional need to reset a ``RegistrationProfile``
270
            and/or re-send an activation email when a user does not receive or
271
            does not act upon the initial activation email; since the account
272
            will be deleted, the user will be able to simply re-register and
273
            receive a new activation key (if accepted).
274
275
        2.  It prevents the possibility of a malicious user registering one or
276
            more accounts and never activating them (thus denying the use of
277
            those username to anyone else); since those accounts will be deleted,
278
            the username will become available for use again.
279
280
        If you have a troublesome ``User`` and wish to disable their account while
281
        keeping it in the database, simply delete the associated 
282
        ``RegistrationProfile``; an inactive ``User`` which does not have an 
283
        associated ``RegistrationProfile`` will be deleted.
284
285
        """
286
        for profile in self.all():
287
            if profile.activation_key_expired():
288
                try:
289
                    user = profile.user
290
                    if not user.is_active:
291
                        user.delete()
292
                        profile.delete()    # just in case
293
                except ObjectDoesNotExist:
294
                    profile.delete()
295
296
    @transaction_atomic
297
    def delete_rejected_users(self):
298
        """delete rejected users from database
299
300
        Remove rejected instance of ``RegistrationProfile`` and their associated
301
        ``User``.
302
303
        Accounts to be deleted are identified by searching for instance of
304
        ``RegistrationProfile`` with rejected status, and then checking
305
        to see if their associated ``User`` instance have the field ``is_active``
306
        set to ``False`` (it is for compatibility of django-registration); any
307
        ``User`` who is both inactive and its registration has been rejected will
308
        be deleted.
309
310
        It is recommended that this method be executed regularly as part of your
311
        routine site maintenance; this application provides a custom management
312
        command which will call this method, accessible as 
313
        ``manage.py cleanuprejectedregistration`` (for just rejected users) or
314
        ``manage.py cleanupregistration`` (for expired or rejected users).
315
316
        Reqularly clearing out accounts which have never been activated servers
317
        two useful purposes:
318
319
        1.  It alleviates the ocasional need to reset a ``RegistrationProfile``
320
            and/or re-send an activation email when a user does not receive or
321
            does not act upon the initial activation email; since the account
322
            will be deleted, the user will be able to simply re-register and
323
            receive a new activation key (if accepted).
324
325
        2.  It prevents the possibility of a malicious user registering one or
326
            more accounts and never activating them (thus denying the use of
327
            those username to anyone else); since those accounts will be deleted,
328
            the username will become available for use again.
329
330
        If you have a troublesome ``User`` and wish to disable their account while
331
        keeping it in the database, simply delete the associated 
332
        ``RegistrationProfile``; an inactive ``User`` which does not have an 
333
        associated ``RegistrationProfile`` will be deleted.
334
335
        """
336
        for profile in self.all():
337
            if profile.status == 'rejected':
338
                try:
339
                    user = profile.user
340
                    if not user.is_active:
341
                        user.delete()
342
                        profile.delete()  # just in case
343
                except ObjectDoesNotExist:
344
                    profile.delete()
345
346
347
@python_2_unicode_compatible
348
class RegistrationProfile(models.Model):
349
350
    """Registration profile model class
351
352
    A simple profile which stores an activation key and inspection status for use
353
    during user account registration/inspection.
354
355
    Generally, you will not want to interact directly with instances of this model;
356
    the provided manager includes method for creating, accepting, rejecting and
357
    activating, as well as for cleaning out accounts which have never been activated
358
    or its registration has been rejected.
359
360
    While it is possible to use this model as the value of the ``AUTH_PROFILE_MODEL``
361
    setting, it's not recommended that you do so. This model's sole purpose is to
362
    store data temporarily during account registration, inspection and activation.
363
364
    """
365
    STATUS_LIST = (
366
        ('untreated', _('Unprocessed')),
367
        ('accepted', _('Registration accepted')),
368
        ('rejected', _('Registration rejected')),
369
    )
370
    user = models.OneToOneField(user_model_label, verbose_name=_('user'),
371
                                related_name='registration_profile',
372
                                editable=False)
373
    _status = models.CharField(_('status'), max_length=10, db_column='status',
374
                               choices=STATUS_LIST, default='untreated',
375
                               editable=False)
376
    activation_key = models.CharField(_('activation key'), max_length=40,
377
                                      null=True, default=None, editable=False)
378
379
    objects = RegistrationManager()
380
381
    class Meta:
382
        verbose_name = _('registration profile')
383
        verbose_name_plural = _('registration profiles')
384
        permissions = (
385
            ('accept_registration', 'Can accept registration'),
386
            ('reject_registration', 'Can reject registration'),
387
            ('activate_user', 'Can activate user in admin site'),
388
        )
389
390
    def _get_supplement_class(self):
391
        """get supplement class of this registration"""
392
        return get_supplement_class()
393
    supplement_class = property(_get_supplement_class)
394
395
    def _get_supplement(self):
396
        """get supplement information of this registration"""
397
        supplement_class = self.supplement_class
398
        if supplement_class:
399
            app_label = supplement_class._meta.app_label
400
            class_name = supplement_class.__name__.lower()
401
            field_name = '_%s_%s_supplement' % (app_label, class_name)
402
            return getattr(self, field_name, None)
403
        else:
404
            return None
405
    supplement = property(_get_supplement)
406
407
    def _get_status(self):
408
        """get inspection status of this profile
409
410
        this will return 'expired' for profile which is accepted but
411
        activation key has expired
412
413
        """
414
        if self.activation_key_expired():
415
            return 'expired'
416
        return self._status
417
418
    def _set_status(self, value):
419
        """set inspection status of this profile
420
421
        Setting status to ``'accepted'`` will generate activation key
422
        and update ``date_joined`` attribute to now of associated ``User``
423
424
        Setting status not to ``'accepted'`` will remove activation key
425
        of this profile.
426
427
        """
428
        self._status = value
429
        # Automatically generate activation key for accepted profile
430
        if value == 'accepted' and not self.activation_key:
431
            username = self.user.username
432
            self.activation_key = generate_activation_key(username)
433
            # update user's date_joined
434
            self.user.date_joined = datetime_now()
435
            self.user.save()
436
        elif value != 'accepted' and self.activation_key:
437
            self.activation_key = None
438
    status = property(_get_status, _set_status)
439
440
    def get_status_display(self):
441
        """get human readable status"""
442
        sl = list(self.STATUS_LIST)
443
        sl.append(('expired', _('Activation key has expired')))
444
        sl = dict(sl)
445
        return sl.get(self.status)
446
    get_status_display.short_description = _("status")
447
448
    def __str__(self):
449
        return "Registration information for %s" % self.user
450
451
    def activation_key_expired(self):
452
        """get whether the activation key of this profile has expired
453
454
        Determine whether this ``RegistrationProfiel``'s activation key has
455
        expired, returning a boolean -- ``True`` if the key has expired.
456
457
        Key expiration is determined by a two-step process:
458
459
        1.  If the inspection status is not ``'accepted'``, the key is set to
460
            ``None``. In this case, this method returns ``False`` because these
461
            profiles are not treated yet or rejected by inspector.
462
463
        2.  Otherwise, the date the user signed up (which automatically updated
464
            in registration acceptance) is incremented by the number of days 
465
            specified in the setting ``ACCOUNT_ACTIVATION_DAYS`` (which should
466
            be the number of days after acceptance during which a user is allowed
467
            to activate their account); if the result is less than or equal to
468
            the current date, the key has expired and this method return ``True``.
469
470
        """
471
        if self._status != 'accepted':
472
            return False
473
        expiration_date = datetime.timedelta(
474
            days=settings.ACCOUNT_ACTIVATION_DAYS)
475
        expired = self.user.date_joined + expiration_date <= datetime_now()
476
        return expired
477
    activation_key_expired.boolean = True
478
    activation_key_expired.short_description = _(
479
        'Activation Key Expired?'
480
    )
481
482
    def _send_email(self, site, action, extra_context=None):
483
        context = {
484
            'user': self.user,
485
            'site': site,
486
        }
487
        if action != 'activation':
488
            # the profile was deleted in 'activation' action
489
            context['profile'] = self
490
491
        if extra_context:
492
            context.update(extra_context)
493
494
        subject = render_to_string(
495
            'registration/%s_email_subject.txt' % action, context)
496
        subject = ''.join(subject.splitlines())
497
        message = render_to_string(
498
            'registration/%s_email.txt' % action, context)
499
500
        mail_from = getattr(settings, 'REGISTRATION_FROM_EMAIL', '') or \
501
                        settings.DEFAULT_FROM_EMAIL
502
        send_mail(subject, message, mail_from, [self.user.email])
503
504
    def send_registration_email(self, site):
505
        """send registration email to the user associated with this profile
506
507
        Send a registration email to the ``User`` associated with this
508
        ``RegistrationProfile``.
509
510
        The registration email will make use of two templates:
511
512
        ``registration/registration_email_subject.txt``
513
            This template will be used for the subject line of the email. Because
514
            it is used as the subject line of an email, this template's output
515
            **must** be only a single line of text; output longer than one line
516
            will be forcibly joined into only a single line.
517
518
        ``registration/registration_email.txt``
519
            This template will be used for the body of the email
520
521
        These templates will each receive the following context variables:
522
523
        ``site``
524
            An object representing the site on which the user registered;this is
525
            an instance of ``django.contrib.sites.models.Site`` or
526
            ``django.contrib.sites.models.RequestSite``
527
528
        ``user``
529
            A ``User`` instance of the registration.
530
531
        ``profile``
532
            A ``RegistrationProfile`` instance of the registration
533
534
        """
535
        self._send_email(site, 'registration')
536
537
    def send_acceptance_email(self, site, message=None):
538
        """send acceptance email to the user associated with this profile
539
540
        Send an acceptance email to the ``User`` associated with this
541
        ``RegistrationProfile``.
542
543
        The acceptance email will make use of two templates:
544
545
        ``registration/acceptance_email_subject.txt``
546
            This template will be used for the subject line of the email. Because
547
            it is used as the subject line of an email, this template's output
548
            **must** be only a single line of text; output longer than one line
549
            will be forcibly joined into only a single line.
550
551
        ``registration/acceptance_email.txt``
552
            This template will be used for the body of the email
553
554
        These templates will each receive the following context variables:
555
556
        ``site``
557
            An object representing the site on which the user registered;this is
558
            an instance of ``django.contrib.sites.models.Site`` or
559
            ``django.contrib.sites.models.RequestSite``
560
561
        ``user``
562
            A ``User`` instance of the registration.
563
564
        ``profile``
565
            A ``RegistrationProfile`` instance of the registration
566
567
        ``activation_key``
568
            The activation key for tne new account. Use following code to get
569
            activation url in the email body::
570
571
                http://{{ site.domain }}
572
                {% url 'registration_activate' activation_key=activation_key %}
573
574
        ``expiration_days``
575
            The number of days remaining during which the account may be activated.
576
577
        ``message``
578
            A message from inspector. In default template, it is not shown.
579
580
        """
581
        extra_context = {
582
            'activation_key': self.activation_key,
583
            'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
584
            'message': message,
585
        }
586
        self._send_email(site, 'acceptance', extra_context)
587
588
    def send_rejection_email(self, site, message=None):
589
        """send rejection email to the user associated with this profile
590
591
        Send a rejection email to the ``User`` associated with this
592
        ``RegistrationProfile``.
593
594
        The rejection email will make use of two templates:
595
596
        ``registration/rejection_email_subject.txt``
597
            This template will be used for the subject line of the email. Because
598
            it is used as the subject line of an email, this template's output
599
            **must** be only a single line of text; output longer than one line
600
            will be forcibly joined into only a single line.
601
602
        ``registration/rejection_email.txt``
603
            This template will be used for the body of the email
604
605
        These templates will each receive the following context variables:
606
607
        ``site``
608
            An object representing the site on which the user registered;this is
609
            an instance of ``django.contrib.sites.models.Site`` or
610
            ``django.contrib.sites.models.RequestSite``
611
612
        ``user``
613
            A ``User`` instance of the registration.
614
615
        ``profile``
616
            A ``RegistrationProfile`` instance of the registration
617
618
        ``message``
619
            A message from inspector. In default template, it is used for explain
620
            why the account registration has been rejected.
621
622
        """
623
        extra_context = {
624
            'message': message,
625
        }
626
        self._send_email(site, 'rejection', extra_context)
627
628
    def send_activation_email(self, site, password=None, is_generated=False,
629
                              message=None):
630
        """send activation email to the user associated with this profile
631
632
        Send a activation email to the ``User`` associated with this
633
        ``RegistrationProfile``.
634
635
        The activation email will make use of two templates:
636
637
        ``registration/activation_email_subject.txt``
638
            This template will be used for the subject line of the email. Because
639
            it is used as the subject line of an email, this template's output
640
            **must** be only a single line of text; output longer than one line
641
            will be forcibly joined into only a single line.
642
643
        ``registration/activation_email.txt``
644
            This template will be used for the body of the email
645
646
        These templates will each receive the following context variables:
647
648
        ``site``
649
            An object representing the site on which the user registered;this is
650
            an instance of ``django.contrib.sites.models.Site`` or
651
            ``django.contrib.sites.models.RequestSite``
652
653
        ``user``
654
            A ``User`` instance of the registration.
655
656
        ``password``
657
            A raw password of ``User``. Use this to tell user to them password
658
            when the password is generated
659
660
        ``is_generated``
661
            A boolean -- ``True`` if the password is generated. Don't forget to
662
            tell user to them password when the password is generated
663
664
        ``message``
665
            A message from inspector. In default template, it is not shown.
666
667
        """
668
        extra_context = {
669
            'password': password,
670
            'is_generated': is_generated,
671
            'message': message,
672
        }
673
        self._send_email(site, 'activation', extra_context)
674