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.
Completed
Push — master ( 0c3d5f...5b7906 )
by Lambda
6s
created

RegistrationManager   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 266
Duplicated Lines 0 %
Metric Value
dl 0
loc 266
rs 10
wmc 25

6 Methods

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