├── tests ├── __init__.py ├── templates │ └── 404.html ├── urls.py └── settings.py ├── locale ├── src └── registration │ ├── locale │ └── zh_Hans │ ├── migrations │ ├── __init__.py │ ├── 0003_registration_profile_status.py │ ├── 0002_default_supplement.py │ └── 0001_initial.py │ ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── cleanupregistration.py │ │ ├── cleanup_rejected_registrations.py │ │ ├── cleanup_expired_registrations.py │ │ └── cleanup_registrations.py │ ├── south_migrations │ ├── __init__.py │ ├── 0001_initial.py │ ├── 0003_status.py │ ├── 0004_activation_keys.py │ ├── 0002_auto__add_field_registrationprofile__status__chg_field_registrationpro.py │ └── 0005_auto__add_defaultregistrationsupplement__chg_field_registrationprofile.py │ ├── supplements │ ├── default │ │ ├── __init__.py │ │ └── models.py │ ├── __init__.py │ └── base.py │ ├── contrib │ ├── autologin │ │ ├── models.py │ │ ├── conf.py │ │ ├── __init__.py │ │ └── tests.py │ ├── notification │ │ ├── models.py │ │ ├── templates │ │ │ └── registration │ │ │ │ ├── notification_email_subject.txt │ │ │ │ └── notification_email.txt │ │ ├── tests │ │ │ ├── urls.py │ │ │ └── __init__.py │ │ ├── conf.py │ │ └── __init__.py │ └── __init__.py │ ├── templates │ ├── registration │ │ ├── activation_email_subject.txt │ │ ├── acceptance_email_subject.txt │ │ ├── registration_email_subject.txt │ │ ├── rejection_email_subject.txt │ │ ├── rejection_email.txt │ │ ├── registration_email.txt │ │ ├── logout.html │ │ ├── activation_complete.html │ │ ├── registration_closed.html │ │ ├── base.html │ │ ├── login.html │ │ ├── acceptance_email.txt │ │ ├── activation_form.html │ │ ├── registration_complete.html │ │ ├── registration_form.html │ │ └── activation_email.txt │ └── admin │ │ └── registration │ │ └── registrationprofile │ │ └── change_form.html │ ├── __init__.py │ ├── tests │ ├── compat.py │ ├── __init__.py │ ├── utils.py │ ├── mock.py │ ├── test_supplements.py │ └── test_forms.py │ ├── signals.py │ ├── conf.py │ ├── utils.py │ ├── urls.py │ ├── backends │ ├── __init__.py │ └── base.py │ ├── compat.py │ ├── admin │ └── forms.py │ └── forms.py ├── requirements.txt ├── requirements-test.txt ├── docs ├── modules.rst ├── _static │ └── img │ │ └── difference_summary.png ├── registration.backends.default.rst ├── registration.management.rst ├── about_registration_backend.rst ├── registration.contrib.rst ├── registration.admin.rst ├── registration.migrations.rst ├── registration.supplements.default.rst ├── registration.backends.rst ├── about_registration_contrib.rst ├── registration.contrib.notification.tests.rst ├── registration.supplements.rst ├── registration.contrib.notification.rst ├── registration.contrib.autologin.rst ├── registration.management.commands.rst ├── registration.south_migrations.rst ├── registration.rst ├── registration.tests.rst ├── about_registration_supplement.rst ├── about_registration_signals.rst ├── about_registration_settings.rst ├── quickmigrations.rst ├── faq.rst ├── quicktutorials.rst ├── make.bat ├── index.rst ├── Makefile └── about_registration_templates.rst ├── setup.cfg ├── requirements-docs.txt ├── .coveragerc ├── MANIFEST.in ├── .gitignore ├── .travis.yml ├── tox.ini ├── manage.py ├── runtests.py ├── setup.py └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /locale: -------------------------------------------------------------------------------- 1 | src/registration/locale/ -------------------------------------------------------------------------------- /src/registration/locale/zh_Hans: -------------------------------------------------------------------------------- 1 | zh-hans -------------------------------------------------------------------------------- /src/registration/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/registration/management/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/registration/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/registration/south_migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/registration/supplements/default/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/templates/404.html: -------------------------------------------------------------------------------- 1 | 404 Not found 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | app_version>=0.1.1 2 | django-appconf 3 | -------------------------------------------------------------------------------- /src/registration/contrib/autologin/models.py: -------------------------------------------------------------------------------- 1 | # fake 2 | -------------------------------------------------------------------------------- /src/registration/contrib/notification/models.py: -------------------------------------------------------------------------------- 1 | # fake 2 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | app_version>=0.1.1 2 | django-appconf 3 | django-override-settings 4 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | src 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | registration 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [upload_docs] 2 | upload-dir = docs/_build/html 3 | 4 | [aliases] 5 | publish = sdist upload 6 | 7 | -------------------------------------------------------------------------------- /docs/_static/img/difference_summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambdalisue/django-inspectional-registration/HEAD/docs/_static/img/difference_summary.png -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | app_version>=0.1.1 2 | django>=1.3 3 | django-appconf 4 | django-override-settings 5 | PyYAML 6 | sphinx 7 | south 8 | sphinxcontrib-napoleon 9 | sphinx-rtd-theme 10 | -------------------------------------------------------------------------------- /src/registration/templates/registration/activation_email_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans with site.name as site_name %}Your account was activated -- {{ site_name }}{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /src/registration/templates/registration/acceptance_email_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans with site.name as site_name %}Your registration was accepted -- {{ site_name }}{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /src/registration/templates/registration/registration_email_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans with site.name as site_name %}Your registration was created -- {{ site_name }}{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /src/registration/templates/registration/rejection_email_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans with site.name as site_name %}Your registration was rejected -- {{ site_name }}{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /src/registration/contrib/notification/templates/registration/notification_email_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans with site.name as site_name %}A new registration was created by {{ user }} -- {{ site_name }}{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /src/registration/management/commands/cleanupregistration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | # This is for compatibility to django-registration 4 | from registration.management.commands.cleanup_registrations import * 5 | -------------------------------------------------------------------------------- /src/registration/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from app_version import get_versions 4 | __version__, VERSION = get_versions('django-inspectional-registration', 5 | allow_ambiguous=True) 6 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | include= 3 | src/*.py 4 | tests/*.py 5 | omit= 6 | src/registration/__init__.py 7 | src/registration/compat.py 8 | src/registration/migrations/*.py 9 | exclude_lines= 10 | pragma: no cover 11 | if __name__ == .__main__.: 12 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include requirements.txt 3 | include requirements-test.txt 4 | include requirements-docs.txt 5 | recursive-include src/registration/locale *.po *.mo 6 | recursive-include src/registration/templates *.html 7 | recursive-include src/registration/templates *.txt 8 | -------------------------------------------------------------------------------- /docs/registration.backends.default.rst: -------------------------------------------------------------------------------- 1 | registration.backends.default package 2 | ===================================== 3 | 4 | Module contents 5 | --------------- 6 | 7 | .. automodule:: registration.backends.default 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ropeproject 2 | .project 3 | *.pyc 4 | *.egg 5 | *.egg-info 6 | .tox 7 | .coverage 8 | database.db 9 | local_settings.py 10 | /dist 11 | /sdist 12 | /tests/*.db 13 | /tests/static/collection 14 | /tests/media 15 | /docs/_build 16 | /issues 17 | /build 18 | .idea 19 | .python-version 20 | *.mo 21 | -------------------------------------------------------------------------------- /src/registration/tests/compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | """ 5 | __author__ = 'Alisue ' 6 | try: 7 | from django.test.utils import override_settings 8 | except ImportError: 9 | from override_settings import override_settings 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - 2.6 5 | - 2.7 6 | - 3.3 7 | - 3.4 8 | - 3.5 9 | 10 | install: 11 | - pip install tox tox-travis 12 | - pip install coverage coveralls 13 | 14 | script: 15 | - tox -r 16 | 17 | after_success: 18 | - coverage report 19 | - coveralls 20 | -------------------------------------------------------------------------------- /src/registration/templates/registration/rejection_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Dear {{ user }},{% endblocktrans %} 3 | 4 | {% trans 'I am afraid that your account registration was rejected by inspector.' %} 5 | 6 | {% if message %} 7 | {% trans 'Rejection reasons:' %} 8 | {{ message }} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from django.contrib import admin 4 | from registration.compat import url, patterns, include 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | url(r'^admin/', include(admin.site.urls)), 9 | url(r'^registration/', include('registration.urls')), 10 | ) 11 | -------------------------------------------------------------------------------- /src/registration/templates/registration/registration_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Dear {{ user }},{% endblocktrans %} 3 | {% trans 'Your account registration was created successfully.' %} 4 | 5 | {% trans 'Please wait until the inspector verify your registration.' %} 6 | {% trans 'You will receive an acceptance or rejection email within several days.' %} 7 | -------------------------------------------------------------------------------- /docs/registration.management.rst: -------------------------------------------------------------------------------- 1 | registration.management package 2 | =============================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | registration.management.commands 10 | 11 | Module contents 12 | --------------- 13 | 14 | .. automodule:: registration.management 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /src/registration/templates/registration/logout.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Logged out' %}{% endblock %} 5 | 6 | {% block content_title %}

{% trans 'Logged out' %}

{% endblock %} 7 | 8 | {% block contents %} 9 | {{ block.super }} 10 |

{% trans 'You are logged out.' %}

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /docs/about_registration_backend.rst: -------------------------------------------------------------------------------- 1 | **************************************************** 2 | About Registration Backend 3 | **************************************************** 4 | 5 | Registration is handled by Registration Backend. See 6 | :py:class:`registration.backends.RegistrationBackendBase` and 7 | :py:class:`registration.backends.default.DefaultRegistrationBackend` for more 8 | detail. 9 | -------------------------------------------------------------------------------- /docs/registration.contrib.rst: -------------------------------------------------------------------------------- 1 | registration.contrib package 2 | ============================ 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | registration.contrib.autologin 10 | registration.contrib.notification 11 | 12 | Module contents 13 | --------------- 14 | 15 | .. automodule:: registration.contrib 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | -------------------------------------------------------------------------------- /src/registration/contrib/autologin/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | """ 5 | __author__ = 'Alisue ' 6 | from django.conf import settings 7 | from appconf import AppConf 8 | 9 | 10 | class InspectionalRegistrationAutoLoginAppConf(AppConf): 11 | AUTO_LOGIN = True 12 | 13 | class Meta: 14 | prefix = 'registration' 15 | -------------------------------------------------------------------------------- /src/registration/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Contributions of django-inspectional-registration 5 | 6 | ``notification`` 7 | If you want to receive notification emails when new users have been registered 8 | in the site. 9 | 10 | ``autologin`` 11 | The activated user will be automatically login the site after activation. 12 | """ 13 | -------------------------------------------------------------------------------- /src/registration/contrib/notification/templates/registration/notification_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans with site.name as site_name %}A new registration of {{ site_name }} was created by {{ user }}.{% endblocktrans %} 3 | 4 | {% trans 'Please click the following url and inspect his/her registration.' %} 5 | 6 | http://{{ site.domain }}{% url 'admin:index' %}registration/registrationprofile/{{ profile.pk }}/ 7 | -------------------------------------------------------------------------------- /src/registration/templates/registration/activation_complete.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Activation was complete' %}{% endblock %} 5 | 6 | {% block content_title %}

{% trans 'Activation was complete' %}

{% endblock %} 7 | 8 | {% block contents %} 9 | {{ block.super }} 10 |

{% trans 'Your account was activated' %}

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /src/registration/templates/registration/registration_closed.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Registration is closed' %}{% endblock %} 5 | 6 | {% block content_title %}

{% trans 'Registration is closed' %}

{% endblock %} 7 | 8 | {% block contents %} 9 | {{ block.super }} 10 |

{% trans 'I am afraid that registration is currently closed.' %}

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /src/registration/templates/registration/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}{% endblock %} 6 | 9 | 10 | 11 | {% block content_title %}{% endblock %} 12 | {% block contents %} 13 | {% endblock %} 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/registration/contrib/notification/tests/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from registration.compat import url 4 | from registration.compat import patterns 5 | from registration.compat import include 6 | 7 | from django.contrib import admin 8 | admin.autodiscover() 9 | 10 | # default template used in template 11 | # require admin site 12 | urlpatterns = patterns('', 13 | url(r'^admin/', include(admin.site.urls)), 14 | ) 15 | -------------------------------------------------------------------------------- /src/registration/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import django 4 | 5 | if django.VERSION < (1, 6): 6 | from registration.tests.test_admin import * 7 | from registration.tests.test_models import * 8 | from registration.tests.test_forms import * 9 | from registration.tests.test_views import * 10 | from registration.tests.test_backends import * 11 | from registration.tests.test_supplements import * 12 | 13 | -------------------------------------------------------------------------------- /docs/registration.admin.rst: -------------------------------------------------------------------------------- 1 | registration.admin package 2 | ========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | registration.admin.forms module 8 | ------------------------------- 9 | 10 | .. automodule:: registration.admin.forms 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: registration.admin 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /src/registration/templates/admin/registration/registrationprofile/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form.html" %} 2 | {% load i18n %} 3 | 4 | {% block content_title %} 5 |

6 | {% blocktrans with original.user as user %}Inspect the registration of {{ user }}{% endblocktrans %} 7 |

8 | {% endblock %} 9 | 10 | {% block content %} 11 |

{% blocktrans %}Please select an action and click the Save button on bottom.{% endblocktrans %}

12 | {{ block.super }} 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /docs/registration.migrations.rst: -------------------------------------------------------------------------------- 1 | registration.migrations package 2 | =============================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | registration.migrations.0001_initial module 8 | ------------------------------------------- 9 | 10 | .. automodule:: registration.migrations.0001_initial 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: registration.migrations 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /src/registration/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Login' %}{% endblock %} 5 | 6 | {% block content_title %}

{% trans 'Login' %}

{% endblock %} 7 | 8 | {% block contents %} 9 | {{ block.super }} 10 |

{% trans 'Please fill your username and password to login' %}

11 |
{% csrf_token %} 12 | {{ form.as_p }} 13 |

14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /src/registration/templates/registration/acceptance_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Dear {{ user }},{% endblocktrans %} 3 | 4 | {% blocktrans with site.name as site_name %}Your registration of {{ site_name }} was accepted by inspector.{% endblocktrans %} 5 | 6 | {% trans 'Please click the following url and set your account password to finish activation.' %} 7 | 8 | http://{{ site.domain }}{% url 'registration_activate' activation_key=activation_key %} 9 | 10 | {% blocktrans %}The activation link above will be expired in {{ expiration_days }} days.{% endblocktrans %} 11 | -------------------------------------------------------------------------------- /docs/registration.supplements.default.rst: -------------------------------------------------------------------------------- 1 | registration.supplements.default package 2 | ======================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | registration.supplements.default.models module 8 | ---------------------------------------------- 9 | 10 | .. automodule:: registration.supplements.default.models 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: registration.supplements.default 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /src/registration/tests/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | """ 5 | __author__ = 'Alisue ' 6 | 7 | 8 | def with_apps(*apps): 9 | """ 10 | Class decorator that makes sure the passed apps are present in 11 | INSTALLED_APPS. 12 | """ 13 | from django.conf import settings 14 | from registration.tests.compat import override_settings 15 | apps_set = set(settings.INSTALLED_APPS) 16 | apps_set.update(apps) 17 | return override_settings(INSTALLED_APPS=list(apps_set)) 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/registration/templates/registration/activation_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Activate your account' %}{% endblock %} 5 | 6 | {% block content_title %}

{% trans 'Activate your account' %}

{% endblock %} 7 | 8 | {% block contents %} 9 | {{ block.super }} 10 |

{% trans 'Please fill the password to complete account activation' %}

11 |
{% csrf_token %} 12 | {{ form.as_p }} 13 |

14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /docs/registration.backends.rst: -------------------------------------------------------------------------------- 1 | registration.backends package 2 | ============================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | registration.backends.default 10 | 11 | Submodules 12 | ---------- 13 | 14 | registration.backends.base module 15 | --------------------------------- 16 | 17 | .. automodule:: registration.backends.base 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: registration.backends 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /src/registration/templates/registration/registration_complete.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Registration was complete' %}{% endblock %} 5 | 6 | {% block content_title %}

{% trans 'Registration was complete' %}

{% endblock %} 7 | 8 | {% block contents %} 9 | {{ block.super }} 10 |

{% trans 'Your registration was created successfully.' %}

11 |

{% trans 'Please wait until the inspector verify your registration.' %} 12 | {% trans 'You will receive an acceptance or rejection email within several days.' %}

13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /docs/about_registration_contrib.rst: -------------------------------------------------------------------------------- 1 | **************************************************************** 2 | About Registration Contributions 3 | **************************************************************** 4 | 5 | How to use contribution 6 | ============================================== 7 | Registration contributions are simple django app thus you just need to add the 8 | path of the contribution to ``INSTALLED_APPS``. See the documentation of each 9 | contribution for more detail. 10 | 11 | 12 | .. currentmodule:: registration.contrib 13 | .. autosummary:: 14 | 15 | autologin 16 | notification 17 | 18 | -------------------------------------------------------------------------------- /src/registration/templates/registration/registration_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'registration/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Registration' %}{% endblock %} 5 | 6 | {% block content_title %}

{% trans 'Registration' %}

{% endblock %} 7 | 8 | {% block contents %} 9 | {{ block.super }} 10 |

{% trans 'Please fill the following fields to create your registration' %}

11 |
{% csrf_token %} 12 | {{ form.as_p }} 13 | {{ supplement_form.as_p }} 14 |

15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /docs/registration.contrib.notification.tests.rst: -------------------------------------------------------------------------------- 1 | registration.contrib.notification.tests package 2 | =============================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | registration.contrib.notification.tests.urls module 8 | --------------------------------------------------- 9 | 10 | .. automodule:: registration.contrib.notification.tests.urls 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: registration.contrib.notification.tests 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /docs/registration.supplements.rst: -------------------------------------------------------------------------------- 1 | registration.supplements package 2 | ================================ 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | registration.supplements.default 10 | 11 | Submodules 12 | ---------- 13 | 14 | registration.supplements.base module 15 | ------------------------------------ 16 | 17 | .. automodule:: registration.supplements.base 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: registration.supplements 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /src/registration/migrations/0003_registration_profile_status.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('registration', '0002_default_supplement'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='registrationprofile', 15 | name='_status', 16 | field=models.CharField(choices=[('untreated', 'Unprocessed'), ('accepted', 'Registration accepted'), ('rejected', 'Registration rejected')], db_column='status', default='untreated', editable=False, max_length=10, verbose_name='status'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/registration/contrib/notification/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | """ 5 | __author__ = 'Alisue ' 6 | from django.conf import settings 7 | from appconf import AppConf 8 | 9 | 10 | class InspectionalRegistrationNotificationAppConf(AppConf): 11 | NOTIFICATION = True 12 | NOTIFICATION_ADMINS = True 13 | NOTIFICATION_MANAGERS = True 14 | NOTIFICATION_RECIPIENTS = None 15 | 16 | NOTIFICATION_EMAIL_TEMPLATE_NAME = ( 17 | r'registration/notification_email.txt') 18 | NOTIFICATION_EMAIL_SUBJECT_TEMPLATE_NAME = ( 19 | r'registration/notification_email_subject.txt') 20 | 21 | class Meta: 22 | prefix = 'registration' 23 | -------------------------------------------------------------------------------- /src/registration/supplements/default/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | A simple registration supplement model which requires ``remarks`` 5 | """ 6 | __author__ = 'Alisue ' 7 | from django.db import models 8 | from django.utils.text import ugettext_lazy as _ 9 | from django.utils.encoding import python_2_unicode_compatible 10 | from registration.supplements.base import RegistrationSupplementBase 11 | 12 | 13 | @python_2_unicode_compatible 14 | class DefaultRegistrationSupplement(RegistrationSupplementBase): 15 | """A simple registration supplement model which requires remarks""" 16 | remarks = models.TextField(_('remarks')) 17 | 18 | def __str__(self): 19 | """return a summary of this addition""" 20 | return self.remarks 21 | 22 | # it is required to specify from django 1.6 23 | class Meta: 24 | app_label = 'registration' 25 | -------------------------------------------------------------------------------- /docs/registration.contrib.notification.rst: -------------------------------------------------------------------------------- 1 | registration.contrib.notification package 2 | ========================================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | registration.contrib.notification.tests 10 | 11 | Submodules 12 | ---------- 13 | 14 | registration.contrib.notification.conf module 15 | --------------------------------------------- 16 | 17 | .. automodule:: registration.contrib.notification.conf 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | registration.contrib.notification.models module 23 | ----------------------------------------------- 24 | 25 | .. automodule:: registration.contrib.notification.models 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | 31 | Module contents 32 | --------------- 33 | 34 | .. automodule:: registration.contrib.notification 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | -------------------------------------------------------------------------------- /docs/registration.contrib.autologin.rst: -------------------------------------------------------------------------------- 1 | registration.contrib.autologin package 2 | ====================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | registration.contrib.autologin.conf module 8 | ------------------------------------------ 9 | 10 | .. automodule:: registration.contrib.autologin.conf 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | registration.contrib.autologin.models module 16 | -------------------------------------------- 17 | 18 | .. automodule:: registration.contrib.autologin.models 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | registration.contrib.autologin.tests module 24 | ------------------------------------------- 25 | 26 | .. automodule:: registration.contrib.autologin.tests 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: registration.contrib.autologin 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /src/registration/templates/registration/activation_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}Dear {{ user }},{% endblocktrans %} 3 | {% if is_generated %} 4 | {% blocktrans with site.name as site_name %}Your account of {{ site_name }} was activated by inspector{% endblocktrans %} 5 | 6 | {% trans 'The password of your account was generated automatically and displayed below.' %} 7 | {% trans 'Please click the following url to login.' %} 8 | {% trans 'It is strongly recommended that you change your initial password to something more secure.' %} 9 | 10 | {% trans '::Your account information::' %} 11 | {% blocktrans %}USERNAME: {{ user }}{% endblocktrans %} 12 | {% blocktrans %}PASSWORD: {{ password }}{% endblocktrans %} 13 | 14 | http://{{ site.domain }}{% url 'login' %} 15 | {% else %} 16 | {% blocktrans with site.name as site_name %}Your account of {{ site_name }} was successfully activated{% endblocktrans %} 17 | 18 | {% trans 'Please click the following url to login.' %} 19 | 20 | {% trans '::Your account information::' %} 21 | {% blocktrans %}USERNAME: {{ user }}{% endblocktrans %} 22 | 23 | http://{{ site.domain }}{% url 'login' %} 24 | {% endif %} 25 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py26-django{15,16}, 4 | py27-django{15,16,17,18,19,110}, 5 | py33-django{15,16,17,18}, 6 | py34-django{15,16,17,18,19,110}, 7 | py35-django{18,19,110}, 8 | docs 9 | 10 | [tox:travis] 11 | 2.6 = py26-django{15,16} 12 | 2.7 = py27-django{15,16,17,18,19,110} 13 | 3.3 = py33-django{15,16,17,18} 14 | 3.4 = py34-django{15,16,17,18,19,110} 15 | 3.5 = py35-django{18,19,110} 16 | 17 | [testenv] 18 | basepython = 19 | py26: python2.6 20 | py27: python2.7 21 | py33: python3.3 22 | py34: python3.4 23 | py35: python3.5 24 | deps= 25 | django15: django>=1.5,<1.6 26 | django16: django>=1.6,<1.7 27 | django17: django>=1.7,<1.8 28 | django18: django>=1.8,<1.9 29 | django19: django>=1.9,<1.10 30 | django110: django>=1.10,<1.11 31 | -rrequirements-test.txt 32 | coverage 33 | commands= 34 | {envbindir}/coverage run --source=src/registration runtests.py [] 35 | coverage report 36 | 37 | [testenv:docs] 38 | basepython=python 39 | changedir=docs 40 | deps=-rrequirements-docs.txt 41 | commands= 42 | make clean 43 | make html 44 | whitelist_externals= 45 | make 46 | -------------------------------------------------------------------------------- /src/registration/signals.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Django custom signals used in django-inspectional-registration 5 | 6 | SIGNALS: 7 | user_registered 8 | sent when user has registered by Backend 9 | 10 | user_accepted 11 | sent when user registration has accepted by Backend 12 | 13 | user_rejected 14 | sent when user registration has rejected by Backend 15 | 16 | user_activated 17 | sent when user has activated by Backend 18 | """ 19 | __author__ = 'Alisue ' 20 | __all__ = ( 21 | 'user_registered', 'user_accepted', 22 | 'user_rejected', 'user_activated' 23 | ) 24 | from django.dispatch import Signal 25 | 26 | # A new user has registered 27 | user_registered = Signal(providing_args=['user', 'profile', 'request']) 28 | 29 | # A user has been accepted his/her registration 30 | user_accepted = Signal(providing_args=['user', 'profile', 'request']) 31 | 32 | # A user has been rejected his/her registration 33 | user_rejected = Signal(providing_args=['user', 'profile', 'request']) 34 | 35 | # A user has activated his/her account. 36 | user_activated = Signal(providing_args=['user', 'password', 'is_generated', 37 | 'request']) 38 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Django 1.2 - 1.6 compatible manage.py 5 | Modify this script to make your own manage.py 6 | """ 7 | __author__ = 'Alisue ' 8 | import os 9 | import sys 10 | 11 | 12 | if __name__ == '__main__': 13 | # add extra sys.path 14 | root = os.path.abspath(os.path.dirname(__file__)) 15 | extra_paths = (root, os.path.join(root, 'src')) 16 | for extra_path in extra_paths: 17 | if extra_path in sys.path: 18 | sys.path.remove(extra_path) 19 | sys.path.insert(0, extra_path) 20 | # set DJANGO_SETTINGS_MODULE 21 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') 22 | 23 | try: 24 | # django 1.4 and above 25 | # https://docs.djangoproject.com/en/1.4/releases/1.4/ 26 | from django.core.management import execute_from_command_line 27 | execute_from_command_line(sys.argv) 28 | except ImportError: 29 | # check django version 30 | import django 31 | if django.VERSION[:2] >= (1.4): 32 | # there are real problems on importing 33 | raise 34 | from django.core.management import execute_manager 35 | settings = __import__(os.environ['DJANGO_SETTINGS_MODULE']) 36 | execute_manager(settings) 37 | -------------------------------------------------------------------------------- /src/registration/migrations/0002_default_supplement.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [ 10 | ('registration', '0001_initial'), 11 | ] 12 | operations = [] 13 | 14 | if settings.REGISTRATION_SUPPLEMENT_CLASS == 'registration.supplements.default.models.DefaultRegistrationSupplement': 15 | operations += [ 16 | migrations.CreateModel( 17 | name='DefaultRegistrationSupplement', 18 | fields=[ 19 | ('id', models.AutoField(verbose_name='ID', serialize=False, primary_key=True, auto_created=True)), 20 | ('remarks', models.TextField(verbose_name='remarks')), 21 | ], 22 | options={ 23 | }, 24 | bases=(models.Model,), 25 | ), 26 | migrations.AddField( 27 | model_name='defaultregistrationsupplement', 28 | name='registration_profile', 29 | field=models.OneToOneField(editable=False, related_name='_registration_defaultregistrationsupplement_supplement', verbose_name='registration profile', to='registration.RegistrationProfile'), 30 | preserve_default=True, 31 | ), 32 | ] 33 | 34 | -------------------------------------------------------------------------------- /docs/registration.management.commands.rst: -------------------------------------------------------------------------------- 1 | registration.management.commands package 2 | ======================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | registration.management.commands.cleanup_expired_registrations module 8 | --------------------------------------------------------------------- 9 | 10 | .. automodule:: registration.management.commands.cleanup_expired_registrations 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | registration.management.commands.cleanup_registrations module 16 | ------------------------------------------------------------- 17 | 18 | .. automodule:: registration.management.commands.cleanup_registrations 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | registration.management.commands.cleanup_rejected_registrations module 24 | ---------------------------------------------------------------------- 25 | 26 | .. automodule:: registration.management.commands.cleanup_rejected_registrations 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | registration.management.commands.cleanupregistration module 32 | ----------------------------------------------------------- 33 | 34 | .. automodule:: registration.management.commands.cleanupregistration 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: registration.management.commands 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /docs/registration.south_migrations.rst: -------------------------------------------------------------------------------- 1 | registration.south_migrations package 2 | ===================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | registration.south_migrations.0001_initial module 8 | ------------------------------------------------- 9 | 10 | .. automodule:: registration.south_migrations.0001_initial 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | registration.south_migrations.0002_auto__add_field_registrationprofile__status__chg_field_registrationpro module 16 | ---------------------------------------------------------------------------------------------------------------- 17 | 18 | .. automodule:: registration.south_migrations.0002_auto__add_field_registrationprofile__status__chg_field_registrationpro 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | registration.south_migrations.0003_status module 24 | ------------------------------------------------ 25 | 26 | .. automodule:: registration.south_migrations.0003_status 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | registration.south_migrations.0004_activation_keys module 32 | --------------------------------------------------------- 33 | 34 | .. automodule:: registration.south_migrations.0004_activation_keys 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: registration.south_migrations 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /src/registration/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='RegistrationProfile', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, primary_key=True, auto_created=True)), 19 | ('_status', models.CharField(editable=False, default='untreated', max_length=10, choices=[('untreated', 'Untreated yet'), ('accepted', 'Registration has accepted'), ('rejected', 'Registration has rejected')], verbose_name='status', db_column='status')), 20 | ('activation_key', models.CharField(null=True, default=None, verbose_name='activation key', max_length=40, editable=False)), 21 | ('user', models.OneToOneField(editable=False, related_name='registration_profile', verbose_name='user', to=settings.AUTH_USER_MODEL)), 22 | ], 23 | options={ 24 | 'permissions': (('accept_registration', 'Can accept registration'), ('reject_registration', 'Can reject registration'), ('activate_user', 'Can activate user in admin site')), 25 | 'verbose_name': 'registration profile', 26 | 'verbose_name_plural': 'registration profiles', 27 | }, 28 | bases=(models.Model,), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /src/registration/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Confiurations of django-inspectional-registration 5 | """ 6 | __author__ = 'Alisue ' 7 | from django.conf import settings 8 | from appconf import AppConf 9 | 10 | 11 | class InspectionalRegistrationAppConf(AppConf): 12 | DEFAULT_PASSWORD_LENGTH = 10 13 | BACKEND_CLASS = 'registration.backends.default.DefaultRegistrationBackend' 14 | SUPPLEMENT_CLASS = None 15 | SUPPLEMENT_ADMIN_INLINE_BASE_CLASS = ( 16 | 'registration.admin.RegistrationSupplementAdminInlineBase') 17 | OPEN = True 18 | 19 | #REGISTRATION_EMAIL = True 20 | ACCEPTANCE_EMAIL = True 21 | REJECTION_EMAIL = True 22 | ACTIVATION_EMAIL = True 23 | 24 | DJANGO_AUTH_URLS_ENABLE = True 25 | DJANGO_AUTH_URL_NAMES_PREFIX = '' 26 | DJANGO_AUTH_URL_NAMES_SUFFIX = '' 27 | 28 | # Issue #36 29 | USE_OBJECT_PERMISSION = False 30 | 31 | class Meta: 32 | prefix = 'registration' 33 | 34 | 35 | def configure_other_settings(): 36 | import warnings 37 | if not hasattr(settings, 'ACCOUNT_ACTIVATION_DAYS'): 38 | warnings.warn("You should set 'ACCOUNT_ACTIVATION_DAYS' in settings " 39 | "module.") 40 | setattr(settings, 'ACCOUNT_ACTIVATION_DAYS', 7) 41 | if not hasattr(settings, 42 | '_REGISTRATION_ADMIN_REQ_ATTR_NAME_IN_MODEL_INS'): 43 | setattr(settings, 44 | '_REGISTRATION_ADMIN_REQ_ATTR_NAME_IN_MODEL_INS', 45 | '_registration_admin_request') 46 | if not hasattr(settings, 'REGISTRATION_REGISTRATION_EMAIL'): 47 | setattr(settings, 48 | 'REGISTRATION_REGISTRATION_EMAIL', True) 49 | configure_other_settings() 50 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | """ 4 | Run Django Test with Python setuptools test command 5 | 6 | 7 | REFERENCE: 8 | http://gremu.net/blog/2010/enable-setuppy-test-your-django-apps/ 9 | 10 | """ 11 | import os 12 | import sys 13 | 14 | def parse_args(): 15 | import optparse 16 | parser = optparse.OptionParser() 17 | parser.add_option('--where', default=None) 18 | opts, args = parser.parse_args() 19 | return opts.where 20 | 21 | def run_tests(base_dir=None, verbosity=1, interactive=False): 22 | base_dir = base_dir or os.path.dirname(__file__) 23 | os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' 24 | sys.path.insert(0, os.path.join(base_dir, 'src')) 25 | sys.path.insert(0, os.path.join(base_dir, 'tests')) 26 | 27 | from django.conf import settings 28 | from django.test.utils import get_runner 29 | """Run Django Test""" 30 | TestRunner = get_runner(settings) 31 | test_runner = TestRunner(verbosity=verbosity, 32 | interactive=interactive, failfast=False) 33 | 34 | import django 35 | if django.VERSION >= (1, 7): 36 | django.setup() 37 | 38 | if django.VERSION >= (1, 6): 39 | app_tests = [ 40 | 'registration', 41 | 'registration.contrib.notification', # registration.contrib.notification 42 | 'registration.contrib.autologin', # registration.contrib.autologin 43 | ] 44 | else: 45 | app_tests = [ 46 | 'registration', 47 | 'notification', # registration.contrib.notification 48 | 'autologin', # registration.contrib.autologin 49 | ] 50 | failures = test_runner.run_tests(app_tests) 51 | sys.exit(bool(failures)) 52 | 53 | if __name__ == '__main__': 54 | base_dir = parse_args() 55 | run_tests(base_dir) 56 | 57 | -------------------------------------------------------------------------------- /src/registration/supplements/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Registration Supplement 5 | """ 6 | __author__ = 'Alisue ' 7 | __all__ = ('RegistrationSupplementBase', 'get_supplement_class') 8 | from django.core.exceptions import ImproperlyConfigured 9 | 10 | from registration.compat import import_module 11 | 12 | 13 | def get_supplement_class(path=None): 14 | """ 15 | Return an instance of a registration supplement, given the dotted 16 | Python import path (as a string) to the supplement class. 17 | 18 | If the addition cannot be located (e.g., because no such module 19 | exists, or because the module does not contain a class of the 20 | appropriate name), ``django.core.exceptions.ImproperlyConfigured`` 21 | is raised. 22 | 23 | """ 24 | from registration.conf import settings 25 | path = path or settings.REGISTRATION_SUPPLEMENT_CLASS 26 | if not path: 27 | return None 28 | i = path.rfind('.') 29 | module, attr = path[:i], path[i+1:] 30 | try: 31 | mod = import_module(module) 32 | except ImportError as e: 33 | raise ImproperlyConfigured( 34 | 'Error loading registration addition %s: "%s"' % (module, e)) 35 | try: 36 | cls = getattr(mod, attr) 37 | except AttributeError: 38 | raise ImproperlyConfigured(( 39 | 'Module "%s" does not define a registration supplement named ' 40 | '"%s"') % (module, attr)) 41 | from registration.supplements.base import RegistrationSupplementBase 42 | if cls and not issubclass(cls, RegistrationSupplementBase): 43 | raise ImproperlyConfigured(( 44 | 'Registration supplement class "%s" must be a subclass of ' 45 | '``registration.supplements.RegistrationSupplementBase``') % path) 46 | return cls 47 | -------------------------------------------------------------------------------- /docs/registration.rst: -------------------------------------------------------------------------------- 1 | registration package 2 | ==================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | registration.admin 10 | registration.backends 11 | registration.contrib 12 | registration.management 13 | registration.migrations 14 | registration.south_migrations 15 | registration.supplements 16 | registration.tests 17 | 18 | Submodules 19 | ---------- 20 | 21 | registration.compat module 22 | -------------------------- 23 | 24 | .. automodule:: registration.compat 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | registration.conf module 30 | ------------------------ 31 | 32 | .. automodule:: registration.conf 33 | :members: 34 | :undoc-members: 35 | :show-inheritance: 36 | 37 | registration.forms module 38 | ------------------------- 39 | 40 | .. automodule:: registration.forms 41 | :members: 42 | :undoc-members: 43 | :show-inheritance: 44 | 45 | registration.models module 46 | -------------------------- 47 | 48 | .. automodule:: registration.models 49 | :members: 50 | :undoc-members: 51 | :show-inheritance: 52 | 53 | registration.signals module 54 | --------------------------- 55 | 56 | .. automodule:: registration.signals 57 | :members: 58 | :undoc-members: 59 | :show-inheritance: 60 | 61 | registration.urls module 62 | ------------------------ 63 | 64 | .. automodule:: registration.urls 65 | :members: 66 | :undoc-members: 67 | :show-inheritance: 68 | 69 | registration.utils module 70 | ------------------------- 71 | 72 | .. automodule:: registration.utils 73 | :members: 74 | :undoc-members: 75 | :show-inheritance: 76 | 77 | registration.views module 78 | ------------------------- 79 | 80 | .. automodule:: registration.views 81 | :members: 82 | :undoc-members: 83 | :show-inheritance: 84 | 85 | 86 | Module contents 87 | --------------- 88 | 89 | .. automodule:: registration 90 | :members: 91 | :undoc-members: 92 | :show-inheritance: 93 | -------------------------------------------------------------------------------- /docs/registration.tests.rst: -------------------------------------------------------------------------------- 1 | registration.tests package 2 | ========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | registration.tests.compat module 8 | -------------------------------- 9 | 10 | .. automodule:: registration.tests.compat 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | registration.tests.mock module 16 | ------------------------------ 17 | 18 | .. automodule:: registration.tests.mock 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | registration.tests.test_admin module 24 | ------------------------------------ 25 | 26 | .. automodule:: registration.tests.test_admin 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | registration.tests.test_backends module 32 | --------------------------------------- 33 | 34 | .. automodule:: registration.tests.test_backends 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | registration.tests.test_forms module 40 | ------------------------------------ 41 | 42 | .. automodule:: registration.tests.test_forms 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | registration.tests.test_models module 48 | ------------------------------------- 49 | 50 | .. automodule:: registration.tests.test_models 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | registration.tests.test_supplements module 56 | ------------------------------------------ 57 | 58 | .. automodule:: registration.tests.test_supplements 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | registration.tests.test_views module 64 | ------------------------------------ 65 | 66 | .. automodule:: registration.tests.test_views 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | registration.tests.utils module 72 | ------------------------------- 73 | 74 | .. automodule:: registration.tests.utils 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | 80 | Module contents 81 | --------------- 82 | 83 | .. automodule:: registration.tests 84 | :members: 85 | :undoc-members: 86 | :show-inheritance: 87 | -------------------------------------------------------------------------------- /docs/about_registration_supplement.rst: -------------------------------------------------------------------------------- 1 | ********************************************************** 2 | About Registration Supplement 3 | ********************************************************** 4 | 5 | Registration Supplement is a django model class which inherit 6 | :py:class:`registration.supplements.RegistrationSupplementBase`. 7 | It is used to add supplemental information to each registration. 8 | Filling the supplemental information is required in registration step 9 | and the filled supplemental information will be displayed in Django Admin 10 | page. 11 | 12 | To disable this supplement feature, set ``REGISTRATION_SUPPLEMENT_CLASS`` to 13 | ``None`` in your ``settings.py``. 14 | 15 | Quick tutorial to create your own Registration Supplement 16 | ========================================================================================== 17 | 18 | 1. Create new app named ``supplementtut`` with the command below:: 19 | 20 | $ ./manage.py startapp supplementtut 21 | 22 | 2. Create new registration supplement model in your ``models.py`` as:: 23 | 24 | from __future__ import unicode_literals 25 | from django.db import models 26 | from django.utils.encoding import python_2_unicode_compatible 27 | from registration.supplements.base import RegistrationSupplementBase 28 | 29 | class MyRegistrationSupplement(RegistrationSupplementBase): 30 | 31 | realname = models.CharField("Real name", max_length=100, help_text="Please fill your real name") 32 | age = models.IntegerField("Age") 33 | remarks = models.TextField("Remarks", blank=True) 34 | 35 | def __str__(self): 36 | # a summary of this supplement 37 | return "%s (%s)" % (self.realname, self.age) 38 | 39 | 40 | 3. Add ``supplementtut`` to ``INSTALLED_APPS`` and set ``REGISTRATION_SUPPLEMENT_CLASS`` to 41 | ``"supplementtut.models.MyRegistrationSupplement`` in your ``settings.py`` 42 | 43 | 4. Done, execute ``syncdb`` and ``runserver`` to confirm your registration 44 | supplement is used correctly. See more documentation in 45 | :py:class:`registration.supplements.RegistrationSupplementBase` 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/about_registration_signals.rst: -------------------------------------------------------------------------------- 1 | ****************************************************** 2 | About Registration Signals 3 | ****************************************************** 4 | django-inspectional-registration provide the following signals. 5 | 6 | ``user_registered(user, profile, request)`` 7 | It is called when a user has registered. 8 | The arguments are: 9 | 10 | ``user`` 11 | An instance of User model 12 | ``profile`` 13 | An instance of RegistrationProfile model of the ``user`` 14 | ``request`` 15 | An instance of django's HttpRequest. It is useful to automatically get 16 | extra user informations 17 | 18 | ``user_accepted(user, profile, request)`` 19 | It is called when a user has accepted by inspectors. 20 | The arguments are: 21 | 22 | ``user`` 23 | An instance of User model 24 | ``profile`` 25 | An instance of RegistrationProfile model of the ``user`` 26 | ``request`` 27 | An instance of django's HttpRequest. It is useful to automatically get 28 | extra user informations 29 | 30 | ``user_rejected(user, profile, request)`` 31 | It is called when a user has rejected by inspectors. 32 | The arguments are: 33 | 34 | ``user`` 35 | An instance of User model 36 | ``profile`` 37 | An instance of RegistrationProfile model of the ``user`` 38 | ``request`` 39 | An instance of django's HttpRequest. It is useful to automatically get 40 | extra user informations 41 | 42 | ``user_activated(user, profile, is_generated, request)`` 43 | It is called when a user has activated by 1) the user access the activation 44 | url, 2) inspectors forcely activate the user. 45 | The arguments are: 46 | 47 | ``user`` 48 | An instance of User model 49 | ``password`` 50 | If the user have forcely activated by inspectors, this indicate the 51 | raw password, otherwise it is ``None`` (So that non automatically 52 | generated user password is protected from the suffering). 53 | ``is_generated`` 54 | When inspectors forcely activate the user, it become ``True``. 55 | It mean that the user do not know own account password thus you need to 56 | tell the password to the user somehow (default activation e-mail 57 | automatically include the user password if this ``is_generated`` is 58 | ``True``) 59 | ``request`` 60 | An instance of django's HttpRequest. It is useful to automatically get 61 | extra user informations 62 | 63 | -------------------------------------------------------------------------------- /docs/about_registration_settings.rst: -------------------------------------------------------------------------------- 1 | ****************************************************** 2 | About Registration Settings 3 | ****************************************************** 4 | 5 | ``ACCOUNT_ACTIVATION_DAYS`` 6 | The number of days to determine the remaining during which the account may 7 | be activated. 8 | 9 | Default: ``7`` 10 | 11 | ``REGISTRATION_DEFAULT_PASSWORD_LENGTH`` 12 | The integer length of the default password programatically generate. 13 | 14 | Default: ``10`` 15 | 16 | ``REGISTRATION_BACKEND_CLASS`` 17 | A string dotted python path for registration backend class. 18 | 19 | Default: ``'registration.backends.default.DefaultRegistrationBackend'`` 20 | 21 | ``REGISTRATION_SUPPLEMENT_CLASS`` 22 | A string dotted python path for registration supplement class. 23 | 24 | Default: ``'registration.supplements.default.DefaultRegistrationSupplement'`` 25 | 26 | ``REGISTRATION_ADMIN_INLINE_BASE_CLASS`` 27 | A string dotted python path for registration supplement admin inline base 28 | class. 29 | 30 | Default: ``'registration.admin.RegistrationSupplementAdminInlineBase'`` 31 | 32 | ``REGISTRATION_OPEN`` 33 | A boolean value whether the registration is currently allowed. 34 | 35 | Default: ``True`` 36 | 37 | ``REGISTRATION_REGISTRATION_EMAIL`` 38 | Set ``False`` to disable sending registration email to the user. 39 | 40 | Default: ``True`` 41 | 42 | ``REGISTRATION_ACCEPTANCE_EMAIL`` 43 | Set ``False`` to disable sending acceptance email to the user. 44 | 45 | Default: ``True`` 46 | 47 | ``REGISTRATION_REJECTION_EMAIL`` 48 | Set ``False`` to disable sending rejection email to the user. 49 | 50 | Default: ``True`` 51 | 52 | ``REGISTRATION_ACTIVATION_EMAIL`` 53 | Set ``False`` to disable sending activation email to the user. 54 | 55 | Default: ``True`` 56 | 57 | ``REGISTRATION_DJANGO_AUTH_URLS_ENABLE`` (from Version 0.4.0) 58 | If it is ``False``, django-inspectional-registration do not define the views of django.contrib.auth. 59 | It is required to define these view manually. 60 | 61 | Default: ``True`` 62 | 63 | ``REGISTRATION_DJANGO_AUTH_URL_NAMES_PREFIX`` (from Version 0.4.0) 64 | It is used as a prefix string of view names of django.contrib.auth. 65 | For backward compatibility, set this value to ``'auth_'``. 66 | 67 | Default: ``''`` 68 | 69 | ``REGISTRATION_DJANGO_AUTH_URL_NAMES_SUFFIX`` (from Version 0.4.0) 70 | It is used as a suffix string of view names of django.contrib.auth. 71 | For backward compatibility, set this value to ``''``. 72 | 73 | Default: ``''`` 74 | -------------------------------------------------------------------------------- /src/registration/contrib/autologin/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Automatically log activated user in when they have activated with their activation 5 | link. This doesn't happen when the user was activated programatically with Django 6 | Admin site. 7 | 8 | It is originally written by 'bluejeansummer' in 9 | https://bitbucket.org/ubernostrum/django-registration/pull-request/5/optional-auto-login 10 | 11 | To disable temporarily this feature, set ``False`` to ``REGISTRATION_AUTO_LOGIN`` 12 | of your ``settings.py`` 13 | 14 | .. Note:: 15 | This feature is not available in tests because default tests of 16 | django-inspectional-registration are not assumed to test with contributes. 17 | 18 | If you do want this feature to be available in tests, set 19 | ``_REGISTRATION_AUTO_LOGIN_IN_TESTS`` to ``True`` in ``setUp()`` method 20 | of the test case class and delete the attribute in ``tearDown()`` method. 21 | """ 22 | __author__ = 'Alisue ' 23 | import sys 24 | from django.contrib.auth import login 25 | from django.contrib.auth import get_backends 26 | from registration.signals import user_activated 27 | from registration.contrib.autologin.conf import settings 28 | 29 | 30 | def is_auto_login_enable(): 31 | """get whether the registration autologin is enable""" 32 | if not settings.REGISTRATION_AUTO_LOGIN: 33 | return False 34 | if 'test' in sys.argv and not getattr(settings, 35 | '_REGISTRATION_AUTO_LOGIN_IN_TESTS', 36 | False): 37 | # Registration Auto Login is not available in test to prevent the test 38 | # fails of ``registration.tests.*``. 39 | # For testing Registration Auto Login, you must set 40 | # ``_REGISTRATION_AUTO_LOGIN_IN_TESTS`` to ``True`` 41 | return False 42 | return True 43 | 44 | 45 | def auto_login_reciver(sender, user, password, is_generated, request, **kwargs): 46 | """automatically log activated user in when they have activated""" 47 | if not is_auto_login_enable() or is_generated: 48 | # auto login feature is currently disabled by setting or 49 | # the user was activated programatically by Django Admin action 50 | # thus no auto login is required. 51 | return 52 | # A bit of a hack to bypass `authenticate()` 53 | backend = get_backends()[0] 54 | user.backend = '%s.%s' % (backend.__module__, backend.__class__.__name__) 55 | login(request, user) 56 | user_activated.connect(auto_login_reciver) 57 | -------------------------------------------------------------------------------- /src/registration/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Utilities for django-inspectional-registration 5 | """ 6 | __author__ = 'Alisue ' 7 | import random 8 | 9 | from django.utils.encoding import force_text 10 | from django.utils.six.moves import range 11 | from registration.compat import sha1 12 | 13 | 14 | def get_site(request): 15 | """get current ``django.contrib.Site`` instance 16 | 17 | return ``django.contrib.RequestSite`` instance when the ``Site`` is 18 | not installed. 19 | 20 | """ 21 | try: 22 | from django.contrib.sites.shortcuts import get_current_site 23 | except ImportError: 24 | from django.contrib.sites.models import get_current_site 25 | return get_current_site(request) 26 | 27 | 28 | def generate_activation_key(username): 29 | """generate activation key with username 30 | 31 | originally written by ubernostrum in django-registration_ 32 | 33 | .. _django-registration: https://bitbucket.org/ubernostrum/django-registration 34 | """ 35 | username = force_text(username) 36 | seed = force_text(random.random()) 37 | salt = sha1(seed.encode('utf-8')).hexdigest()[:5] 38 | activation_key = sha1((salt+username).encode('utf-8')).hexdigest() 39 | return activation_key 40 | 41 | 42 | def generate_random_password(length=10): 43 | """generate random password with passed length""" 44 | # Without 1, l, O, 0 because those character are hard to tell 45 | # the difference between in same fonts 46 | chars = '23456789abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' 47 | password = "".join([random.choice(chars) for i in range(length)]) 48 | return password 49 | 50 | 51 | def send_mail(subject, message, from_email, recipients): 52 | """send mail to recipients 53 | 54 | this method use django-mailer_ ``send_mail`` method when 55 | the app is in ``INSTALLED_APPS`` 56 | 57 | .. Note:: 58 | django-mailer_ ``send_mail`` is not used duaring unittest 59 | because it is a little bit difficult to check the number of 60 | mail sent in unittest for both django-mailer and original 61 | django ``send_mail`` 62 | 63 | .. _django-mailer: http://code.google.com/p/django-mailer/ 64 | """ 65 | from django.conf import settings 66 | from django.core.mail import send_mail as django_send_mail 67 | import sys 68 | if 'test' not in sys.argv and 'mailer' in settings.INSTALLED_APPS: 69 | try: 70 | from mailer import send_mail 71 | return send_mail(subject, message, from_email, recipients) 72 | except ImportError: 73 | pass 74 | return django_send_mail(subject, message, from_email, recipients) 75 | -------------------------------------------------------------------------------- /docs/quickmigrations.rst: -------------------------------------------------------------------------------- 1 | ******************************** 2 | Quick Migrations 3 | ******************************** 4 | 5 | Instructions 6 | ======================== 7 | 8 | django-inspectional-registration can be migrate from django-registration by 9 | south. To migrate, follow the instructions 10 | 11 | 1. Confirm your application has ``'south'``, ``'django.contrib.admin'`` and 12 | in your ``INSTALLED_APPS``, if you haven't, 13 | add these and run ``syncdb`` command to create the database table required. 14 | 15 | 2. Execute following commands:: 16 | 17 | $ ./manage.py migrate registration 0001 --fake 18 | $ ./manage.py migrate registration 19 | 20 | 3. Rewrite your most top of ``urls.py`` as:: 21 | 22 | from django.conf.urls.defaults import patterns, include, urls 23 | 24 | from django.contrib import admin 25 | admin.autodiscover() 26 | 27 | urlpatterns = pattern('', 28 | # some urls... 29 | 30 | # django-inspectional-registration require Django Admin page 31 | # to inspect registrations 32 | url('^admin/', include(admin.site.urls)), 33 | 34 | # Add django-inspectional-registration urls. The urls also define 35 | # Login, Logout and password_change or lot more for handle 36 | # registration. 37 | url('^registration/', include('registration.urls')), 38 | ) 39 | 40 | 4. Set ``REGISTRATION_SUPPLEMENT_CLASS`` to ``None`` in your ``settings.py`` 41 | 42 | .. Note:: 43 | django-inspectional-registration can handle registration supplemental 44 | information. If you want to use your own custom registration 45 | supplemental information, check :doc:`about_registration_supplement` for 46 | documents. 47 | 48 | Settings ``REGISTRATION_SUPPLEMENT_CLASS`` to ``None`` mean no 49 | registration supplemental information will be used. 50 | 51 | 5. Done. Enjoy! 52 | 53 | The database difference between django-registration and django-inspectional-registration 54 | ================================================================================================================================================================================ 55 | 56 | django-inspectional-registration add new ``CharField`` named :py:attr:`registration.models.RegistrationProfile._status` to 57 | the :py:class:`registration.models.RegistrationProfile` and change the storategy to delete 58 | ``RegistrationProfile`` which has been activated from the database insted of 59 | setting ``'ALREADY_ACTIVATED'`` to :py:attr:`registration.models.RegistrationProfile.activation_key`. 60 | 61 | ``activation_key`` will be generated when the ``_status`` of ``RegistrationProfile`` 62 | be ``'accepted'`` otherwise it is set ``None`` 63 | 64 | -------------------------------------------------------------------------------- /src/registration/tests/mock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Mock request for unittest 5 | 6 | This is a modification of django-registration_ ``admin.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 | __author__ = 'Alisue ' 44 | from django.contrib.sessions.middleware import SessionMiddleware 45 | from django.test.client import RequestFactory 46 | from registration.utils import get_site 47 | 48 | 49 | def mock_request(): 50 | """ 51 | Construct and return a mock ``HttpRequest`` object; this is used 52 | in testing backend methods which expect an ``HttpRequest`` but 53 | which are not being called from views. 54 | 55 | """ 56 | request = RequestFactory().get('/') 57 | 58 | # We have to manually add a session since we'll be bypassing 59 | # the middleware chain. 60 | session_middleware = SessionMiddleware() 61 | session_middleware.process_request(request) 62 | 63 | return request 64 | 65 | 66 | def mock_site(): 67 | """ 68 | Construct and return a mock `Site`` object; this is used 69 | in testing methods which expect an ``Site`` 70 | 71 | """ 72 | return get_site(mock_request()) 73 | 74 | -------------------------------------------------------------------------------- /src/registration/management/commands/cleanup_rejected_registrations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | A management command which deletes expired or rejected accounts (e.g., 5 | accounts which signed up but never activated) from the database. 6 | 7 | Calls ``RegistrationProfile.objects.delete_expired_users()`` and 8 | ``RegistrationProfile.objects.delete_rejected_users()``, which 9 | contains the actual logic for determining which accounts are deleted. 10 | 11 | This is a modification of django-registration_ ``cleanupregistration.py`` 12 | The original code is written by James Bennett 13 | 14 | .. _django-registration: https://bitbucket.org/ubernostrum/django-registration 15 | 16 | 17 | Original License:: 18 | 19 | Copyright (c) 2007-2011, James Bennett 20 | All rights reserved. 21 | 22 | Redistribution and use in source and binary forms, with or without 23 | modification, are permitted provided that the following conditions are 24 | met: 25 | 26 | * Redistributions of source code must retain the above copyright 27 | notice, this list of conditions and the following disclaimer. 28 | * Redistributions in binary form must reproduce the above 29 | copyright notice, this list of conditions and the following 30 | disclaimer in the documentation and/or other materials provided 31 | with the distribution. 32 | * Neither the name of the author nor the names of other 33 | contributors may be used to endorse or promote products derived 34 | from this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 37 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 38 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 39 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 40 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 41 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 42 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 43 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 44 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 45 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 46 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 | """ 48 | __author__ = 'Alisue ' 49 | from registration.models import RegistrationProfile 50 | try: 51 | from django.core.management import BaseCommand 52 | 53 | class Command(BaseCommand): 54 | help = "Delete rejected user registrations from the database" 55 | 56 | def handle(self, **options): 57 | RegistrationProfile.objects.delete_rejected_users() 58 | except ImportError: 59 | from django.core.management import NoArgsCommand 60 | 61 | class Command(NoArgsCommand): 62 | help = "Delete rejected user registrations from the database" 63 | 64 | def handle_noargs(self, **options): 65 | RegistrationProfile.objects.delete_rejected_users() 66 | -------------------------------------------------------------------------------- /src/registration/management/commands/cleanup_expired_registrations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | A management command which deletes expired or rejected accounts (e.g., 5 | accounts which signed up but never activated) from the database. 6 | 7 | Calls ``RegistrationProfile.objects.delete_expired_users()`` and 8 | ``RegistrationProfile.objects.delete_rejected_users()``, which 9 | contains the actual logic for determining which accounts are deleted. 10 | 11 | This is a modification of django-registration_ ``cleanupregistration.py`` 12 | The original code is written by James Bennett 13 | 14 | .. _django-registration: https://bitbucket.org/ubernostrum/django-registration 15 | 16 | 17 | Original License:: 18 | 19 | Copyright (c) 2007-2011, James Bennett 20 | All rights reserved. 21 | 22 | Redistribution and use in source and binary forms, with or without 23 | modification, are permitted provided that the following conditions are 24 | met: 25 | 26 | * Redistributions of source code must retain the above copyright 27 | notice, this list of conditions and the following disclaimer. 28 | * Redistributions in binary form must reproduce the above 29 | copyright notice, this list of conditions and the following 30 | disclaimer in the documentation and/or other materials provided 31 | with the distribution. 32 | * Neither the name of the author nor the names of other 33 | contributors may be used to endorse or promote products derived 34 | from this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 37 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 38 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 39 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 40 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 41 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 42 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 43 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 44 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 45 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 46 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 | 48 | """ 49 | __author__ = 'Alisue ' 50 | from registration.models import RegistrationProfile 51 | 52 | try: 53 | from django.core.management import BaseCommand 54 | 55 | class Command(BaseCommand): 56 | help = "Delete expired user registrations from the database" 57 | 58 | def handle(self, **options): 59 | RegistrationProfile.objects.delete_expired_users() 60 | except ImportError: 61 | from django.core.management import NoArgsCommand 62 | 63 | class Command(NoArgsCommand): 64 | help = "Delete expired user registrations from the database" 65 | 66 | def handle_noargs(self, **options): 67 | RegistrationProfile.objects.delete_expired_users() 68 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | ------ 3 | 4 | Help! Email have not been sent to the user! 5 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | To enable sending email in django, you must have the following settings in your 7 | ``settings.py``:: 8 | 9 | # if your smtp host use TLS 10 | #EMAIL_USE_TLS = True 11 | # url of your smtp host 12 | EMAIL_HOST = '' 13 | # if your smtp host requre username 14 | #EMAIL_HOST_USER = '' 15 | # if your smtp host require password 16 | #EMAIL_HOST_PASSWORD = '' 17 | # port number which your smtp host used (default 25) 18 | # EMAIL_PORT = 587 19 | DEFAULT_FROM_EMAIL = 'webmaster@your.domain' 20 | 21 | If you don't have SMTP host but you have Gmail, use the following settings 22 | to use your Gmail for SMTP host:: 23 | 24 | EMAIL_USE_TLS = True 25 | EMAIL_PORT = 587 26 | EMAIL_HOST = 'smtp.gmail.com' 27 | EMAIL_HOST_USER = 'your_email_address@gmail.com' 28 | EMAIL_HOST_PASSWORD = 'your gmail password' 29 | DEFAULT_FROM_EMAIL = 'your_email_address@gmail.com' 30 | 31 | 32 | How can I get notification email when new user has registered in the site? 33 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 | Use :mod:`registration.contrib.notification`. 35 | 36 | Add ``'registration.contrib.notification'`` to your ``INSTALLED_APPS`` and create 37 | following template files in your template directory. 38 | 39 | - ``registration/notification_email.txt`` 40 | - ``registration/notification_email_subject.txt`` 41 | 42 | 43 | I want to use django-inspectional-registration but I don't need inspection step 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | If you don't need inspection step, use original django-registration_ in that 46 | case. 47 | 48 | However, sometime you do may want to use django-inspectional-registration but 49 | inspection. Then follow the instructions below 50 | 51 | 1. Disable sending registration email with setting 52 | ``REGISTRATION_REGISTRATION_EMAIL`` to ``False`` 53 | 54 | 2. Add special signal reciever which automatically accept the user 55 | registration:: 56 | 57 | from registration.backends import get_backend 58 | from registration.signals import user_registered 59 | 60 | def automatically_accept_registration_reciver(sender, user, profile, request, **kwargs): 61 | backend = get_backend() 62 | backend.accept(profile, request=request) 63 | user_registered.connect(automatically_accept_registration_reciver) 64 | 65 | Then the application behaviors like django-registration_ 66 | 67 | 68 | How can I contribute to django-inspectional-registration 69 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | Any contributions include `adding translations `_ are welcome! 71 | Use github's ``pull request`` for contribution. 72 | 73 | .. _django-registration: https://bitbucket.org/ubernostrum/django-registration/ 74 | 75 | -------------------------------------------------------------------------------- /src/registration/contrib/autologin/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | """ 5 | __author__ = 'Alisue ' 6 | from django.test import TestCase 7 | from django.contrib.auth.models import AnonymousUser 8 | from registration.backends.default import DefaultRegistrationBackend 9 | from registration.tests.mock import mock_request 10 | from registration.tests.compat import override_settings 11 | 12 | 13 | @override_settings( 14 | ACCOUNT_ACTIVATION_DAYS=7, 15 | REGISTRATION_OPEN=True, 16 | REGISTRATION_SUPPLEMENT_CLASS=None, 17 | REGISTRATION_BACKEND_CLASS=( 18 | 'registration.backends.default.DefaultRegistrationBackend'), 19 | REGISTRATION_AUTO_LOGIN=True, 20 | _REGISTRATION_AUTO_LOGIN_IN_TESTS=True, 21 | ) 22 | class RegistrationAutoLoginTestCase(TestCase): 23 | backend = DefaultRegistrationBackend() 24 | mock_request = mock_request() 25 | 26 | def test_no_auto_login_with_setting(self): 27 | """Auto login feature should be able to off with ``REGISTRATION_AUTO_LOGIN = False``""" 28 | self.mock_request.user = AnonymousUser() 29 | 30 | with override_settings(REGISTRATION_AUTO_LOGIN = False): 31 | 32 | new_user = self.backend.register( 33 | 'bob', 'bob@test.com', request=self.mock_request, 34 | ) 35 | self.backend.accept( 36 | new_user.registration_profile, request=self.mock_request, 37 | ) 38 | self.backend.activate( 39 | new_user.registration_profile.activation_key, 40 | password='password',request=self.mock_request, 41 | ) 42 | 43 | self.failIf(self.mock_request.user.is_authenticated()) 44 | 45 | def test_no_auto_login_with_no_password(self): 46 | """Auto login feature should not be occur with no password 47 | (programatically activated by Django Admin action) 48 | 49 | """ 50 | self.mock_request.user = AnonymousUser() 51 | 52 | new_user = self.backend.register( 53 | 'bob', 'bob@test.com', request=self.mock_request, 54 | ) 55 | self.backend.accept( 56 | new_user.registration_profile, request=self.mock_request, 57 | ) 58 | self.backend.activate( 59 | new_user.registration_profile.activation_key, 60 | request=self.mock_request, 61 | ) 62 | 63 | self.failIf(self.mock_request.user.is_authenticated()) 64 | 65 | def test_auto_login(self): 66 | """Wheather auto login feature works correctly""" 67 | self.mock_request.user = AnonymousUser() 68 | 69 | new_user = self.backend.register( 70 | 'bob', 'bob@test.com', request=self.mock_request, 71 | ) 72 | self.backend.accept( 73 | new_user.registration_profile, request=self.mock_request, 74 | ) 75 | self.backend.activate( 76 | new_user.registration_profile.activation_key, 77 | password='password',request=self.mock_request, 78 | ) 79 | 80 | self.failUnless(self.mock_request.user.is_authenticated()) 81 | -------------------------------------------------------------------------------- /src/registration/management/commands/cleanup_registrations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | A management command which deletes expired or rejected accounts (e.g., 5 | accounts which signed up but never activated) from the database. 6 | 7 | Calls ``RegistrationProfile.objects.delete_expired_users()`` and 8 | ``RegistrationProfile.objects.delete_rejected_users()``, which 9 | contains the actual logic for determining which accounts are deleted. 10 | 11 | This is a modification of django-registration_ ``cleanupregistration.py`` 12 | The original code is written by James Bennett 13 | 14 | .. _django-registration: https://bitbucket.org/ubernostrum/django-registration 15 | 16 | 17 | Original License:: 18 | 19 | Copyright (c) 2007-2011, James Bennett 20 | All rights reserved. 21 | 22 | Redistribution and use in source and binary forms, with or without 23 | modification, are permitted provided that the following conditions are 24 | met: 25 | 26 | * Redistributions of source code must retain the above copyright 27 | notice, this list of conditions and the following disclaimer. 28 | * Redistributions in binary form must reproduce the above 29 | copyright notice, this list of conditions and the following 30 | disclaimer in the documentation and/or other materials provided 31 | with the distribution. 32 | * Neither the name of the author nor the names of other 33 | contributors may be used to endorse or promote products derived 34 | from this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 37 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 38 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 39 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 40 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 41 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 42 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 43 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 44 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 45 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 46 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 47 | """ 48 | __author__ = 'Alisue ' 49 | from registration.models import RegistrationProfile 50 | try: 51 | from django.core.management import BaseCommand 52 | 53 | class Command(BaseCommand): 54 | help = "Delete expired/rejected user registrations from the database" 55 | 56 | def handle(self, **options): 57 | RegistrationProfile.objects.delete_expired_users() 58 | RegistrationProfile.objects.delete_rejected_users() 59 | except ImportError: 60 | from django.core.management import NoArgsCommand 61 | 62 | class Command(NoArgsCommand): 63 | help = "Delete expired/rejected user registrations from the database" 64 | 65 | def handle_noargs(self, **options): 66 | RegistrationProfile.objects.delete_expired_users() 67 | RegistrationProfile.objects.delete_rejected_users() 68 | -------------------------------------------------------------------------------- /src/registration/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | URLconf for django-inspectional-registration 5 | """ 6 | __author__ = 'Alisue ' 7 | from registration.compat import url 8 | 9 | from registration.views import RegistrationView 10 | from registration.views import RegistrationClosedView 11 | from registration.views import RegistrationCompleteView 12 | from registration.views import ActivationView 13 | from registration.views import ActivationCompleteView 14 | 15 | urlpatterns = [ 16 | url(r'^activate/complete/$', ActivationCompleteView.as_view(), 17 | name='registration_activation_complete'), 18 | url(r'^activate/(?P\w+)/$', ActivationView.as_view(), 19 | name='registration_activate'), 20 | url(r'^register/$', RegistrationView.as_view(), 21 | name='registration_register'), 22 | url(r'^register/closed/$', RegistrationClosedView.as_view(), 23 | name='registration_disallowed'), 24 | url(r'^register/complete/$', RegistrationCompleteView.as_view(), 25 | name='registration_complete'), 26 | ] 27 | 28 | # django.contrib.auth 29 | from registration.conf import settings 30 | from django.contrib.auth import views as auth_views 31 | if settings.REGISTRATION_DJANGO_AUTH_URLS_ENABLE: 32 | prefix = settings.REGISTRATION_DJANGO_AUTH_URL_NAMES_PREFIX 33 | suffix = settings.REGISTRATION_DJANGO_AUTH_URL_NAMES_SUFFIX 34 | 35 | import django 36 | if django.VERSION >= (1, 6): 37 | uidb = r"(?P[0-9A-Za-z_\-]+)" 38 | token = r"(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})" 39 | password_reset_confirm_rule = ( 40 | r"^password/reset/confirm/%s/%s/$" % (uidb, token) 41 | ) 42 | else: 43 | uidb = r"(?P[0-9A-Za-z]+)" 44 | token = r"(?P.+)" 45 | password_reset_confirm_rule = ( 46 | r"^password/reset/confirm/%s-%s/$" % (uidb, token) 47 | ) 48 | 49 | urlpatterns += [ 50 | url(r'^login/$', auth_views.login, 51 | {'template_name': 'registration/login.html'}, 52 | name=prefix+'login'+suffix), 53 | url(r'^logout/$', auth_views.logout, 54 | {'template_name': 'registration/logout.html'}, 55 | name=prefix+'logout'+suffix), 56 | url(r'^password/change/$', auth_views.password_change, 57 | name=prefix+'password_change'+suffix), 58 | url(r'^password/change/done/$', auth_views.password_change_done, 59 | name=prefix+'password_change_done'+suffix), 60 | url(r'^password/reset/$', auth_views.password_reset, 61 | name=prefix+'password_reset'+suffix, kwargs=dict( 62 | post_reset_redirect=prefix+'password_reset_done'+suffix)), 63 | url(password_reset_confirm_rule, 64 | auth_views.password_reset_confirm, 65 | name=prefix+'password_reset_confirm'+suffix), 66 | url(r'^password/reset/complete/$', auth_views.password_reset_complete, 67 | name=prefix+'password_reset_complete'+suffix), 68 | url(r'^password/reset/done/$', auth_views.password_reset_done, 69 | name=prefix+'password_reset_done'+suffix), 70 | ] 71 | 72 | import django 73 | if django.VERSION <= (1, 8): 74 | from registration.compat import patterns 75 | urlpatterns = patterns('', *urlpatterns) 76 | -------------------------------------------------------------------------------- /src/registration/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Backend 5 | 6 | This is a modification of django-registration_ ``backends/__init__.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 | __author__ = 'Alisue ' 44 | __all__ = ('get_backend', 'RegistrationBackendBase') 45 | from django.core.exceptions import ImproperlyConfigured 46 | 47 | from registration.conf import settings 48 | from registration.compat import import_module 49 | from registration.backends.base import RegistrationBackendBase 50 | 51 | 52 | def get_backend_class(path=None): 53 | """ 54 | Return an class of a registration backend, given the dotted 55 | Python import path (as a string) to the backend class. 56 | 57 | If the backend cannot be located (e.g., because no such module 58 | exists, or because the module does not contain a class of the 59 | appropriate name), ``django.core.exceptions.ImproperlyConfigured`` 60 | is raised. 61 | 62 | """ 63 | path = path or settings.REGISTRATION_BACKEND_CLASS 64 | i = path.rfind('.') 65 | module, attr = path[:i], path[i+1:] 66 | try: 67 | mod = import_module(module) 68 | except ImportError as e: 69 | raise ImproperlyConfigured( 70 | 'Error loading registration backend %s: "%s"' % (module, e)) 71 | try: 72 | cls = getattr(mod, attr) 73 | except AttributeError: 74 | raise ImproperlyConfigured(( 75 | 'Module "%s" does not define a registration backend named "%s"' 76 | ) % (module, attr)) 77 | if cls and not issubclass(cls, RegistrationBackendBase): 78 | raise ImproperlyConfigured(( 79 | 'Registration backend class "%s" must be a subclass of ' 80 | '``registration.backends.RegistrationBackendBase``') % path) 81 | return cls 82 | 83 | 84 | def get_backend(path=None): 85 | """ 86 | Return an instance of a registration backend, given the dotted 87 | Python import path (as a string) to the backend class. 88 | 89 | If the backend cannot be located (e.g., because no such module 90 | exists, or because the module does not contain a class of the 91 | appropriate name), ``django.core.exceptions.ImproperlyConfigured`` 92 | is raised. 93 | 94 | """ 95 | return get_backend_class(path)() 96 | -------------------------------------------------------------------------------- /src/registration/supplements/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | A registration supplemental abstract model 5 | """ 6 | __author__ = 'Alisue ' 7 | from django.db import models 8 | from django.forms.models import modelform_factory 9 | from django.utils.text import ugettext_lazy as _ 10 | from django.utils.encoding import python_2_unicode_compatible 11 | 12 | 13 | @python_2_unicode_compatible 14 | class RegistrationSupplementBase(models.Model): 15 | """A registration supplement abstract model 16 | 17 | Registration supplement model is used to add supplemental information to 18 | the account registration. The supplemental information is written by the 19 | user who tried to register the site and displaied in django admin page to 20 | help determine the acceptance/rejection of the registration 21 | 22 | The ``__str__()`` method is used to display the summary of the 23 | supplemental information in django admin's change list view. Thus subclasses 24 | must define them own ``__str__()`` method. 25 | 26 | The ``get_form_class()`` is a class method return a value of ``form_class`` 27 | attribute to determine the form class used for filling up the supplemental 28 | informatin in registration view if ``form_class`` is specified. Otherwise the 29 | method create django's ``ModelForm`` and return. 30 | 31 | The ``get_admin_fields()`` is a class method return a list of field names 32 | displayed in django admin site. It simply return a value of ``admin_fields`` 33 | attribute in default. If the method return ``None``, then all fields except 34 | ``id`` (and fields in ``admin_excludes``) will be displayed. 35 | 36 | The ``get_admin_excludes()`` is a class method return a list of field names 37 | NOT displayed in django admin site. It simply return a value of ``admin_excludes`` 38 | attribute in default. If the method return ``None``, then all fields selected 39 | with ``admin_fields`` except ``id`` will be displayed. 40 | 41 | The ``registration_profile`` field is used to determine the registration 42 | profile associated with. ``related_name`` of the field is used to get the 43 | supplemental information in ``_get_supplement()`` method of 44 | ``RegistrationProfile`` thus DO NOT CHANGE the name. 45 | 46 | """ 47 | form_class = None 48 | admin_fields = None 49 | admin_excludes = None 50 | registration_profile = models.OneToOneField( 51 | 'registration.RegistrationProfile', 52 | verbose_name=_('registration profile'), 53 | editable=False, related_name='_%(app_label)s_%(class)s_supplement') 54 | 55 | class Meta: 56 | abstract = True 57 | 58 | def __str__(self): 59 | """return the summary of this supplemental information 60 | 61 | Subclasses must define them own method 62 | """ 63 | raise NotImplementedError( 64 | "You must define '__str__' method and return summary of " 65 | "the supplement") 66 | 67 | @classmethod 68 | def get_form_class(cls): 69 | """Return the form class used for this registration supplement model 70 | 71 | When ``form_class`` is specified, this method return the value of the 72 | attribute. Otherwise it generate django's ``ModelForm``, set it to 73 | ``form_class`` and return it 74 | 75 | This method MUST BE class method. 76 | 77 | """ 78 | if not getattr(cls, 'form_class', None): 79 | setattr(cls, 'form_class', modelform_factory(cls, exclude=[])) 80 | return getattr(cls, 'form_class') 81 | 82 | @classmethod 83 | def get_admin_fields(cls): 84 | """Return a list of field names displayed in django admin site 85 | 86 | It is simply return a value of ``admin_fields`` in default. If it returns 87 | ``None`` then all fields except ``id`` (and fields in ``admin_excludes``) 88 | will be displayed. 89 | 90 | """ 91 | return cls.admin_fields 92 | 93 | @classmethod 94 | def get_admin_excludes(cls): 95 | """Return a list of field names NOT displayed in django admin site 96 | 97 | It is simply return a value of ``admin_excludes`` in default. If it returns 98 | ``None`` then all fields (selected in ``admin_fields``) except ``id`` 99 | will be displayed. 100 | 101 | """ 102 | return cls.admin_excludes 103 | -------------------------------------------------------------------------------- /src/registration/south_migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Adding model 'RegistrationProfile' 13 | db.create_table('registration_registrationprofile', ( 14 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 15 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], unique=True)), 16 | ('activation_key', self.gf('django.db.models.fields.CharField')(max_length=40)), 17 | )) 18 | db.send_create_signal('registration', ['RegistrationProfile']) 19 | 20 | 21 | def backwards(self, orm): 22 | 23 | # Deleting model 'RegistrationProfile' 24 | db.delete_table('registration_registrationprofile') 25 | 26 | 27 | models = { 28 | 'auth.group': { 29 | 'Meta': {'object_name': 'Group'}, 30 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 31 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 32 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 33 | }, 34 | 'auth.permission': { 35 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 36 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 37 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 38 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 39 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 40 | }, 41 | 'auth.user': { 42 | 'Meta': {'object_name': 'User'}, 43 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 44 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 45 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 46 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 47 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 48 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 49 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 50 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 51 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 52 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 53 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 54 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 55 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 56 | }, 57 | 'contenttypes.contenttype': { 58 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 59 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 60 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 61 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 62 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 63 | }, 64 | 'registration.registrationprofile': { 65 | 'Meta': {'object_name': 'RegistrationProfile'}, 66 | 'activation_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), 67 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 68 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) 69 | } 70 | } 71 | 72 | complete_apps = ['registration'] 73 | -------------------------------------------------------------------------------- /src/registration/south_migrations/0003_status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import datetime 4 | from south.db import db 5 | from south.v2 import DataMigration 6 | from django.db import models 7 | 8 | class Migration(DataMigration): 9 | 10 | def forwards(self, orm): 11 | for profile in orm.RegistrationProfile.objects.all(): 12 | if profile.user.is_active: 13 | # the registration profile is no longer required 14 | profile.delete() 15 | 16 | 17 | def backwards(self, orm): 18 | from django.conf import settings 19 | for profile in orm.RegistrationProfile.objects.all(): 20 | if profile._status == 'rejected': 21 | # the profile has rejected thus set the profile expired 22 | profile.user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS+1) 23 | profile.user.save() 24 | 25 | 26 | models = { 27 | 'auth.group': { 28 | 'Meta': {'object_name': 'Group'}, 29 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 31 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 32 | }, 33 | 'auth.permission': { 34 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 35 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 36 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 37 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 38 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 39 | }, 40 | 'auth.user': { 41 | 'Meta': {'object_name': 'User'}, 42 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 43 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 44 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 45 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 46 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 48 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 49 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 50 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 51 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 52 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 53 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 54 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 55 | }, 56 | 'contenttypes.contenttype': { 57 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 58 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 59 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 60 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 61 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 62 | }, 63 | 'registration.registrationprofile': { 64 | 'Meta': {'object_name': 'RegistrationProfile'}, 65 | '_status': ('django.db.models.fields.CharField', [], {'default': "'untreated'", 'max_length': '10', 'db_column': "'status'"}), 66 | 'activation_key': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '40', 'null': 'True'}), 67 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 68 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'registration_profile'", 'unique': 'True', 'to': "orm['auth.User']"}) 69 | } 70 | } 71 | 72 | complete_apps = ['registration'] 73 | -------------------------------------------------------------------------------- /src/registration/south_migrations/0004_activation_keys.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import datetime 4 | from south.db import db 5 | from south.v2 import DataMigration 6 | from django.db import models 7 | 8 | class Migration(DataMigration): 9 | 10 | def forwards(self, orm): 11 | for profile in orm.RegistrationProfile.objects.all(): 12 | # all registration profile was set status 'untreated' by default 13 | profile.activation_key = None 14 | 15 | 16 | def backwards(self, orm): 17 | for profile in orm.RegistrationProfile.objects.all(): 18 | # profile which is haven't treated yet should set activation_key 19 | # and should send activation key as well 20 | # Note: 21 | # all 'rejeted' profile is expired previously thus doesn't matter 22 | if profile._status == 'untreated': 23 | orm.RegistrationProfile.objects.accept_registration(profile) 24 | 25 | 26 | models = { 27 | 'auth.group': { 28 | 'Meta': {'object_name': 'Group'}, 29 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 31 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 32 | }, 33 | 'auth.permission': { 34 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 35 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 36 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 37 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 38 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 39 | }, 40 | 'auth.user': { 41 | 'Meta': {'object_name': 'User'}, 42 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 43 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 44 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 45 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 46 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 48 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 49 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 50 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 51 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 52 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 53 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 54 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 55 | }, 56 | 'contenttypes.contenttype': { 57 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 58 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 59 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 60 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 61 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 62 | }, 63 | 'registration.registrationprofile': { 64 | 'Meta': {'object_name': 'RegistrationProfile'}, 65 | '_status': ('django.db.models.fields.CharField', [], {'default': "'untreated'", 'max_length': '10', 'db_column': "'status'"}), 66 | 'activation_key': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '40', 'null': 'True'}), 67 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 68 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'registration_profile'", 'unique': 'True', 'to': "orm['auth.User']"}) 69 | } 70 | } 71 | 72 | complete_apps = ['registration'] 73 | -------------------------------------------------------------------------------- /src/registration/backends/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Base class of registration backend 5 | 6 | All backends of django-inspectional-registration should be a subclass 7 | of the ``BackendBase`` 8 | """ 9 | __author__ = 'Alisue ' 10 | from registration.utils import get_site 11 | 12 | 13 | class RegistrationBackendBase(object): 14 | 15 | """Base class of registration backend 16 | 17 | Methods: 18 | get_site -- return current site 19 | register -- register a new user 20 | accept -- accept a registration 21 | reject -- reject a registration 22 | activate -- activate a user 23 | get_supplement_class -- get registration supplement class 24 | get_activation_form_class -- get activation form class 25 | get_registration_form_class -- get registration form class 26 | get_supplement_form_class -- get registration supplement form class 27 | get_activation_complete_url -- get activation complete redirect url 28 | get_registration_complete_url -- get registration complete redirect url 29 | get_registration_closed_url -- get registration closed redirect url 30 | registration_allowed -- whether registration is allowed now 31 | 32 | """ 33 | 34 | def get_site(self, request): 35 | """get current ``django.contrib.Site`` instance 36 | 37 | return ``django.contrib.RequestSite`` instance when the ``Site`` is 38 | not installed. 39 | 40 | """ 41 | return get_site(request) 42 | 43 | def register(self, username, email, request, 44 | supplement=None, send_email=True): 45 | """register a new user account with given ``username`` and ``email`` 46 | 47 | Returning should be a instance of new ``User`` 48 | 49 | """ 50 | raise NotImplementedError 51 | 52 | def accept(self, profile, request, 53 | send_email=True, message=None, force=False): 54 | """accept account registration with given ``profile`` (an instance of 55 | ``RegistrationProfile``) 56 | 57 | Returning should be a instance of accepted ``User`` for success, 58 | ``None`` for fail. 59 | 60 | This method **SHOULD** work even after the account registration has 61 | rejected. 62 | 63 | """ 64 | raise NotImplementedError 65 | 66 | def reject(self, profile, request, send_email=True, message=None): 67 | """reject account registration with given ``profile`` (an instance of 68 | ``RegistrationProfile``) 69 | 70 | Returning should be a instance of accepted ``User`` for success, 71 | ``None`` for fail. 72 | 73 | This method **SHOULD NOT** work after the account registration has 74 | accepted. 75 | 76 | """ 77 | raise NotImplementedError 78 | 79 | def activate(self, activation_key, request, password=None, send_email=True, 80 | message=None, no_profile_delete=False): 81 | """activate account with ``activation_key`` and ``password`` 82 | 83 | This method should be called after the account registration has 84 | accepted, otherwise it should not be success. 85 | 86 | Returning is ``user``, ``password`` and ``is_generated`` for success, 87 | ``None`` for fail. 88 | 89 | If ``password`` is not given, this method will generate password and 90 | ``is_generated`` should be ``True`` in this case. 91 | 92 | """ 93 | raise NotImplementedError 94 | 95 | def get_supplement_class(self): 96 | """Return the current registration supplement class""" 97 | raise NotImplementedError 98 | 99 | def get_activation_form_class(self): 100 | """get activation form class""" 101 | raise NotImplementedError 102 | 103 | def get_registration_form_class(self): 104 | """get registration form class""" 105 | raise NotImplementedError 106 | 107 | def get_supplement_form_class(self): 108 | """get registration supplement form class""" 109 | raise NotImplementedError 110 | 111 | def get_activation_complete_url(self, user): 112 | """get activation complete url""" 113 | raise NotImplementedError 114 | 115 | def get_registration_complete_url(self, user): 116 | """get registration complete url""" 117 | raise NotImplementedError 118 | 119 | def get_registration_closed_url(self): 120 | """get registration closed url""" 121 | raise NotImplementedError 122 | 123 | def registration_allowed(self): 124 | """return ``False`` if the registration has closed""" 125 | raise NotImplementedError 126 | -------------------------------------------------------------------------------- /docs/quicktutorials.rst: -------------------------------------------------------------------------------- 1 | **************************** 2 | Quick Tutorial 3 | **************************** 4 | 5 | 6 | 1. Install django-inspectional-registration 7 | ====================================================================================== 8 | 9 | django-inspectional-registration is found on PyPI so execute the 10 | following command:: 11 | 12 | $ pip install django-inspectional-registration 13 | 14 | or 15 | 16 | $ easy_install django-inspectional-registration 17 | 18 | And also the application is developped on `github `_ so you 19 | can install it from the repository as:: 20 | 21 | $ pip install git+https://github.com/lambdalisue/django-inspectional-registration.git#egg=django-inspectional-registration 22 | 23 | 2. Configure the application 24 | ========================================================== 25 | 26 | To configure django-inspectional-registration, follow the instructions below 27 | 28 | 1. Add ``'registration'``, ``'django.contrib.admin'`` to your ``INSTALLED_APPS`` of ``settings.py`` 29 | 30 | .. Note:: 31 | If you already use django-registration, see :doc:`quickmigrations` for 32 | migration. 33 | 34 | 2. Add ``'registration.supplements.default'`` to your ``INSTALLED_APPS`` of 35 | ``settings.py`` or set ``REGISTRATION_SUPPLEMENT_CLASS`` to ``None`` 36 | 37 | .. Note:: 38 | django-inspectional-registration can handle registration supplemental 39 | information. If you want to use your own custom registration 40 | supplemental information, check :doc:`about_registration_supplement` for 41 | documents. 42 | 43 | Settings ``REGISTRATION_SUPPLEMENT_CLASS`` to ``None`` mean no 44 | registration supplemental information will be used. 45 | 46 | 3. Add ``url('^registration/', include('registration.urls')),`` to your 47 | very top of (same directory as ``settings.py`` in default) ``urls.py`` like 48 | below:: 49 | 50 | from django.conf.urls.defaults import patterns, include, urls 51 | 52 | from django.contrib import admin 53 | admin.autodiscover() 54 | 55 | urlpatterns = pattern('', 56 | # some urls... 57 | 58 | # django-inspectional-registration require Django Admin page 59 | # to inspect registrations 60 | url('^admin/', include(admin.site.urls)), 61 | 62 | # Add django-inspectional-registration urls. The urls also define 63 | # Login, Logout and password_change or lot more for handle 64 | # registration. 65 | url('^registration/', include('registration.urls')), 66 | ) 67 | 68 | 4. Call ``syncdb`` command to create the database tables like below:: 69 | 70 | $ ./manage.py syncdb 71 | 72 | 5. Confirm that Django E-mail settings were properly configured. See 73 | https://docs.djangoproject.com/en/dev/topics/email/ for more detail. 74 | 75 | .. Note:: 76 | If you don't want or too lazy to configure the settings. See 77 | `django-mailer `_ which store 78 | the email on database before sending. 79 | 80 | To use django-mailer insted of django's default email system in this 81 | application. Simply add 'mailer' to your ``INSTALLED_APPS`` then the 82 | application will use django-mailer insted. 83 | 84 | How to use it 85 | ========================== 86 | 87 | 1. Access http://localhost:8000/registration/register then you will see 88 | the registration page. So fill up (use your own real email address) the 89 | fields and click ``Register`` button. 90 | 91 | .. Note:: 92 | Did you start your development server? If not:: 93 | 94 | $ ./manage.py runserver 8000 95 | 96 | 2. Now go on the http://localhost:8000/admin/registration/registrationprofile/1/ 97 | and accept your registration by clicking ``Save`` button. 98 | 99 | .. Note:: 100 | To reject or force to activate the registration, change ``Action`` 101 | and click ``Save`` 102 | 103 | ``Message`` will be passed to each email template thus you can use the 104 | value of ``Message`` as ``{{ message }}`` in your email template. In 105 | default, the ``Message`` is only available in rejection email template 106 | to explain why the registration was rejected. 107 | 108 | 3. You may get an Email from your website. The email contains an activation 109 | key so click the url. 110 | 111 | .. Note:: 112 | If you get ``http://example.com/register/activate/XXXXXXXX`` for your 113 | activation key, that mean you haven't configure the site domain name 114 | in Django Admin. To prevent this, just set domain name of your site in 115 | Admin page. 116 | 117 | 4. Two password form will be displayed on the activation page, fill up the 118 | password and click ``Activate`` to activate your account. 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/registration/compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Compatibility module 5 | """ 6 | __author__ = 'Alisue ' 7 | import django 8 | from django.conf import settings 9 | 10 | try: 11 | # django 1.4, timezone 12 | from django.utils.timezone import now as datetime_now 13 | except ImportError: 14 | import datetime 15 | datetime_now = datetime.datetime.now 16 | 17 | if django.VERSION >= (1, 9): 18 | patterns = lambda x, *args: args 19 | from django.conf.urls import url 20 | from django.conf.urls import include 21 | else: 22 | try: 23 | # django 1.4 24 | from django.conf.urls import url 25 | from django.conf.urls import patterns 26 | from django.conf.urls import include 27 | except ImportError: 28 | from django.conf.urls.defaults import url 29 | from django.conf.urls.defaults import patterns 30 | from django.conf.urls.defaults import include 31 | 32 | try: 33 | from django.contrib.admin.utils import unquote 34 | except ImportError: 35 | from django.contrib.admin.util import unquote 36 | 37 | # Python 2.7 has an importlib with import_module; for older Pythons, 38 | # Django's bundled copy provides it. 39 | try: 40 | from importlib import import_module 41 | except ImportError: 42 | from django.utils.importlib import import_module 43 | 44 | try: 45 | from hashlib import sha1 46 | except ImportError: 47 | from django.utils.hashcompat import sha_constructor as sha1 48 | 49 | # 50 | # Django change the transaction strategy from Django 1.6 51 | # https://docs.djangoproject.com/en/1.6/topics/db/transactions/ 52 | # 53 | # This change cause issue #15 thus the compatibility importing is required 54 | # 55 | # `change_view` in Django 1.6 use transaction.atomic 56 | # https://github.com/django/django/blob/1.6/django/contrib/admin/options.py#L1186 57 | # `change_view` in Django 1.5 use commit_on_success 58 | # https://github.com/django/django/blob/1.5/django/contrib/admin/options.py#L1063 59 | # 60 | try: 61 | from django.db.transaction import atomic as transaction_atomic 62 | except ImportError: 63 | from django.db.transaction import commit_on_success as transaction_atomic 64 | 65 | 66 | # Custom User support section ==============================================={{{ 67 | # 68 | # The following code (from === to ===) was copied from django-userena at 69 | # 2014/05/24 to solve issue #18. 70 | # The code is protected with BSD License. 71 | # 72 | # Ref: https://github.com/bread-and-pepper/django-userena/blob/master/userena/utils.py#L165-L176 73 | # 74 | # ---------------------------------------------------------------------------{{{ 75 | # Copyright (c) 2010, Petar Radosevic 76 | # All rights reserved. 77 | # 78 | # Redistribution and use in source and binary forms, with or without 79 | # modification, are permitted provided that the following conditions are 80 | # met: 81 | # 82 | # * Redistributions of source code must retain the above copyright 83 | # notice, this list of conditions and the following disclaimer. 84 | # * Redistributions in binary form must reproduce the above 85 | # copyright notice, this list of conditions and the following 86 | # disclaimer in the documentation and/or other materials provided 87 | # with the distribution. 88 | # * Neither the name of the author nor the names of other 89 | # contributors may be used to endorse or promote products derived 90 | # from this software without specific prior written permission. 91 | # 92 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 93 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 94 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 95 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 96 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 97 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 98 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 99 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 100 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 101 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 102 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 103 | # ---------------------------------------------------------------------------}}} 104 | # 105 | # Django 1.5 compatibility utilities, providing support for custom User models. 106 | # Since get_user_model() causes a circular import if called when app models are 107 | # being loaded, the user_model_label should be used when possible, with calls 108 | # to get_user_model deferred to execution time 109 | 110 | user_model_label = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') 111 | 112 | try: 113 | from django.contrib.auth import get_user_model 114 | except ImportError: 115 | from django.contrib.auth.models import User 116 | get_user_model = lambda: User 117 | # ===========================================================================}}} 118 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | # Django settings for tests project. 4 | import os 5 | import sys 6 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 7 | # add `src` 8 | sys.path.insert(0, os.path.join(BASE_DIR, 'src')) 9 | 10 | DEBUG = True 11 | 12 | ADMINS = ( 13 | # ('Your Name', 'your_email@domain.com'), 14 | ) 15 | 16 | MANAGERS = ADMINS 17 | 18 | DATABASES = { 19 | 'default': { 20 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 21 | 'NAME': 'database.db', # Or path to database file if using sqlite3. 22 | 'USER': '', # Not used with sqlite3. 23 | 'PASSWORD': '', # Not used with sqlite3. 24 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 25 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 26 | } 27 | } 28 | 29 | # Local time zone for this installation. Choices can be found here: 30 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 31 | # although not all choices may be available on all operating systems. 32 | # On Unix systems, a value of None will cause Django to use the same 33 | # timezone as the operating system. 34 | # If running in a Windows environment this must be set to the same as your 35 | # system time zone. 36 | TIME_ZONE = 'America/Chicago' 37 | 38 | # Language code for this installation. All choices can be found here: 39 | # http://www.i18nguy.com/unicode/language-identifiers.html 40 | LANGUAGE_CODE = 'en-us' 41 | 42 | SITE_ID = 1 43 | 44 | # If you set this to False, Django will make some optimizations so as not 45 | # to load the internationalization machinery. 46 | USE_I18N = True 47 | 48 | # If you set this to False, Django will not format dates, numbers and 49 | # calendars according to the current locale 50 | USE_L10N = True 51 | 52 | # Absolute path to the directory that holds media. 53 | # Example: "/home/media/media.lawrence.com/" 54 | MEDIA_ROOT = '' 55 | STATIC_ROOT = '' 56 | 57 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 58 | # trailing slash if there is a path component (optional in other cases). 59 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 60 | MEDIA_URL = '' 61 | 62 | # Make this unique, and don't share it with anybody. 63 | SECRET_KEY = 'rbs62_^fuahxz!4k1!&yj$h8a=&-h_%do+3jk&%#v=o2%ep=7@' 64 | 65 | 66 | MIDDLEWARE_CLASSES = ( 67 | 'django.middleware.common.CommonMiddleware', 68 | 'django.contrib.sessions.middleware.SessionMiddleware', 69 | 'django.middleware.csrf.CsrfViewMiddleware', 70 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 71 | 'django.contrib.messages.middleware.MessageMiddleware', 72 | ) 73 | 74 | ROOT_URLCONF = 'tests.urls' 75 | 76 | INSTALLED_APPS = ( 77 | 'django.contrib.auth', 78 | 'django.contrib.contenttypes', 79 | 'django.contrib.sessions', 80 | 'django.contrib.sites', 81 | 'django.contrib.messages', 82 | # Uncomment the next line to enable the admin: 83 | 'django.contrib.admin', 84 | 'registration.supplements.default', 85 | 'registration', 86 | 'registration.contrib.notification', 87 | 'registration.contrib.autologin', 88 | ) 89 | 90 | REGISTRATION_SUPPLEMENT_CLASS = ( 91 | 'registration.supplements.default.models.DefaultRegistrationSupplement') 92 | 93 | ACCOUNT_ACTIVATION_DAYS = 7 94 | 95 | import django 96 | 97 | if django.VERSION <= (1, 3): 98 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 99 | # trailing slash. 100 | # Examples: "http://foo.com/media/", "/media/". 101 | ADMIN_MEDIA_PREFIX = '/media/' 102 | 103 | if django.VERSION >= (1, 6): 104 | TEST_RUNNER = 'django.test.runner.DiscoverRunner' 105 | 106 | if django.VERSION >= (1, 10): 107 | TEMPLATES = [ 108 | { 109 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 110 | 'DIRS': [ 111 | os.path.join(os.path.dirname(__file__), 'templates'), 112 | ], 113 | 'OPTIONS': { 114 | 'context_processors': [ 115 | 'django.contrib.auth.context_processors.auth', 116 | ], 117 | 'loaders': [ 118 | 'django.template.loaders.filesystem.Loader', 119 | 'django.template.loaders.app_directories.Loader', 120 | ], 121 | 'debug': DEBUG, 122 | }, 123 | } 124 | ] 125 | else: 126 | TEMPLATE_DEBUG = DEBUG 127 | # List of callables that know how to import templates from various sources. 128 | TEMPLATE_LOADERS = ( 129 | 'django.template.loaders.filesystem.Loader', 130 | 'django.template.loaders.app_directories.Loader', 131 | # 'django.template.loaders.eggs.Loader', 132 | ) 133 | 134 | TEMPLATE_DIRS = ( 135 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 136 | # Always use forward slashes, even on Windows. 137 | # Don't forget to use absolute paths, not relative paths. 138 | os.path.join(os.path.dirname(__file__), 'templates'), 139 | ) 140 | -------------------------------------------------------------------------------- /src/registration/south_migrations/0002_auto__add_field_registrationprofile__status__chg_field_registrationpro.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | import datetime 4 | from south.db import db 5 | from south.v2 import SchemaMigration 6 | from django.db import models 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Adding field 'RegistrationProfile._status' 13 | db.add_column('registration_registrationprofile', '_status', self.gf('django.db.models.fields.CharField')(default='untreated', max_length=10, db_column='status'), keep_default=False) 14 | 15 | # Changing field 'RegistrationProfile.activation_key' 16 | db.alter_column('registration_registrationprofile', 'activation_key', self.gf('django.db.models.fields.CharField')(max_length=40, null=True)) 17 | 18 | # Changing field 'RegistrationProfile.user' 19 | db.alter_column('registration_registrationprofile', 'user_id', self.gf('django.db.models.fields.related.OneToOneField')(unique=True, to=orm['auth.User'])) 20 | 21 | 22 | def backwards(self, orm): 23 | 24 | # Deleting field 'RegistrationProfile._status' 25 | db.delete_column('registration_registrationprofile', 'status') 26 | 27 | # Changing field 'RegistrationProfile.activation_key' 28 | db.alter_column('registration_registrationprofile', 'activation_key', self.gf('django.db.models.fields.CharField')(default='', max_length=40)) 29 | 30 | # Changing field 'RegistrationProfile.user' 31 | db.alter_column('registration_registrationprofile', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], unique=True)) 32 | 33 | 34 | models = { 35 | 'auth.group': { 36 | 'Meta': {'object_name': 'Group'}, 37 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 38 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 39 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 40 | }, 41 | 'auth.permission': { 42 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 43 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 44 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 45 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 46 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 47 | }, 48 | 'auth.user': { 49 | 'Meta': {'object_name': 'User'}, 50 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 51 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 52 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 53 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 56 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 57 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 58 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 59 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 60 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 61 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 62 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 63 | }, 64 | 'contenttypes.contenttype': { 65 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 66 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 67 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 68 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 69 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 70 | }, 71 | 'registration.registrationprofile': { 72 | 'Meta': {'object_name': 'RegistrationProfile'}, 73 | '_status': ('django.db.models.fields.CharField', [], {'default': "'untreated'", 'max_length': '10', 'db_column': "'status'"}), 74 | 'activation_key': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '40', 'null': 'True'}), 75 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 76 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'registration_profile'", 'unique': 'True', 'to': "orm['auth.User']"}) 77 | } 78 | } 79 | 80 | complete_apps = ['registration'] 81 | -------------------------------------------------------------------------------- /src/registration/tests/test_supplements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | """ 5 | __author__ = 'Alisue ' 6 | from django.test import TestCase 7 | from django.core import mail 8 | from django.core.urlresolvers import reverse 9 | from django.core.exceptions import ImproperlyConfigured 10 | 11 | from registration import forms 12 | from registration.supplements import get_supplement_class 13 | from registration.models import RegistrationProfile 14 | from registration.tests.utils import with_apps 15 | from registration.tests.compat import override_settings 16 | 17 | 18 | class RegistrationSupplementRetrievalTests(TestCase): 19 | 20 | def test_get_supplement_class(self): 21 | from registration.supplements.default.models import DefaultRegistrationSupplement 22 | supplement_class = get_supplement_class( 23 | 'registration.supplements.default.models.DefaultRegistrationSupplement') 24 | self.failUnless(supplement_class is DefaultRegistrationSupplement) 25 | 26 | def test_supplement_error_invalid(self): 27 | self.assertRaises(ImproperlyConfigured, get_supplement_class, 28 | 'registration.supplements.doesnotexist.NonExistenBackend') 29 | 30 | def test_supplement_attribute_error(self): 31 | self.assertRaises(ImproperlyConfigured, get_supplement_class, 32 | 'registration.supplements.default.NonexistenBackend') 33 | 34 | 35 | @with_apps( 36 | 'django.contrib.contenttypes', 37 | 'registration.supplements.default' 38 | ) 39 | @override_settings( 40 | ACCOUNT_ACTIVATION_DAYS=7, 41 | REGISTRATION_OPEN=True, 42 | REGISTRATION_SUPPLEMENT_CLASS=( 43 | 'registration.supplements.default.models.DefaultRegistrationSupplement'), 44 | REGISTRATION_BACKEND_CLASS=( 45 | 'registration.backends.default.DefaultRegistrationBackend'), 46 | ) 47 | class RegistrationViewWithDefaultRegistrationSupplementTestCase(TestCase): 48 | def test_registration_view_get(self): 49 | """ 50 | A ``GET`` to the ``register`` view uses the appropriate 51 | template and populates the registration form into the context. 52 | 53 | """ 54 | from registration.supplements.default.models import DefaultRegistrationSupplement 55 | response = self.client.get(reverse('registration_register')) 56 | self.assertEqual(response.status_code, 200) 57 | self.assertTemplateUsed(response, 58 | 'registration/registration_form.html') 59 | self.failUnless(isinstance(response.context['form'], 60 | forms.RegistrationForm)) 61 | self.failUnless(isinstance(response.context['supplement_form'].instance, 62 | DefaultRegistrationSupplement)) 63 | 64 | def test_registration_view_post_success(self): 65 | """ 66 | A ``POST`` to the ``register`` view with valid data properly 67 | creates a new user and issues a redirect. 68 | 69 | """ 70 | from registration.supplements.default.models import DefaultRegistrationSupplement 71 | response = self.client.post(reverse('registration_register'), 72 | data={'username': 'alice', 73 | 'email1': 'alice@example.com', 74 | 'email2': 'alice@example.com', 75 | 'remarks': 'Hello'}) 76 | self.assertRedirects(response, 77 | 'http://testserver%s' % reverse('registration_complete')) 78 | self.assertEqual(RegistrationProfile.objects.count(), 1) 79 | self.assertEqual(DefaultRegistrationSupplement.objects.count(), 1) 80 | self.assertEqual(len(mail.outbox), 1) 81 | 82 | profile = RegistrationProfile.objects.get(user__username='alice') 83 | self.assertEqual(profile.supplement.remarks, 'Hello') 84 | 85 | def test_registration_view_post_failure(self): 86 | """ 87 | A ``POST`` to the ``register`` view with invalid data does not 88 | create a user, and displays appropriate error messages. 89 | 90 | """ 91 | response = self.client.post(reverse('registration_register'), 92 | data={'username': 'bob', 93 | 'email1': 'bobe@example.com', 94 | 'email2': 'mark@example.com', 95 | 'remarks': 'Hello'}) 96 | self.assertEqual(response.status_code, 200) 97 | self.failIf(response.context['form'].is_valid()) 98 | self.failUnless(response.context['supplement_form'].is_valid()) 99 | self.assertFormError(response, 'form', field=None, 100 | errors="The two email fields didn't match.") 101 | self.assertEqual(len(mail.outbox), 0) 102 | 103 | def test_registration_view_post_no_remarks_failure(self): 104 | """ 105 | A ``POST`` to the ``register`` view with invalid data does not 106 | create a user, and displays appropriate error messages. 107 | 108 | """ 109 | response = self.client.post(reverse('registration_register'), 110 | data={'username': 'bob', 111 | 'email1': 'bobe@example.com', 112 | 'email2': 'bobe@example.com'}) 113 | self.assertEqual(response.status_code, 200) 114 | self.failUnless(response.context['form'].is_valid()) 115 | self.failIf(response.context['supplement_form'].is_valid()) 116 | self.assertFormError(response, 'supplement_form', field='remarks', 117 | errors="This field is required.") 118 | self.assertEqual(len(mail.outbox), 0) 119 | -------------------------------------------------------------------------------- /src/registration/south_migrations/0005_auto__add_defaultregistrationsupplement__chg_field_registrationprofile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'DefaultRegistrationSupplement' 12 | db.create_table(u'registration_defaultregistrationsupplement', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('registration_profile', self.gf('django.db.models.fields.related.OneToOneField')(related_name=u'_registration_defaultregistrationsupplement_supplement', unique=True, to=orm['registration.RegistrationProfile'])), 15 | ('remarks', self.gf('django.db.models.fields.TextField')()), 16 | )) 17 | db.send_create_signal(u'registration', ['DefaultRegistrationSupplement']) 18 | 19 | 20 | # Changing field 'RegistrationProfile._status' 21 | db.alter_column(u'registration_registrationprofile', u'status', self.gf('django.db.models.fields.CharField')(max_length=10, db_column=u'status')) 22 | 23 | def backwards(self, orm): 24 | # Deleting model 'DefaultRegistrationSupplement' 25 | db.delete_table(u'registration_defaultregistrationsupplement') 26 | 27 | 28 | # Changing field 'RegistrationProfile._status' 29 | db.alter_column(u'registration_registrationprofile', 'status', self.gf(u'django.db.models.fields.CharField')(max_length=10, db_column='status')) 30 | 31 | models = { 32 | u'auth.group': { 33 | 'Meta': {'object_name': 'Group'}, 34 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 35 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 36 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 37 | }, 38 | u'auth.permission': { 39 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, 40 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 41 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), 42 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 43 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 44 | }, 45 | u'auth.user': { 46 | 'Meta': {'object_name': 'User'}, 47 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 48 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 49 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 50 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 51 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 52 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 53 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 54 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 55 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 56 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 57 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 58 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 59 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 60 | }, 61 | u'contenttypes.contenttype': { 62 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 63 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 64 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 65 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 66 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 67 | }, 68 | u'registration.defaultregistrationsupplement': { 69 | 'Meta': {'object_name': 'DefaultRegistrationSupplement'}, 70 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 71 | 'registration_profile': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "u'_registration_defaultregistrationsupplement_supplement'", 'unique': 'True', 'to': u"orm['registration.RegistrationProfile']"}), 72 | 'remarks': ('django.db.models.fields.TextField', [], {}) 73 | }, 74 | u'registration.registrationprofile': { 75 | 'Meta': {'object_name': 'RegistrationProfile'}, 76 | '_status': ('django.db.models.fields.CharField', [], {'default': "u'untreated'", 'max_length': '10', 'db_column': "u'status'"}), 77 | 'activation_key': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '40', 'null': 'True'}), 78 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 79 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "u'registration_profile'", 'unique': 'True', 'to': u"orm['auth.User']"}) 80 | } 81 | } 82 | 83 | complete_apps = ['registration'] -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-inspectional-registration.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-inspectional-registration.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ************************************************************ 2 | Welcome to django-inspectional-registration's documentation! 3 | ************************************************************ 4 | .. image:: https://secure.travis-ci.org/lambdalisue/django-inspectional-registration.png?branch=master 5 | :target: http://travis-ci.org/lambdalisue/django-inspectional-registration 6 | :alt: Build status 7 | 8 | .. image:: https://coveralls.io/repos/lambdalisue/django-inspectional-registration/badge.png?branch=master 9 | :target: https://coveralls.io/r/lambdalisue/django-inspectional-registration/ 10 | :alt: Coverage 11 | 12 | .. image:: https://pypip.in/d/django-inspectional-registration/badge.png 13 | :target: https://pypi.python.org/pypi/django-inspectional-registration/ 14 | :alt: Downloads 15 | 16 | .. image:: https://pypip.in/v/django-inspectional-registration/badge.png 17 | :target: https://pypi.python.org/pypi/django-inspectional-registration/ 18 | :alt: Latest version 19 | 20 | .. image:: https://pypip.in/wheel/django-inspectional-registration/badge.png 21 | :target: https://pypi.python.org/pypi/django-inspectional-registration/ 22 | :alt: Wheel Status 23 | 24 | .. image:: https://pypip.in/egg/django-inspectional-registration/badge.png 25 | :target: https://pypi.python.org/pypi/django-inspectional-registration/ 26 | :alt: Egg Status 27 | 28 | .. image:: https://pypip.in/license/django-inspectional-registration/badge.png 29 | :target: https://pypi.python.org/pypi/django-inspectional-registration/ 30 | :alt: License 31 | 32 | Author 33 | Alisue 34 | Supported python versions 35 | 2.6, 2.7, 3.2, 3.3, 3.4 36 | Supported django versions 37 | 1.3 - 1.7 38 | 39 | django-inspectional-registration is a enhanced application of 40 | django-registration_. The following features are available 41 | 42 | - Inspection steps for registration. You can accept or reject the account 43 | registration before sending activation key to the user. 44 | 45 | - Password will be filled in after the activation step to prevent that the 46 | user forget them previously filled password in registration step (No 47 | password filling in registration step) 48 | 49 | - Password can be generated programatically and force to activate the 50 | user. The generated password will be sent to the user by e-mail. 51 | 52 | - Any Django models are available to use as supplemental information of 53 | registration if the models are subclasses of 54 | ``registration.supplements.RegistrationSupplementBase``. 55 | It is commonly used for inspection. 56 | 57 | - You can send any additional messages to the user in each steps 58 | (acceptance, rejection and activation) 59 | 60 | - The behaviors of the application are customizable with Backend feature. 61 | 62 | - The E-mails or HTMLs are customizable with Django template system. 63 | 64 | - Can be migrate from django-registration_ simply by south_ 65 | 66 | - `django-mailer `_ compatible. 67 | Emails sent from the application will use django-mailer if 'mailer' is 68 | in your ``INSTALLED_APPS`` 69 | 70 | 71 | Documentations 72 | ============================================================================== 73 | 74 | .. toctree:: 75 | :maxdepth: 2 76 | 77 | quicktutorials 78 | quickmigrations 79 | about_registration_supplement 80 | about_registration_backend 81 | about_registration_templates 82 | about_registration_signals 83 | about_registration_settings 84 | about_registration_contrib 85 | faq 86 | 87 | API Reference 88 | 89 | The difference between django-registration 90 | ============================================================================== 91 | 92 | While django-registration_ requires 3 steps for registration, 93 | django-inspectional-registration requires 5 steps and inspector for 94 | registration. See the conceptual summary below. 95 | 96 | 97 | .. image:: _static/img/difference_summary.png 98 | 99 | 100 | .. _django-registration: https://bitbucket.org/ubernostrum/django-registration/ 101 | .. _south: http://south.aeracode.org/ 102 | 103 | 104 | For translators 105 | ================================================================================ 106 | You can compile the latest message files with the following command 107 | 108 | .. code:: sh 109 | 110 | $ python setup.py compile_messages 111 | 112 | The command above is automatically called before ``sdist`` command if you call 113 | ``python manage.py sdist``. 114 | 115 | 116 | Backward incompatibility 117 | ================================================================================ 118 | Because of an `issue#24 `_, django-inspectional-registration add the following three new options. 119 | 120 | - ``REGISTRATION_DJANGO_AUTH_URLS_ENABLE`` 121 | If it is ``False``, django-inspectional-registration do not define the views of django.contrib.auth. 122 | It is required to define these view manually. (Default: ``True``) 123 | - ``REGISTRATION_DJANGO_AUTH_URL_NAMES_PREFIX`` 124 | It is used as a prefix string of view names of django.contrib.auth. 125 | For backward compatibility, set this value to ``'auth_'``. (Default: ``''``) 126 | - ``REGISTRATION_DJANGO_AUTH_URL_NAMES_SUFFIX`` 127 | It is used as a suffix string of view names of django.contrib.auth. 128 | For backward compatibility, set this value to ``''``. (Default: ``''``) 129 | 130 | This changes were introduced from version 0.4.0, to keep the backward compatibility, write the following in your settings module. 131 | 132 | .. code:: python 133 | 134 | REGISTRATION_DJANGO_AUTH_URLS_ENABLE = True 135 | REGISTRATION_DJANGO_AUTH_URL_NAMES_PREFIX = 'auth_' 136 | REGISTRATION_DJANGO_AUTH_URL_NAMES_SUFFIX = '' 137 | 138 | 139 | Indices and tables 140 | ================== 141 | 142 | * :ref:`genindex` 143 | * :ref:`modindex` 144 | * :ref:`search` 145 | 146 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | import os 4 | from setuptools import setup, find_packages, Command 5 | from setuptools.command.sdist import sdist as original_sdist 6 | 7 | NAME = 'django-inspectional-registration' 8 | VERSION = '0.6.2' 9 | 10 | 11 | class compile_docs(Command): 12 | description = ("re-compile documentations") 13 | user_options = [] 14 | 15 | def initialize_options(self): 16 | self.cwd = None 17 | 18 | def finalize_options(self): 19 | self.cwd = os.getcwd() 20 | 21 | def run(self): 22 | compile_docs.compile_docs() 23 | 24 | @classmethod 25 | def compile_docs(cls): 26 | """ 27 | Compile '.rst' files into '.html' files via Sphinx. 28 | """ 29 | original_cwd = os.getcwd() 30 | BASE = os.path.abspath(os.path.dirname(__file__)) 31 | root = os.path.join(BASE, 'docs') 32 | os.chdir(root) 33 | os.system('make html') 34 | os.system('xdg-open _build/html/index.html') 35 | os.chdir(original_cwd) 36 | return True 37 | 38 | 39 | class compile_messages(Command): 40 | description = ("re-compile local message files ('.po' to '.mo'). " 41 | "it require django-admin.py") 42 | user_options = [] 43 | 44 | def initialize_options(self): 45 | self.cwd = None 46 | 47 | def finalize_options(self): 48 | self.cwd = os.getcwd() 49 | 50 | def run(self): 51 | compile_messages.compile_messages() 52 | 53 | @classmethod 54 | def compile_messages(cls): 55 | """ 56 | Compile '.po' into '.mo' via 'django-admin.py' thus the function 57 | require the django to be installed. 58 | 59 | It return True when the process successfully end, otherwise it print 60 | error messages and return False. 61 | 62 | https://docs.djangoproject.com/en/dev/ref/django-admin/#compilemessages 63 | """ 64 | try: 65 | import django 66 | except ImportError: 67 | print('####################################################\n' 68 | 'Django is not installed.\nIt will not be possible to ' 69 | 'compile the locale files during installation of ' 70 | 'django-inspectional-registration.\nPlease, install ' 71 | 'Django first. Done so, install the django-registration' 72 | '-inspectional\n' 73 | '####################################################\n') 74 | return False 75 | else: 76 | original_cwd = os.getcwd() 77 | BASE = os.path.abspath(os.path.dirname(__file__)) 78 | root = os.path.join(BASE, 'src/registration') 79 | os.chdir(root) 80 | os.system('django-admin.py compilemessages') 81 | os.chdir(original_cwd) 82 | return True 83 | 84 | 85 | class sdist(original_sdist): 86 | """ 87 | Run 'sdist' command but make sure that the message files are latest by 88 | running 'compile_messages' before 'sdist' 89 | """ 90 | def run(self): 91 | compile_messages.compile_messages() 92 | original_sdist.run(self) 93 | 94 | 95 | def read(filename): 96 | import os 97 | BASE_DIR = os.path.dirname(__file__) 98 | filename = os.path.join(BASE_DIR, filename) 99 | with open(filename, 'r') as fi: 100 | return fi.read() 101 | 102 | 103 | def readlist(filename): 104 | rows = read(filename).split("\n") 105 | rows = [x.strip() for x in rows if x.strip()] 106 | return list(rows) 107 | 108 | # if we are running on python 3, enable 2to3 and 109 | # let it use the custom fixers from the custom_fixers 110 | # package. 111 | extra = {} 112 | if sys.version_info >= (3, 0): 113 | extra.update( 114 | use_2to3=True, 115 | ) 116 | 117 | setup( 118 | name=NAME, 119 | version=VERSION, 120 | description=("Django registration app which required inspection step " 121 | "before activation"), 122 | long_description = read('README.rst'), 123 | classifiers = ( 124 | 'Development Status :: 5 - Production/Stable', 125 | 'Environment :: Web Environment', 126 | 'Framework :: Django', 127 | 'Intended Audience :: Developers', 128 | 'License :: OSI Approved :: MIT License', 129 | 'Operating System :: OS Independent', 130 | 'Programming Language :: Python', 131 | 'Programming Language :: Python :: 2', 132 | 'Programming Language :: Python :: 2.6', 133 | 'Programming Language :: Python :: 2.7', 134 | 'Programming Language :: Python :: 3', 135 | 'Programming Language :: Python :: 3.2', 136 | 'Programming Language :: Python :: 3.3', 137 | 'Programming Language :: Python :: 3.4', 138 | 'Topic :: Internet :: WWW/HTTP', 139 | 'Topic :: Software Development :: Libraries', 140 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 141 | 'Topic :: Software Development :: Libraries :: Python Modules', 142 | ), 143 | keywords = "django app registration inspection", 144 | author = 'Alisue', 145 | author_email = 'lambdalisue@hashnote.net', 146 | url = 'https://github.com/lambdalisue/%s' % NAME, 147 | download_url = 'https://github.com/lambdalisue/%s/tarball/master' % NAME, 148 | license = 'MIT', 149 | packages = find_packages('src'), 150 | package_dir = {'': 'src'}, 151 | include_package_data = True, 152 | package_data = { 153 | '': ['README.rst', 154 | 'requirements.txt', 155 | 'requirements-test.txt', 156 | 'requirements-docs.txt'], 157 | }, 158 | zip_safe=True, 159 | install_requires=readlist('requirements.txt'), 160 | test_suite='runtests.run_tests', 161 | tests_require=readlist('requirements-test.txt'), 162 | cmdclass={ 163 | 'compile_messages': compile_messages, 164 | 'compile_docs': compile_docs, 165 | 'sdist': sdist, 166 | }, 167 | **extra 168 | ) 169 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-inspectional-registration.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-inspectional-registration.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-inspectional-registration" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-inspectional-registration" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /src/registration/contrib/notification/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Send notification emails to admins, managers or particular recipients 5 | when new user has registered in the site 6 | 7 | admins or managers are determined from ``ADMINS`` and ``MANAGERS`` attribute of 8 | ``settings.py`` 9 | 10 | You can disable this notification feature by setting ``False`` to 11 | ``REGISTRATION_NOTIFICATION``. 12 | You can disable sending emails to admins by setting ``False`` to 13 | ``REGISTRATION_NOTIFICATION_ADMINS``. 14 | You can disable sending emails to managers by settings ``False`` to 15 | ``REGISTRATION_NOTIFICATION_MANAGERS``. 16 | 17 | If you need extra recipients for the notification email, set a list of email 18 | addresses or a function which return a list to 19 | ``REGISTRATION_NOTIFICATION_RECIPIENTS`` 20 | 21 | The notification email use the following templates in default 22 | 23 | ``registration/notification_email.txt`` 24 | Used for email body, the following context will be passed 25 | 26 | ``site`` 27 | A instance of ``django.contrib.sites.models.Site`` or 28 | ``django.contrib.sites.models.RequestSite`` 29 | 30 | ``user`` 31 | A ``User`` instance who has just registered 32 | 33 | ``profile`` 34 | A ``RegistrationProfile`` instance of the ``user`` 35 | 36 | ``registration/notification_email_subject.txt`` 37 | Used for email subject, the following context will be passed 38 | 39 | ``site`` 40 | A instance of ``django.contrib.sites.models.Site`` or 41 | ``django.contrib.sites.models.RequestSite`` 42 | 43 | ``user`` 44 | A ``User`` instance who has just registered 45 | 46 | ``profile`` 47 | A ``RegistrationProfile`` instance of the ``user`` 48 | 49 | .. Note:: 50 | Newlies of the template will be removed. 51 | 52 | If you want to change the name of template, use following settings 53 | 54 | - ``REGISTRATION_NOTIFICATION_EMAIL_TEMPLATE_NAME`` 55 | - ``REGISTRATION_NOTIFICATION_EMAIL_SUBJECT_TEMPLATE_NAME`` 56 | 57 | 58 | .. Note:: 59 | This feature is not available in tests because default tests of 60 | django-inspectional-registration are not assumed to test with contributes. 61 | 62 | If you do want this feature to be available in tests, set 63 | ``_REGISTRATION_NOTIFICATION_IN_TESTS`` to ``True`` in ``setUp()`` method 64 | of the test case class and delete the attribute in ``tearDown()`` method. 65 | 66 | """ 67 | __author__ = "Alisue " 68 | import sys 69 | 70 | from django.core.exceptions import ImproperlyConfigured 71 | from django.template.loader import render_to_string 72 | 73 | from registration.utils import get_site 74 | from registration.utils import send_mail 75 | from registration.signals import user_registered 76 | from registration.contrib.notification.conf import settings 77 | 78 | 79 | def is_notification_enable(): 80 | """get whether the registration notification is enable""" 81 | if not settings.REGISTRATION_NOTIFICATION: 82 | return False 83 | if 'test' in sys.argv and not getattr(settings, 84 | '_REGISTRATION_NOTIFICATION_IN_TESTS', 85 | False): 86 | # Registration Notification is not available in test to prevent the test 87 | # fails of ``registration.tests.*``. 88 | # For testing Registration Notification, you must set 89 | # ``_REGISTRATION_NOTIFICATION_IN_TESTS`` to ``True`` 90 | return False 91 | admins = settings.REGISTRATION_NOTIFICATION_ADMINS 92 | managers = settings.REGISTRATION_NOTIFICATION_MANAGERS 93 | recipients = settings.REGISTRATION_NOTIFICATION_RECIPIENTS 94 | if not (admins or managers or recipients): 95 | # All REGISTRATION_NOTIFICATION_{ADMINS, MANAGERS, RECIPIENTS} = False 96 | # is same as REGISTRATION_NOTIFICATION = False but user should use 97 | # REGISTRATION_NOTIFICATION = False insted of setting False to all 98 | # settings of notification. 99 | import warnings 100 | warnings.warn( 101 | 'To set ``registration.contrib.notification`` disable, ' 102 | 'set ``REGISTRATION_NOTIFICATION`` to ``False``') 103 | return False 104 | return True 105 | 106 | 107 | def send_notification_email_reciver(sender, user, profile, request, **kwargs): 108 | """send a notification email to admins/managers""" 109 | if not is_notification_enable(): 110 | return 111 | 112 | context = { 113 | 'user': user, 114 | 'profile': profile, 115 | 'site': get_site(request), 116 | } 117 | subject = render_to_string( 118 | settings.REGISTRATION_NOTIFICATION_EMAIL_SUBJECT_TEMPLATE_NAME, 119 | context) 120 | subject = "".join(subject.splitlines()) 121 | message = render_to_string( 122 | settings.REGISTRATION_NOTIFICATION_EMAIL_TEMPLATE_NAME, 123 | context) 124 | 125 | recipients = [] 126 | if settings.REGISTRATION_NOTIFICATION_ADMINS: 127 | for userinfo in settings.ADMINS: 128 | recipients.append(userinfo[1]) 129 | if settings.REGISTRATION_NOTIFICATION_MANAGERS: 130 | for userinfo in settings.MANAGERS: 131 | recipients.append(userinfo[1]) 132 | if settings.REGISTRATION_NOTIFICATION_RECIPIENTS: 133 | method_or_iterable = settings.REGISTRATION_NOTIFICATION_RECIPIENTS 134 | if callable(method_or_iterable): 135 | recipients.extend(method_or_iterable()) 136 | elif isinstance(method_or_iterable, (list, tuple)): 137 | recipients.extend(method_or_iterable) 138 | else: 139 | raise ImproperlyConfigured(( 140 | '``REGISTRATION_NOTIFICATION_RECIPIENTS`` must ' 141 | 'be a list of recipients or function which return ' 142 | 'a list of recipients (Currently the value was "%s")' 143 | ) % method_or_iterable) 144 | # remove duplications 145 | recipients = frozenset(recipients) 146 | 147 | mail_from = getattr(settings, 'REGISTRATION_FROM_EMAIL', '') or \ 148 | settings.DEFAULT_FROM_EMAIL 149 | send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, recipients) 150 | user_registered.connect(send_notification_email_reciver) 151 | -------------------------------------------------------------------------------- /src/registration/contrib/notification/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | """ 5 | __author__ = 'Alisue ' 6 | from django.test import TestCase 7 | from django.conf import settings 8 | from django.core import mail 9 | from registration.backends.default import DefaultRegistrationBackend 10 | from registration.tests.mock import mock_request 11 | from registration.tests.compat import override_settings 12 | 13 | 14 | @override_settings( 15 | ACCOUNT_ACTIVATION_DAYS=7, 16 | ADMINS=( 17 | ('admin1', 'admin1@test.com'), 18 | ('admin2', 'admin2@test.com'), 19 | ), 20 | MANAGERS=( 21 | ('manager1', 'manager1@test.com'), 22 | ('manager2', 'manager2@test.com'), 23 | ), 24 | REGISTRATION_OPEN=True, 25 | REGISTRATION_SUPPLEMENT_CLASS=None, 26 | REGISTRATION_BACKEND_CLASS=( 27 | 'registration.backends.default.DefaultRegistrationBackend'), 28 | REGISTRATION_REGISTRATION_EMAIL=False, 29 | REGISTRATION_NOTIFICATION=True, 30 | REGISTRATION_NOTIFICATION_ADMINS=True, 31 | REGISTRATION_NOTIFICATION_MANAGERS=True, 32 | REGISTRATION_NOTIFICATION_RECIPIENTS=False, 33 | _REGISTRATION_NOTIFICATION_IN_TESTS=True, 34 | ) 35 | class RegistrationNotificationTestCase(TestCase): 36 | backend = DefaultRegistrationBackend() 37 | mock_request = mock_request() 38 | 39 | def test_notify_admins(self): 40 | with override_settings(REGISTRATION_NOTIFICATION_MANAGERS=False): 41 | self.backend.register( 42 | 'bob', 'bob@test.com', request=self.mock_request 43 | ) 44 | 45 | self.assertEqual(len(mail.outbox), 1) 46 | self.assertEqual(sorted(mail.outbox[0].to), sorted([ 47 | 'admin1@test.com', 48 | 'admin2@test.com', 49 | ])) 50 | 51 | def test_notify_managers(self): 52 | with override_settings(REGISTRATION_NOTIFICATION_ADMINS=False): 53 | self.backend.register( 54 | 'bob', 'bob@test.com', request=self.mock_request 55 | ) 56 | 57 | self.assertEqual(len(mail.outbox), 1) 58 | self.assertEqual(sorted(mail.outbox[0].to), sorted([ 59 | 'manager1@test.com', 60 | 'manager2@test.com', 61 | ])) 62 | 63 | def test_notify_recipients_iterable(self): 64 | with override_settings( 65 | REGISTRATION_NOTIFICATION_ADMINS = False, 66 | REGISTRATION_NOTIFICATION_MANAGERS = False, 67 | REGISTRATION_NOTIFICATION_RECIPIENTS=( 68 | 'recipient1@test.com', 69 | 'recipient2@test.com', 70 | )): 71 | self.backend.register( 72 | 'bob', 'bob@test.com', request=self.mock_request 73 | ) 74 | 75 | self.assertEqual(len(mail.outbox), 1) 76 | self.assertEqual(sorted(mail.outbox[0].to), sorted([ 77 | 'recipient1@test.com', 78 | 'recipient2@test.com', 79 | ])) 80 | 81 | def test_notify_recipients_function(self): 82 | with override_settings( 83 | REGISTRATION_NOTIFICATION_ADMINS=False, 84 | REGISTRATION_NOTIFICATION_MANAGERS=False, 85 | REGISTRATION_NOTIFICATION_RECIPIENTS=lambda:( 86 | 'recipient1@test.com', 87 | 'recipient2@test.com', 88 | )): 89 | self.backend.register( 90 | 'bob', 'bob@test.com', request=self.mock_request 91 | ) 92 | 93 | self.assertEqual(len(mail.outbox), 1) 94 | self.assertEqual(sorted(mail.outbox[0].to), sorted([ 95 | 'recipient1@test.com', 96 | 'recipient2@test.com', 97 | ])) 98 | 99 | def test_notify_all(self): 100 | with override_settings( 101 | REGISTRATION_NOTIFICATION_ADMINS=True, 102 | REGISTRATION_NOTIFICATION_MANAGERS=True, 103 | REGISTRATION_NOTIFICATION_RECIPIENTS=( 104 | 'recipient1@test.com', 105 | 'recipient2@test.com', 106 | )): 107 | self.backend.register( 108 | 'bob', 'bob@test.com', request=self.mock_request 109 | ) 110 | 111 | self.assertEqual(len(mail.outbox), 1) 112 | self.assertEqual(sorted(mail.outbox[0].to), sorted([ 113 | 'admin1@test.com', 114 | 'admin2@test.com', 115 | 'manager1@test.com', 116 | 'manager2@test.com', 117 | 'recipient1@test.com', 118 | 'recipient2@test.com', 119 | ])) 120 | 121 | def test_notify_duplicated(self): 122 | with override_settings( 123 | REGISTRATION_NOTIFICATION_ADMINS=True, 124 | REGISTRATION_NOTIFICATION_MANAGERS=True, 125 | REGISTRATION_NOTIFICATION_RECIPIENTS=( 126 | 'admin1@test.com', 127 | 'admin2@test.com', 128 | 'manager1@test.com', 129 | 'manager2@test.com', 130 | 'recipient1@test.com', 131 | 'recipient2@test.com', 132 | ), 133 | ADMINS=( 134 | ('admin1', 'admin1@test.com'), 135 | ('admin2', 'admin2@test.com'), 136 | ('manager1', 'manager1@test.com'), 137 | ('manager2', 'manager2@test.com'), 138 | ('recipient1', 'recipient1@test.com'), 139 | ('recipient2', 'recipient2@test.com'), 140 | ), 141 | MANAGERS=( 142 | ('admin1', 'admin1@test.com'), 143 | ('admin2', 'admin2@test.com'), 144 | ('manager1', 'manager1@test.com'), 145 | ('manager2', 'manager2@test.com'), 146 | ('recipient1', 'recipient1@test.com'), 147 | ('recipient2', 'recipient2@test.com'), 148 | )): 149 | self.backend.register( 150 | 'bob', 'bob@test.com', request=self.mock_request 151 | ) 152 | 153 | self.assertEqual(len(mail.outbox), 1) 154 | self.assertEqual(sorted(mail.outbox[0].to), sorted([ 155 | 'admin1@test.com', 156 | 'admin2@test.com', 157 | 'manager1@test.com', 158 | 'manager2@test.com', 159 | 'recipient1@test.com', 160 | 'recipient2@test.com', 161 | ])) 162 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-inspectional-registration 2 | =============================================================================== 3 | .. image:: https://secure.travis-ci.org/lambdalisue/django-inspectional-registration.png?branch=master 4 | :target: http://travis-ci.org/lambdalisue/django-inspectional-registration 5 | :alt: Build status 6 | 7 | .. image:: https://coveralls.io/repos/lambdalisue/django-inspectional-registration/badge.png?branch=master 8 | :target: https://coveralls.io/r/lambdalisue/django-inspectional-registration/ 9 | :alt: Coverage 10 | 11 | .. image:: https://requires.io/github/lambdalisue/django-inspectional-registration/requirements.svg?branch=master 12 | :target: https://requires.io/github/lambdalisue/django-inspectional-registration/requirements/?branch=master 13 | :alt: Requirements Status 14 | 15 | .. image:: https://landscape.io/github/lambdalisue/django-inspectional-registration/master/landscape.svg?style=flat 16 | :target: https://landscape.io/github/lambdalisue/django-inspectional-registration/master 17 | :alt: Code Health 18 | 19 | .. image:: https://scrutinizer-ci.com/g/lambdalisue/django-inspectional-registration/badges/quality-score.png?b=master 20 | :target: https://scrutinizer-ci.com/g/lambdalisue/django-inspectional-registration/inspections 21 | :alt: Inspection 22 | 23 | 24 | Author 25 | Alisue 26 | Supported python versions 27 | 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 28 | Supported django versions 29 | 1.5 - 1.10 30 | 31 | django-inspectional-registration is a enhanced application of 32 | django-registration_. The following features are available 33 | 34 | - Inspection steps for registration. You can accept or reject the account 35 | registration before sending activation key to the user. 36 | 37 | - Password will be filled in after the activation step to prevent that the 38 | user forget them previously filled password in registration step (No 39 | password filling in registration step) 40 | 41 | - Password can be generated programatically and force to activate the 42 | user. The generated password will be sent to the user by e-mail. 43 | 44 | - Any Django models are available to use as supplemental information of 45 | registration if the models are subclasses of 46 | ``registration.supplements.RegistrationSupplementBase``. 47 | It is commonly used for inspection. 48 | 49 | - You can send any additional messages to the user in each steps 50 | (acceptance, rejection and activation) 51 | 52 | - The behaviors of the application are customizable with Backend feature. 53 | 54 | - The E-mails or HTMLs are customizable with Django template system. 55 | 56 | - Can be migrated from django-registration_ simply by south_ 57 | 58 | - `django-mailer `_ compatible. 59 | Emails sent from the application will use django-mailer if 'mailer' is 60 | in your ``INSTALLED_APPS`` 61 | 62 | The difference with django-registration 63 | ------------------------------------------------------------------------------ 64 | 65 | While django-registration_ requires 3 steps for registration, 66 | django-inspectional-registration requires 5 steps and inspector for 67 | registration. 68 | 69 | .. _django-registration: https://bitbucket.org/ubernostrum/django-registration/ 70 | .. _south: http://south.aeracode.org/ 71 | 72 | Online documentation 73 | ------------------------------------------------------------------------------- 74 | See `django-inspectional-registration official documents `_ for more detail 75 | 76 | 77 | For translators 78 | --------------------------------------------------------------------------------- 79 | To create a message file, execute the following command (with your language) 80 | 81 | .. code:: sh 82 | 83 | $ python manage.py makemessages -l ja 84 | 85 | 86 | You can compile the latest message files with the following command 87 | 88 | .. code:: sh 89 | 90 | $ python setup.py compile_messages 91 | 92 | The command above is automatically called before ``sdist`` command if you call 93 | ``python manage.py sdist``. 94 | 95 | Email 96 | --------------------------------------------------------------------------------- 97 | REGISTRATION_FROM_EMAIL is used as the *FROM* email address emails send by 98 | django-inspectional-registration. If REGISTRATION_FROM_EMAIL is not set the Django 99 | setting _DEFAULT_FROM_EMAIL: 100 | will be used instead. 101 | 102 | To set REGISTRATION_FROM_EMAIL add REGISTRATION_FROM_EMAIL to your settings file. 103 | 104 | Example: 105 | 106 | ``REGISTRATION_FROM_EMAIL = 'help@example.com'`` 107 | 108 | Backward incompatibility 109 | --------------------------------------------------------------------------------- 110 | Because of an `issue#24 `_, django-inspectional-registration add the following three new options. 111 | 112 | - ``REGISTRATION_DJANGO_AUTH_URLS_ENABLE`` 113 | If it is ``False``, django-inspectional-registration do not define the views of django.contrib.auth. 114 | It is required to define these view manually. (Default: ``True``) 115 | - ``REGISTRATION_DJANGO_AUTH_URL_NAMES_PREFIX`` 116 | It is used as a prefix string of view names of django.contrib.auth. 117 | For backward compatibility, set this value to ``'auth_'``. (Default: ``''``) 118 | - ``REGISTRATION_DJANGO_AUTH_URL_NAMES_SUFFIX`` 119 | It is used as a suffix string of view names of django.contrib.auth. 120 | For backward compatibility, set this value to ``''``. (Default: ``''``) 121 | 122 | This changes were introduced from version 0.4.0, to keep the backward compatibility, write the following in your settings module. 123 | 124 | .. code:: python 125 | 126 | REGISTRATION_DJANGO_AUTH_URLS_ENABLE = True 127 | REGISTRATION_DJANGO_AUTH_URL_NAMES_PREFIX = 'auth_' 128 | REGISTRATION_DJANGO_AUTH_URL_NAMES_SUFFIX = '' 129 | 130 | Because of an `issue#36 `_, django-inspectional-registration add the following new option. 131 | 132 | - ``REGISTRATION_USE_OBJECT_PERMISSION`` 133 | If it is ``True``, django-inspectional-registration pass ``obj`` to ``request.user.has_perm`` in ``RegistrationAdmin.has_*_permission()`` methods. A default permission backend of Django does not support object permission thus it should be ``False`` if you don't use extra permission backends such as `django-permission `_. 134 | 135 | This change was introduced from version 0.4.7. To keep backward compatibility, write the following in your settings module. 136 | 137 | .. code:: python 138 | 139 | REGISTRATION_USE_OBJECT_PERMISSION = True 140 | -------------------------------------------------------------------------------- /src/registration/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | """ 5 | __author__ = 'Alisue ' 6 | from django.test import TestCase 7 | from registration.compat import get_user_model 8 | from registration import forms 9 | 10 | 11 | class ActivationFormTests(TestCase): 12 | """ 13 | Test the default registration forms. 14 | 15 | """ 16 | def test_activation_form(self): 17 | """ 18 | Test that ``ActivationForm`` enforces username constraints 19 | and matching passwords. 20 | 21 | """ 22 | User = get_user_model() 23 | # Create a user so we can verify that duplicate usernames aren't 24 | # permitted. 25 | User.objects.create_user('alice', 'alice@example.com', 'secret') 26 | 27 | invalid_data_dicts = [ 28 | # Mismatched passwords. 29 | {'data': {'password1': 'foo', 30 | 'password2': 'bar'}, 31 | 'error': ('__all__', ["The two password fields didn't match."])}, 32 | ] 33 | 34 | for invalid_dict in invalid_data_dicts: 35 | form = forms.ActivationForm(data=invalid_dict['data']) 36 | self.failIf(form.is_valid()) 37 | self.assertEqual(form.errors[invalid_dict['error'][0]], 38 | invalid_dict['error'][1]) 39 | 40 | form = forms.ActivationForm(data={'password1': 'foo', 41 | 'password2': 'foo'}) 42 | self.failUnless(form.is_valid()) 43 | 44 | 45 | class RegistrationFormTests(TestCase): 46 | """ 47 | Test the default registration forms. 48 | 49 | """ 50 | def test_registration_form(self): 51 | """ 52 | Test that ``RegistrationForm`` enforces username constraints 53 | and matching passwords. 54 | 55 | """ 56 | User = get_user_model() 57 | # Create a user so we can verify that duplicate usernames aren't 58 | # permitted. 59 | User.objects.create_user('alice', 'alice@example.com', 'secret') 60 | 61 | invalid_data_dicts = [ 62 | # Non-alphanumeric username. 63 | {'data': {'username': 'foo/bar', 64 | 'email1': 'foo@example.com', 65 | 'email2': 'foo@example.com'}, 66 | 'error': ('username', ["This value must contain only letters, numbers and underscores."])}, 67 | # Already-existing username. 68 | {'data': {'username': 'alice', 69 | 'email1': 'alice@example.com', 70 | 'email2': 'alice@example.com'}, 71 | 'error': ('username', ["A user with that username already exists."])}, 72 | # Mismatched email. 73 | {'data': {'username': 'foo', 74 | 'email1': 'foo@example.com', 75 | 'email2': 'bar@example.com'}, 76 | 'error': ('__all__', ["The two email fields didn't match."])}, 77 | ] 78 | 79 | for invalid_dict in invalid_data_dicts: 80 | form = forms.RegistrationForm(data=invalid_dict['data']) 81 | self.failIf(form.is_valid()) 82 | self.assertEqual(form.errors[invalid_dict['error'][0]], 83 | invalid_dict['error'][1]) 84 | 85 | form = forms.RegistrationForm(data={'username': 'foofoohogehoge', 86 | 'email1': 'foo@example.com', 87 | 'email2': 'foo@example.com'}) 88 | self.failUnless(form.is_valid()) 89 | 90 | def test_registration_form_tos(self): 91 | """ 92 | Test that ``RegistrationFormTermsOfService`` requires 93 | agreement to the terms of service. 94 | 95 | """ 96 | form = forms.RegistrationFormTermsOfService(data={'username': 'foo', 97 | 'email1': 'foo@example.com', 98 | 'email2': 'foo@example.com'}) 99 | self.failIf(form.is_valid()) 100 | self.assertEqual(form.errors['tos'], 101 | ["You must agree to the terms to register"]) 102 | 103 | form = forms.RegistrationFormTermsOfService(data={'username': 'foofoohogehoge', 104 | 'email1': 'foo@example.com', 105 | 'email2': 'foo@example.com', 106 | 'tos': 'on'}) 107 | self.failUnless(form.is_valid()) 108 | 109 | def test_registration_form_unique_email(self): 110 | """ 111 | Test that ``RegistrationFormUniqueEmail`` validates uniqueness 112 | of email addresses. 113 | 114 | """ 115 | User = get_user_model() 116 | # Create a user so we can verify that duplicate addresses 117 | # aren't permitted. 118 | User.objects.create_user('alice', 'alice@example.com', 'secret') 119 | 120 | form = forms.RegistrationFormUniqueEmail(data={'username': 'foo', 121 | 'email1': 'alice@example.com', 122 | 'email2': 'alice@example.com'}) 123 | self.failIf(form.is_valid()) 124 | self.assertEqual(form.errors['email1'], 125 | ["This email address is already in use. Please supply a different email address."]) 126 | 127 | form = forms.RegistrationFormUniqueEmail(data={'username': 'foofoohogehoge', 128 | 'email1': 'foo@example.com', 129 | 'email2': 'foo@example.com'}) 130 | self.failUnless(form.is_valid()) 131 | 132 | def test_registration_form_no_free_email(self): 133 | """ 134 | Test that ``RegistrationFormNoFreeEmail`` disallows 135 | registration with free email addresses. 136 | 137 | """ 138 | base_data = {'username': 'foofoohogehoge'} 139 | for domain in forms.RegistrationFormNoFreeEmail.bad_domains: 140 | invalid_data = base_data.copy() 141 | invalid_data['email1'] = "foo@%s" % domain 142 | invalid_data['email2'] = invalid_data['email1'] 143 | form = forms.RegistrationFormNoFreeEmail(data=invalid_data) 144 | self.failIf(form.is_valid()) 145 | self.assertEqual(form.errors['email1'], 146 | ["Registration using free email addresses is prohibited. Please supply a different email address."]) 147 | 148 | base_data['email1'] = 'foo@example.com' 149 | base_data['email2'] = base_data['email1'] 150 | form = forms.RegistrationFormNoFreeEmail(data=base_data) 151 | self.failUnless(form.is_valid()) 152 | -------------------------------------------------------------------------------- /src/registration/admin/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | A forms used in RegistrationAdmin 5 | """ 6 | __author__ = 'Alisue ' 7 | __all__ = ( 8 | 'RegistrationAdminForm', 9 | ) 10 | from django import forms 11 | from django.core.exceptions import ValidationError 12 | from django.utils.translation import ugettext_lazy as _ 13 | 14 | from registration.conf import settings 15 | from registration.backends import get_backend 16 | from registration.models import RegistrationProfile 17 | 18 | 19 | class RegistrationAdminForm(forms.ModelForm): 20 | 21 | """A special form for handling ``RegistrationProfile`` 22 | 23 | This form handle ``RegistrationProfile`` correctly in ``save()`` 24 | method. Because ``RegistrationProfile`` is not assumed to handle 25 | by hands, instance modification by hands is not allowed. Thus subclasses 26 | should feel free to add any additions they need, but should avoid 27 | overriding a ``save()`` method. 28 | 29 | """ 30 | registration_backend = get_backend() 31 | 32 | UNTREATED_ACTIONS = ( 33 | ('accept', _('Accept this registration')), 34 | ('reject', _('Reject this registration')), 35 | ('force_activate', _( 36 | 'Activate the associated user of this registration forcibly')), 37 | ) 38 | ACCEPTED_ACTIONS = ( 39 | ('accept', _('Re-accept this registration')), 40 | ('activate', 41 | _('Activate the associated user of this registration')), 42 | ) 43 | REJECTED_ACTIONS = ( 44 | ('accept', _('Accept this registration')), 45 | ('force_activate', _( 46 | 'Activate the associated user of this registration forcibly')), 47 | ) 48 | 49 | action_name = forms.ChoiceField(label=_('Action')) 50 | message = forms.CharField(label=_('Message'), 51 | widget=forms.Textarea, required=False, 52 | help_text=_( 53 | 'You can use the value of this field in templates for acceptance, ' 54 | 'rejection, and activation email with "{{ message }}". ' 55 | 'It is displayed in rejection email as "Rejection reasons" in ' 56 | 'default templates.' 57 | )) 58 | 59 | class Meta: 60 | model = RegistrationProfile 61 | exclude = ('user', '_status') 62 | 63 | def __init__(self, *args, **kwargs): 64 | super(RegistrationAdminForm, self).__init__(*args, **kwargs) 65 | # dynamically set choices of _status field 66 | if self.instance._status == 'untreated': 67 | self.fields['action_name'].choices = self.UNTREATED_ACTIONS 68 | elif self.instance._status == 'accepted': 69 | self.fields['action_name'].choices = self.ACCEPTED_ACTIONS 70 | elif self.instance._status == 'rejected': 71 | self.fields['action_name'].choices = self.REJECTED_ACTIONS 72 | 73 | def clean_action(self): 74 | """clean action value 75 | 76 | Insted of raising AttributeError, validate the current registration 77 | profile status and the requested action and then raise ValidationError 78 | 79 | """ 80 | action_name = self.cleaned_data['action_name'] 81 | if action_name == 'reject': 82 | if self.instance._status == 'accepted': 83 | raise ValidationError(_( 84 | "You cannot reject a previously accepted registration.")) 85 | elif action_name == 'activate': 86 | if self.instance._status != 'accepted': 87 | raise ValidationError(_( 88 | "You cannot activate a user whose registration has not " 89 | "been accepted yet.")) 90 | elif action_name != 'force_activate': 91 | # with using django admin page, the code below never be called. 92 | raise ValidationError( 93 | "Unknown action_name '%s' was requested." % action_name) 94 | return self.cleaned_data['action_name'] 95 | 96 | def save(self, commit=True): 97 | """Call appropriate action via current registration backend 98 | 99 | Insted of modifing the registration profile, this method call current 100 | registration backend's accept/reject/activate method as requested. 101 | 102 | """ 103 | fail_message = 'update' if self.instance.pk else 'create' 104 | opts = self.instance._meta 105 | if self.errors: 106 | raise ValueError("The %s chould not be %s because the data did'nt" 107 | "validate." % (opts.object_name, fail_message)) 108 | action_name = self.cleaned_data['action_name'] 109 | message = self.cleaned_data['message'] 110 | # this is a bit hack. to get request instance in form instance, 111 | # RegistrationAdmin save its request to bundle model instance 112 | _request = getattr( 113 | self.instance, 114 | settings._REGISTRATION_ADMIN_REQ_ATTR_NAME_IN_MODEL_INS 115 | ) 116 | if action_name == 'accept': 117 | self.registration_backend.accept( 118 | self.instance, _request, message=message, 119 | force=True, 120 | ) 121 | elif action_name == 'reject': 122 | self.registration_backend.reject( 123 | self.instance, _request, message=message) 124 | elif action_name == 'activate': 125 | # DO NOT delete profile otherwise Django Admin will raise 126 | # IndexError 127 | self.registration_backend.activate( 128 | self.instance.activation_key, _request, message=message, 129 | no_profile_delete=True, 130 | ) 131 | elif action_name == 'force_activate': 132 | self.registration_backend.accept( 133 | self.instance, _request, send_email=False) 134 | # DO NOT delete profile otherwise Django Admin will raise 135 | # IndexError 136 | self.registration_backend.activate( 137 | self.instance.activation_key, _request, message=message, 138 | no_profile_delete=True, 139 | ) 140 | else: 141 | raise AttributeError( 142 | 'Unknwon action_name "%s" was requested.' % action_name) 143 | if action_name not in ('activate', 'force_activate'): 144 | new_instance = self.instance.__class__.objects.get( 145 | pk=self.instance.pk) 146 | else: 147 | new_instance = self.instance 148 | # the instance has been deleted by activate method however 149 | # ``save()`` method will be called, thus set mock save method 150 | new_instance.save = lambda *args, **kwargs: new_instance 151 | return new_instance 152 | 153 | # this form doesn't have ``save_m2m()`` method and it is required 154 | # in default ModelAdmin class to use. thus set mock save_m2m method 155 | save_m2m = lambda x: x 156 | -------------------------------------------------------------------------------- /docs/about_registration_templates.rst: -------------------------------------------------------------------------------- 1 | ******************************************************** 2 | About Registration Templates 3 | ******************************************************** 4 | 5 | django-inspectional-registration use the following templates 6 | 7 | Email templates 8 | ============================== 9 | Used to create the email 10 | 11 | acceptance Email 12 | ------------------------------ 13 | Sent when inspector accpet the account registration 14 | 15 | ``registration/acceptance_email.txt`` 16 | Used to create acceptance email. The following context will be passed 17 | 18 | ``site`` 19 | An instance of ``django.contrib.site.Site`` to determine the site name 20 | and domain name 21 | 22 | ``user`` 23 | A user instance 24 | 25 | ``profile`` 26 | An instance of :py:class:`registration.models.RegistrationProfile` 27 | 28 | ``activation_key`` 29 | An activation key used to generate activation url. To generate 30 | activation url, use the following template command:: 31 | 32 | 33 | http://{{ site.domain }}{% url 'registration_activate' activation_key=activation_key %} 34 | 35 | ``expiration_days`` 36 | A number of days remaining during which the account may be activated. 37 | 38 | ``message`` 39 | A message from inspector. Not used in default template. 40 | 41 | ``registration/acceptance_email_subject.txt`` 42 | Used to create acceptance email subject. The following context will be passed 43 | 44 | ``site`` 45 | An instance of ``django.contrib.site.Site`` to determine the site name 46 | and domain name 47 | 48 | ``user`` 49 | A user instance 50 | 51 | ``profile`` 52 | An instance of :py:class:`registration.models.RegistrationProfile` 53 | 54 | ``activation_key`` 55 | An activation key used to generate activation url. To generate 56 | activation url, use the following template command:: 57 | 58 | 59 | http://{{ site.domain }}{% url 'registration_activate' activation_key=activation_key %} 60 | 61 | ``expiration_days`` 62 | A number of days remaining during which the account may be activated. 63 | 64 | ``message`` 65 | A message from inspector. Not used in default template. 66 | 67 | .. Note:: 68 | All newline will be removed in this template because it is a subject. 69 | 70 | Activation Email 71 | -------------------------------- 72 | Sent when the activation has complete. 73 | 74 | ``registration/activation_email.txt`` 75 | Used to create activation email. The following context will be passed 76 | 77 | ``site`` 78 | An instance of ``django.contrib.site.Site`` to determine the site name 79 | and domain name 80 | 81 | ``user`` 82 | A user instance 83 | 84 | ``password`` 85 | A password of the account. Use this for telling the password when the 86 | password is generated automatically. 87 | 88 | ``is_generated`` 89 | If ``True``, the password was generated programatically thus you have 90 | to tell the password to the user. 91 | 92 | ``message`` 93 | A message from inspector. Not used in default template. 94 | 95 | ``registration/activation_email_subject.txt`` 96 | Used to create activation email subject. The following context will be passed 97 | 98 | ``site`` 99 | An instance of ``django.contrib.site.Site`` to determine the site name 100 | and domain name 101 | 102 | ``user`` 103 | A user instance 104 | 105 | ``password`` 106 | A password of the account. Use this for telling the password when the 107 | password is generated automatically. 108 | 109 | ``is_generated`` 110 | If ``True``, the password was generated programatically thus you have 111 | to tell the password to the user. 112 | 113 | ``message`` 114 | A message from inspector. Not used in default template. 115 | 116 | .. Note:: 117 | All newline will be removed in this template because it is a subject. 118 | 119 | Registration Email 120 | ------------------------------------ 121 | Sent when the registration has complete. 122 | 123 | ``registration/registration_email.txt`` 124 | Used to create registration email. The following context will be passed 125 | 126 | ``site`` 127 | An instance of ``django.contrib.site.Site`` to determine the site name 128 | and domain name 129 | 130 | ``user`` 131 | A user instance 132 | 133 | ``profile`` 134 | An instance of :py:class:`registration.models.RegistrationProfile` 135 | 136 | ``registration/registration_email_subject.txt`` 137 | Used to create registration email subject. The following context will be passed 138 | 139 | ``site`` 140 | An instance of ``django.contrib.site.Site`` to determine the site name 141 | and domain name 142 | 143 | ``user`` 144 | A user instance 145 | 146 | ``profile`` 147 | An instance of :py:class:`registration.models.RegistrationProfile` 148 | 149 | .. Note:: 150 | All newline will be removed in this template because it is a subject. 151 | 152 | Rejection Email 153 | ------------------------------ 154 | Sent when inspector reject the account registration 155 | 156 | ``registration/rejection_email.txt`` 157 | Used to create rejection email. The following context will be passed 158 | 159 | ``site`` 160 | An instance of ``django.contrib.site.Site`` to determine the site name 161 | and domain name 162 | 163 | ``user`` 164 | A user instance 165 | 166 | ``profile`` 167 | An instance of :py:class:`registration.models.RegistrationProfile` 168 | 169 | ``message`` 170 | A message from inspector. Used for explain why the account 171 | registration was rejected in default template 172 | 173 | ``registration/rejection_email_subject.txt`` 174 | Used to create rejection email subject. The following context will be passed 175 | 176 | ``site`` 177 | An instance of ``django.contrib.site.Site`` to determine the site name 178 | and domain name 179 | 180 | ``user`` 181 | A user instance 182 | 183 | ``profile`` 184 | An instance of :py:class:`registration.models.RegistrationProfile` 185 | 186 | ``message`` 187 | A message from inspector. Used for explain why the account 188 | registration was rejected in default template 189 | 190 | .. Note:: 191 | All newline will be removed in this template because it is a subject. 192 | 193 | HTML Templates 194 | ============================ 195 | The following template will be used 196 | 197 | ``registration/activation_complete.html`` 198 | Used for activation complete page. 199 | 200 | ``registration/activation_form`` 201 | Used for activation page. ``form`` context will be passed 202 | to generate the activation form. 203 | 204 | ``registration/login.html`` 205 | Used for login page. ``form`` context will be passed 206 | to generate the login form. 207 | 208 | ``registration/logout.html`` 209 | Used for logged out page. 210 | 211 | ``registration/registration_closed.html`` 212 | Used for registration closed page. 213 | 214 | ``registration/registration_complete.html`` 215 | Used for registration complete page. ``registration_profile`` context will 216 | be passed. 217 | 218 | ``registration/registration_form.html`` 219 | Used for registration page. ``form`` context will be passed 220 | to generate registration form and ``supplement_form`` context 221 | will be passed to generate registration supplement form when 222 | the registration supplement exists. Use the following code 223 | in your template:: 224 | 225 |
{% csrf_token %} 226 | {{ form.as_p }} 227 | {{ supplement_form.as_p }} 228 |

229 |
230 | 231 | -------------------------------------------------------------------------------- /src/registration/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | """ 4 | Forms of django-inspectional-registration 5 | 6 | This is a modification of django-registration_ ``forms.py`` 7 | The original code is written by James Bennett 8 | 9 | .. _django-registration: https://bitbucket.org/ubernostrum/django-registration 10 | 11 | Original License:: 12 | 13 | Copyright (c) 2007-2011, James Bennett 14 | All rights reserved. 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are 18 | met: 19 | 20 | * Redistributions of source code must retain the above copyright 21 | notice, this list of conditions and the following disclaimer. 22 | * Redistributions in binary form must reproduce the above 23 | copyright notice, this list of conditions and the following 24 | disclaimer in the documentation and/or other materials provided 25 | with the distribution. 26 | * Neither the name of the author nor the names of other 27 | contributors may be used to endorse or promote products derived 28 | from this software without specific prior written permission. 29 | 30 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 31 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 32 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 33 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 34 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 36 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 37 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 38 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 39 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 40 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 41 | """ 42 | __author__ = 'Alisue ' 43 | __all__ = ( 44 | 'ActivationForm', 'RegistrationForm', 45 | 'RegistrationFormNoFreeEmail', 46 | 'RegistrationFormTermsOfService', 47 | 'RegistrationFormUniqueEmail', 48 | ) 49 | from django import forms 50 | from django.utils.translation import ugettext_lazy as _ 51 | from registration.compat import get_user_model 52 | 53 | attrs_dict = {'class': 'required'} 54 | 55 | class ActivationForm(forms.Form): 56 | """Form for activating a user account. 57 | 58 | Requires the password to be entered twice to catch typos. 59 | 60 | Subclasses should feel free to add any additional validation they need, but 61 | should avoid defining a ``save()`` method -- the actual saving of collected 62 | user data is delegated to the active registration backend. 63 | 64 | """ 65 | password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, 66 | render_value=False), 67 | label=_("Password")) 68 | password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, 69 | render_value=False), 70 | label=_("Password (again)")) 71 | 72 | def clean(self): 73 | """Check the passed two password are equal 74 | 75 | Verifiy that the values entered into the two password fields match. 76 | Note that an error here will end up in ``non_field_errors()`` because it 77 | doesn't apply to a single field. 78 | 79 | """ 80 | if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data: 81 | if self.cleaned_data['password1'] != self.cleaned_data['password2']: 82 | raise forms.ValidationError(_("The two password fields didn't match.")) 83 | return self.cleaned_data 84 | 85 | 86 | class RegistrationForm(forms.Form): 87 | """Form for registration a user account. 88 | 89 | Validates that the requested username is not already in use, and requires 90 | the email to be entered twice to catch typos. 91 | 92 | Subclasses should feel free to add any additional validation they need, but 93 | should avoid defining a ``save()`` method -- the actual saving of collected 94 | user data is delegated to the active registration backend. 95 | 96 | """ 97 | username = forms.RegexField(regex=r'^[\w.@+-]+$', 98 | max_length=30, 99 | widget=forms.TextInput(attrs=attrs_dict), 100 | label=_("Username"), 101 | error_messages={ 102 | 'invalid': _("This value must contain " 103 | "only letters, numbers and " 104 | "underscores.") 105 | }) 106 | email1 = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict, 107 | maxlength=75)), 108 | label=_("E-mail")) 109 | email2 = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict, 110 | maxlength=75)), 111 | label=_("E-mail (again)")) 112 | 113 | def clean_username(self): 114 | """ 115 | Validate that the username is alphanumeric and is not already in use. 116 | """ 117 | User = get_user_model() 118 | try: 119 | User.objects.get(username__iexact=self.cleaned_data['username']) 120 | except User.DoesNotExist: 121 | return self.cleaned_data['username'] 122 | raise forms.ValidationError(_( 123 | "A user with that username already exists.")) 124 | 125 | def clean(self): 126 | """Check the passed two email are equal 127 | 128 | Verifiy that the values entered into the two email fields match. 129 | Note that an error here will end up in ``non_field_errors()`` because 130 | it doesn't apply to a single field. 131 | 132 | """ 133 | if 'email1' in self.cleaned_data and 'email2' in self.cleaned_data: 134 | if self.cleaned_data['email1'] != self.cleaned_data['email2']: 135 | raise forms.ValidationError(_( 136 | "The two email fields didn't match.")) 137 | return self.cleaned_data 138 | 139 | 140 | class RegistrationFormTermsOfService(RegistrationForm): 141 | """ 142 | Subclass of ``RegistrationForm`` which adds a required checkbox for 143 | agreeing to a site's Terms of Service. 144 | 145 | """ 146 | tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict), 147 | label=_('I have read and agree to the Terms ' 148 | 'of Service'), 149 | error_messages={'required': _( 150 | "You must agree to the terms to register")}) 151 | 152 | 153 | class RegistrationFormUniqueEmail(RegistrationForm): 154 | """ 155 | Subclass of ``RegistrationForm`` which enforces uniqueness of email address 156 | """ 157 | 158 | def clean_email1(self): 159 | """Validate that the supplied email address is unique for the site.""" 160 | User = get_user_model() 161 | if User.objects.filter(email__iexact=self.cleaned_data['email1']): 162 | raise forms.ValidationError(_( 163 | "This email address is already in use. " 164 | "Please supply a different email address.")) 165 | return self.cleaned_data['email1'] 166 | 167 | 168 | class RegistrationFormNoFreeEmail(RegistrationForm): 169 | """ 170 | Subclass of ``RegistrationForm`` which disallows registration with email 171 | addresses from popular free webmail services; moderately useful for 172 | preventing automated spam registration. 173 | 174 | To change the list of banned domains, subclass this form and override the 175 | attribute ``bad_domains``. 176 | 177 | """ 178 | bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com', 179 | 'googlemail.com', 'hotmail.com', 'hushmail.com', 180 | 'msn.com', 'mail.ru', 'mailinator.com', 'live.com', 181 | 'yahoo.com'] 182 | 183 | def clean_email1(self): 184 | """ 185 | Check the supplied email address against a list of known free webmail 186 | domains. 187 | """ 188 | email_domain = self.cleaned_data['email1'].split('@')[1] 189 | if email_domain in self.bad_domains: 190 | raise forms.ValidationError(_( 191 | "Registration using free email addresses is prohibited. " 192 | "Please supply a different email address.")) 193 | return self.cleaned_data['email1'] 194 | --------------------------------------------------------------------------------