├── .coveragerc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .readthedocs.yaml ├── AUTHORS ├── CHANGELOG ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── admin-approval-backend.rst ├── conf.py ├── default-backend.rst ├── faq.rst ├── forms.rst ├── index.rst ├── make.bat ├── quickstart.rst ├── release-notes.rst ├── signals.rst ├── simple-backend.rst ├── upgrade.rst ├── urls.rst └── views.rst ├── manage.py ├── registration ├── __init__.py ├── admin.py ├── apps.py ├── auth_urls.py ├── backends │ ├── __init__.py │ ├── admin_approval │ │ ├── __init__.py │ │ ├── urls.py │ │ └── views.py │ ├── default │ │ ├── __init__.py │ │ ├── urls.py │ │ └── views.py │ └── simple │ │ ├── __init__.py │ │ ├── urls.py │ │ └── views.py ├── forms.py ├── locale │ ├── ar │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── bg │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ca │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── cs │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── da │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── el │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── en │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es_AR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fa │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── he │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── hr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── hu │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── id │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── is │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── it │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ja │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ko │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── nb │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── nl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pt │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ru │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── sl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── sr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── sv │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── th │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── tr_TR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── zh_CN │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── zh_TW │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── cleanupregistration.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_registrationprofile_activated.py │ ├── 0003_migrate_activatedstatus.py │ ├── 0004_supervisedregistrationprofile.py │ ├── 0005_activation_key_sha256.py │ └── __init__.py ├── models.py ├── signals.py ├── templates │ └── registration │ │ ├── activate.html │ │ ├── activation_complete.html │ │ ├── activation_complete_admin_pending.html │ │ ├── activation_email.html │ │ ├── activation_email.txt │ │ ├── activation_email_subject.txt │ │ ├── admin_approve.html │ │ ├── admin_approve_complete.html │ │ ├── admin_approve_complete_email.html │ │ ├── admin_approve_complete_email.txt │ │ ├── admin_approve_complete_email_subject.txt │ │ ├── admin_approve_email.html │ │ ├── admin_approve_email.txt │ │ ├── admin_approve_email_subject.txt │ │ ├── login.html │ │ ├── logout.html │ │ ├── password_change_done.html │ │ ├── password_change_form.html │ │ ├── password_reset_complete.html │ │ ├── password_reset_confirm.html │ │ ├── password_reset_done.html │ │ ├── password_reset_email.html │ │ ├── password_reset_form.html │ │ ├── registration_base.html │ │ ├── registration_closed.html │ │ ├── registration_complete.html │ │ ├── registration_form.html │ │ ├── resend_activation_complete.html │ │ └── resend_activation_form.html ├── tests │ ├── __init__.py │ ├── admin_actions.py │ ├── admin_approval_backend.py │ ├── default_backend.py │ ├── forms.py │ ├── forms_custom_user.py │ ├── models.py │ ├── simple_backend.py │ └── urls.py ├── users.py ├── utils.py └── views.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── tasks.py ├── test-requirements.txt ├── test_app ├── __init__.py ├── models.py ├── settings.py ├── settings_test.py ├── templates │ ├── base.html │ ├── index.html │ └── profile.html ├── urls_admin_approval.py ├── urls_default.py └── urls_simple.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = 4 | */migrations/* 5 | */south_migrations/* 6 | relative_files = True 7 | 8 | source = registration 9 | 10 | [report] 11 | exclude_lines = 12 | # Have to re-enable the standard pragma 13 | pragma: no cover 14 | 15 | # Don't complain if tests don't hit defensive assertion code: 16 | raise NotImplementedError 17 | show_missing = 1 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | max-parallel: 4 16 | matrix: 17 | python: ["3.8", "3.9", "3.10", "3.11", "3.12"] 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | - name: Set up Python ${{ matrix.python }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python }} 25 | - name: Install test dependencies 26 | run: | 27 | pip install -r test-requirements.txt 28 | - name: Test 29 | run: | 30 | make lint && tox 31 | - name: Coveralls GitHub Action 32 | uses: AndreMiras/coveralls-python-action@develop 33 | with: 34 | github-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | venv/ 12 | bin/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | include/ 26 | .eggs/ 27 | 28 | # Installer logs 29 | pip-log.txt 30 | pip-delete-this-directory.txt 31 | 32 | # Unit test / coverage reports 33 | htmlcov/ 34 | .tox/ 35 | .coverage 36 | .cache 37 | nosetests.xml 38 | coverage.xml 39 | 40 | # Translations 41 | *.mo 42 | 43 | # Mr Developer 44 | .mr.developer.cfg 45 | .project 46 | .pydevproject 47 | 48 | # Rope 49 | .ropeproject 50 | 51 | # Django stuff: 52 | *.log 53 | *.pot 54 | 55 | dr.sqlite3 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # Pycharm 61 | .idea 62 | 63 | # PIP 64 | pip-selfcheck.json 65 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | python: 21 | install: 22 | - requirements: requirements.txt 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The authors of django-registration (redux) can be found at 2 | 3 | 4 | ------------------------------------------------- 5 | 6 | The original author of django-registration was James Bennett 7 | , who may be found online at 8 | . 9 | 10 | 11 | Others who have contributed to the application: 12 | 13 | * Samuel Adam (French translation) 14 | * Jannis Leidel (German translation) 15 | * Rapahel Hertzog (helpful suggestions on packaging and distribution) 16 | * Panos Laganakos (Greek translation) 17 | * Ilya Filippov and Andy Mikhailenko (Russian translation) 18 | * Jarek Zgoda (Polish translation) 19 | * Meir Kriheli (Hebrew translation) 20 | * Italo Maia (Brazilian Portuguese translation) 21 | * Shinya Okano (Japanese translation) 22 | * A. Al-Ibrahim (Arabic translation) 23 | * Ernesto Rico Schmidt (Spanish translation) 24 | * Vladislav Mitov (Bulgarian translation) 25 | * Leonardo Manuel Rocha (Argentinean Spanish translation) 26 | * Emil Stenström (Swedish translation) 27 | * Liang Feng (Chinese translations) 28 | * Nebojsa Djordjevic (Serbian translation) 29 | * Nicola Larosa (Italian translation) 30 | * Joost Cassee (Dutch translation) 31 | * Björn Kristinsson (Icelandic translation) 32 | * Rune Bromer (Danish translation) 33 | * Domen Kožar (Slovenian translation) 34 | * Young Gyu Park (Korean translation) 35 | * Flavio Curella (updated Italian translation) 36 | * Jon Lønne (Bokmål translation) 37 | * Carles Barrobés (Catalan translation) 38 | * Nuno Mariz (Portuguese translation) 39 | * Patrick Samson (French translation) 40 | * Recep Kirmizi (Turkish translation) 41 | * Mohsen Mansouryar (Persian translation) 42 | * Jeffrey Long (Testing templates) 43 | * Ilya Baryshev (Test app / config) 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2013, James Bennett 2 | Copyright (c) 2014-2017, Andrew Cutler, Stephen DiCato, Joshua Blum, and others 3 | (see AUTHORS) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are 8 | met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | * Neither the name of the author nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG 2 | include INSTALL 3 | include LICENSE 4 | include MANIFEST.in 5 | include README.rst 6 | include AUTHORS 7 | recursive-include docs * 8 | prune docs/_build 9 | recursive-include registration/locale * 10 | recursive-include registration/templates * 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: installdeps\ 2 | build\ 3 | test\ 4 | lint\ 5 | clean \ 6 | release 7 | 8 | installdeps: 9 | pip install -U -r requirements.txt 10 | 11 | build: 12 | invoke build $(ARGS) 13 | 14 | test: 15 | invoke test 16 | 17 | lint: 18 | invoke lint 19 | 20 | clean: 21 | invoke clean $(ARGS) 22 | 23 | release: clean lint test 24 | ifeq ($(TAG_NAME),) 25 | $(error Usage: make release TAG_NAME=) 26 | endif 27 | # NOTE(joshblum): First you should update the changelog and bump the 28 | # version in setup.py 29 | git clean -dxf 30 | git tag $(TAG_NAME) 31 | git push --tags 32 | # Create the wheels for Python2 and Python3. 33 | python setup.py sdist bdist_wheel --universal 34 | # To check whether the README formats properly. 35 | twine check dist/* 36 | # Upload to pypi. 37 | twine upload dist/* 38 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. -*-restructuredtext-*- 2 | 3 | :Description: Django-registration provides user registration functionality for Django websites. 4 | :maintainers: Macropin_, DiCato_, and joshblum_ 5 | :contributors: `list of contributors `_ 6 | 7 | .. _Macropin: https://github.com/macropin 8 | .. _DiCato: https://github.com/dicato 9 | .. _joshblum: https://github.com/joshblum 10 | 11 | .. image:: https://github.com/macropin/django-registration/actions/workflows/ci.yml/badge.svg 12 | :target: https://github.com/macropin/django-registration/actions 13 | 14 | .. image:: https://coveralls.io/repos/macropin/django-registration/badge.svg?branch=main 15 | :target: https://coveralls.io/r/macropin/django-registration/ 16 | 17 | .. image:: https://badge.fury.io/py/django-registration-redux.svg 18 | :target: https://pypi.python.org/pypi/django-registration-redux/ 19 | 20 | .. image:: https://readthedocs.org/projects/django-registration-redux/badge/?version=latest 21 | :target: http://django-registration-redux.readthedocs.org/en/latest/?badge=latest 22 | :alt: Documentation Status 23 | 24 | .. image:: https://img.shields.io/pypi/pyversions/django-registration-redux.svg 25 | :target: https://pypi.python.org/pypi/django-registration-redux 26 | .. image:: https://img.shields.io/badge/_4.2_|_5.0-blue?color=0C4B33&label=django&logo=django&logoColor=white 27 | :target: https://github.com/django/django 28 | 29 | If you have issues with the "django-registration-redux" package then please `raise them here`_. 30 | 31 | This is a fairly simple user-registration application for Django, designed to 32 | make allowing user signups as painless as possible. It requires a functional 33 | installation of Django 3.1 or newer, but has no other dependencies. 34 | 35 | 36 | Installation 37 | ------------ 38 | 39 | Install, upgrade and uninstall django-registration-redux with these commands:: 40 | 41 | pip install django-registration-redux 42 | pip install --upgrade django-registration-redux 43 | pip uninstall django-registration-redux 44 | 45 | To install it manually, run the following command inside this source directory:: 46 | 47 | python setup.py install 48 | 49 | 50 | Or if you'd prefer you can simply place the included ``registration`` 51 | directory somewhere on your Python path, or symlink to it from 52 | somewhere on your Python path; this is useful if you're working from a 53 | Git checkout. 54 | 55 | Note that this application requires Python 3.5 or later, and a 56 | functional installation of Django 3.1 or newer. 57 | 58 | If you are running on Django <=2.0, you can install a previous version of 59 | `django-registration-redux`, which supports older versions of Django. See the 60 | `CHANGELOG`_ for support details. Older versions will receive minor bug fixes as 61 | needed, but are no longer actively developed:: 62 | 63 | pip install django-registration-redux==1.10 64 | 65 | 66 | Getting started with development 67 | -------------------------------- 68 | 69 | To get started with development, first install the required packages:: 70 | 71 | make installdeps 72 | 73 | For convenience a ``Makefile`` is included which wraps the Python `invoke 74 | `_ library. Once you work on a patch, you can test 75 | the functionality by running:: 76 | 77 | make test 78 | 79 | Or equivalently:: 80 | 81 | invoke test 82 | 83 | Command line arguments can be passed to the ``invoke`` script through the 84 | ``Makefile`` via the ``ARGS`` parameter. For example:: 85 | 86 | make build ARGS=--docs 87 | 88 | Or equivalently:: 89 | 90 | invoke build --docs 91 | 92 | Alternatives 93 | ------------ 94 | 95 | `djangopackages.com `_ 96 | has a comprehensive comparison of Django packages used for user registration 97 | and authentication. 98 | 99 | For example, `django-allauth `_ 100 | is an alternative to django-registration-redux that provides user registration 101 | in addition to social authentication and email address management. 102 | 103 | License 104 | ------- 105 | 106 | Django-registration-redux is licensed under `BSD License`. 107 | 108 | 109 | 110 | .. _`available online`: https://django-registration-redux.readthedocs.org/ 111 | .. _`raise them here`: https://github.com/macropin/django-registration/issues 112 | .. _`CHANGELOG`: https://github.com/macropin/django-registration/blob/main/CHANGELOG 113 | -------------------------------------------------------------------------------- /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 | 9 | # Internal variables. 10 | PAPEROPT_a4 = -D latex_paper_size=a4 11 | PAPEROPT_letter = -D latex_paper_size=letter 12 | ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 13 | 14 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 15 | 16 | help: 17 | @echo "Please use \`make ' where is one of" 18 | @echo " html to make standalone HTML files" 19 | @echo " dirhtml to make HTML files named index.html in directories" 20 | @echo " pickle to make pickle files" 21 | @echo " json to make JSON files" 22 | @echo " htmlhelp to make HTML files and a HTML help project" 23 | @echo " qthelp to make HTML files and a qthelp project" 24 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 25 | @echo " changes to make an overview of all changed/added/deprecated items" 26 | @echo " linkcheck to check all external links for integrity" 27 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 28 | 29 | clean: 30 | -rm -rf _build/* 31 | 32 | html: 33 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html 34 | @echo 35 | @echo "Build finished. The HTML pages are in _build/html." 36 | 37 | dirhtml: 38 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml 39 | @echo 40 | @echo "Build finished. The HTML pages are in _build/dirhtml." 41 | 42 | pickle: 43 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle 44 | @echo 45 | @echo "Build finished; now you can process the pickle files." 46 | 47 | json: 48 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json 49 | @echo 50 | @echo "Build finished; now you can process the JSON files." 51 | 52 | htmlhelp: 53 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp 54 | @echo 55 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 56 | ".hhp project file in _build/htmlhelp." 57 | 58 | qthelp: 59 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp 60 | @echo 61 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 62 | ".qhcp project file in _build/qthelp, like this:" 63 | @echo "# qcollectiongenerator _build/qthelp/django-registration.qhcp" 64 | @echo "To view the help file:" 65 | @echo "# assistant -collectionFile _build/qthelp/django-registration.qhc" 66 | 67 | latex: 68 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex 69 | @echo 70 | @echo "Build finished; the LaTeX files are in _build/latex." 71 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 72 | "run these through (pdf)latex." 73 | 74 | changes: 75 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes 76 | @echo 77 | @echo "The overview file is in _build/changes." 78 | 79 | linkcheck: 80 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck 81 | @echo 82 | @echo "Link check complete; look for any errors in the above output " \ 83 | "or in _build/linkcheck/output.txt." 84 | 85 | doctest: 86 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest 87 | @echo "Testing of doctests in the sources finished, look at the " \ 88 | "results in _build/doctest/output.txt." 89 | -------------------------------------------------------------------------------- /docs/admin-approval-backend.rst: -------------------------------------------------------------------------------- 1 | .. _admin-approval-backend: 2 | .. module:: registration.backends.admin_approval 3 | 4 | The admin approval backend 5 | ========================== 6 | 7 | As an alternative to :ref:`the default backend `, and 8 | an example of writing alternate workflows, |project| bundles 9 | an approval-needed registration system in 10 | ``registration.backend.admin_approval``. This backend's workflow is similar to 11 | the default with one extra step of approval from an admin. Specifically the 12 | steps are the following: 13 | 14 | 1. A user signs up by filling out a registration form. 15 | 16 | 2. The user confirms the account by following the link sent to the email 17 | address supplied during registration. 18 | 19 | 3. An admin receives an email with a link that will approve the user 20 | registration. 21 | 22 | 4. When the admin approves the request, the user receives an email informing 23 | them that they can now login. 24 | 25 | 26 | Configuration 27 | ------------- 28 | 29 | To make use of this backend, simply include the URLConf 30 | ``registration.backends.admin_approval.urls`` at whatever location you choose 31 | in your URL hierarchy. 32 | 33 | This backend makes use of the same settings documented in 34 | :ref:`the default backend ` plus the following settings: 35 | 36 | ``REGISTRATION_ADMINS`` 37 | A list with the same structure as the ``ADMINS`` Django setting containing 38 | names and emails. Approval emails will be sent to the emails defined here. 39 | If this setting is not set (or is empty), emails defined in ``ADMINS`` 40 | will be used. Optionally, this can be defined as a string with the path 41 | of a callable that returns a list of the same structure as the 42 | ``ADMINS`` setting. 43 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # django-registration documentation build configuration file, created by 2 | # sphinx-quickstart on Mon Jun 22 02:57:42 2009. 3 | # 4 | # This file is execfile()d with the current directory set to its containing dir. 5 | # 6 | # Note that not all possible configuration values are present in this 7 | # autogenerated file. 8 | # 9 | # All configuration values have a default; values that are commented out 10 | # serve to show the default. 11 | 12 | import os 13 | import sys 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | #sys.path.append(os.path.abspath('.')) 19 | 20 | # -- General configuration ----------------------------------------------------- 21 | 22 | # Add any Sphinx extension module names here, as strings. They can be extensions 23 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 24 | extensions = [] 25 | 26 | # Add any paths that contain templates here, relative to this directory. 27 | templates_path = ['_templates'] 28 | 29 | # The suffix of source filenames. 30 | source_suffix = '.rst' 31 | 32 | # The encoding of source files. 33 | #source_encoding = 'utf-8' 34 | 35 | # The master toctree document. 36 | master_doc = 'index' 37 | 38 | # General information about the project. 39 | project = u'django-registration-redux' 40 | copyright = u'2007-2013, James Bennett. 2014-2017, Andrew Cutler and others.' 41 | 42 | # The version info for the project you're documenting, acts as replacement for 43 | # |version| and |release|, also used in various other places throughout the 44 | # built documents. 45 | # 46 | # The short X.Y version. 47 | version = '2.13' 48 | # The full version, including alpha/beta/rc tags. 49 | release = '2.13' 50 | 51 | # The language for content autogenerated by Sphinx. Refer to documentation 52 | # for a list of supported languages. 53 | #language = None 54 | 55 | # There are two options for replacing |today|: either, you set today to some 56 | # non-false value, then it is used: 57 | #today = '' 58 | # Else, today_fmt is used as the format for a strftime call. 59 | #today_fmt = '%B %d, %Y' 60 | 61 | # List of documents that shouldn't be included in the build. 62 | #unused_docs = [] 63 | 64 | # List of directories, relative to source directory, that shouldn't be searched 65 | # for source files. 66 | exclude_trees = ['_build'] 67 | 68 | # The reST default role (used for this markup: `text`) to use for all documents. 69 | #default_role = None 70 | 71 | # If true, '()' will be appended to :func: etc. cross-reference text. 72 | #add_function_parentheses = True 73 | 74 | # If true, the current module name will be prepended to all description 75 | # unit titles (such as .. function::). 76 | #add_module_names = True 77 | 78 | # If true, sectionauthor and moduleauthor directives will be shown in the 79 | # output. They are ignored by default. 80 | #show_authors = False 81 | 82 | # The name of the Pygments (syntax highlighting) style to use. 83 | pygments_style = 'sphinx' 84 | 85 | # A list of ignored prefixes for module index sorting. 86 | #modindex_common_prefix = [] 87 | 88 | 89 | # -- Options for HTML output --------------------------------------------------- 90 | 91 | # The theme to use for HTML and HTML Help pages. Major themes that come with 92 | # Sphinx are currently 'default' and 'sphinxdoc'. 93 | html_theme = 'default' 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | #html_theme_options = {} 99 | 100 | # Add any paths that contain custom themes here, relative to this directory. 101 | #html_theme_path = [] 102 | 103 | # The name for this set of Sphinx documents. If None, it defaults to 104 | # " v documentation". 105 | #html_title = None 106 | 107 | # A shorter title for the navigation bar. Default is the same as html_title. 108 | #html_short_title = None 109 | 110 | # The name of an image file (relative to this directory) to place at the top 111 | # of the sidebar. 112 | #html_logo = None 113 | 114 | # The name of an image file (within the static path) to use as favicon of the 115 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 116 | # pixels large. 117 | #html_favicon = None 118 | 119 | # Add any paths that contain custom static files (such as style sheets) here, 120 | # relative to this directory. They are copied after the builtin static files, 121 | # so a file named "default.css" will overwrite the builtin "default.css". 122 | #html_static_path = ['_static'] 123 | 124 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 125 | # using the given strftime format. 126 | #html_last_updated_fmt = '%b %d, %Y' 127 | 128 | # If true, SmartyPants will be used to convert quotes and dashes to 129 | # typographically correct entities. 130 | #html_use_smartypants = True 131 | 132 | # Custom sidebar templates, maps document names to template names. 133 | #html_sidebars = {} 134 | 135 | # Additional templates that should be rendered to pages, maps page names to 136 | # template names. 137 | #html_additional_pages = {} 138 | 139 | # If false, no module index is generated. 140 | #html_use_modindex = True 141 | 142 | # If false, no index is generated. 143 | #html_use_index = True 144 | 145 | # If true, the index is split into individual pages for each letter. 146 | #html_split_index = False 147 | 148 | # If true, links to the reST sources are added to the pages. 149 | #html_show_sourcelink = True 150 | 151 | # If true, an OpenSearch description file will be output, and all pages will 152 | # contain a tag referring to it. The value of this option must be the 153 | # base URL from which the finished HTML is served. 154 | #html_use_opensearch = '' 155 | 156 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 157 | #html_file_suffix = '' 158 | 159 | # Output file base name for HTML help builder. 160 | htmlhelp_basename = 'django-registration-reduxdoc' 161 | 162 | 163 | # -- Options for LaTeX output -------------------------------------------------- 164 | 165 | # The paper size ('letter' or 'a4'). 166 | #latex_paper_size = 'letter' 167 | 168 | # The font size ('10pt', '11pt' or '12pt'). 169 | #latex_font_size = '10pt' 170 | 171 | # Grouping the document tree into LaTeX files. List of tuples 172 | # (source start file, target name, title, author, documentclass [howto/manual]). 173 | latex_documents = [ 174 | ('index', 'django-registration-redux.tex', u'django-registration-redux Documentation', 175 | u'James Bennett', 'manual'), 176 | ] 177 | 178 | # The name of an image file (relative to this directory) to place at the top of 179 | # the title page. 180 | #latex_logo = None 181 | 182 | # For "manual" documents, if this is true, then toplevel headings are parts, 183 | # not chapters. 184 | #latex_use_parts = False 185 | 186 | # Additional stuff for the LaTeX preamble. 187 | #latex_preamble = '' 188 | 189 | # Documents to append as an appendix to all manuals. 190 | #latex_appendices = [] 191 | 192 | # If false, no module index is generated. 193 | #latex_use_modindex = True 194 | 195 | 196 | rst_epilog = '.. |project| replace:: %s' % project 197 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | Frequently-asked questions 4 | ========================== 5 | 6 | The following are miscellaneous common questions and answers related 7 | to installing/using |project|, culled from bug reports, 8 | emails and other sources. 9 | 10 | 11 | General 12 | ------- 13 | 14 | **What license is django-registration-redux under?** 15 | |project| is offered under a three-clause BSD-style 16 | license; this is `an OSI-approved open-source license 17 | `_, and allows 18 | you a large degree of freedom in modifying and redistributing the 19 | code. For the full terms, see the file ``LICENSE`` which came with 20 | your copy of |project|; if you did not receive a copy of 21 | this file, you can `view it online 22 | `_. 23 | 24 | **Why are the forms and models for the default backend not in the default backend?** 25 | The model and manager used by :ref:`the default backend 26 | ` are in ``registration.models``, and the default 27 | form class (and various subclasses) are in ``registration.forms``; 28 | logically, they might be expected to exist in 29 | ``registration.backends.default``, but there are several reasons 30 | why that's not such a good idea: 31 | 32 | 1. Older versions of |project| made use of the model and 33 | form classes, and moving them would create an unnecessary 34 | backwards incompatibility: ``import`` statements would need to 35 | be changed, and some database updates would be needed to 36 | reflect the new location of the 37 | :class:`~registration.models.RegistrationProfile` model. 38 | 39 | 2. Due to the design of Django's ORM, the ``RegistrationProfile`` 40 | model would end up with an ``app_label`` of ``default``, which 41 | isn't particularly descriptive and may conflict with other 42 | applications. By keeping it in ``registration.models``, it 43 | retains an ``app_label`` of ``registration``, which more 44 | accurately reflects what it does and is less likely to cause 45 | problems. 46 | 47 | 3. Although the ``RegistrationProfile`` model and the various 48 | :ref:`form classes ` are used by the default backend, 49 | they can and are meant to be reused as needed by other 50 | backends. Any backend which uses an activation step should feel 51 | free to reuse the ``RegistrationProfile`` model, for example, 52 | and the registration form classes are in no way tied to a 53 | specific backend (and cover a number of common use cases which 54 | will crop up regardless of the specific backend logic in use). 55 | 56 | 57 | Installation and setup 58 | ---------------------- 59 | 60 | **How do I install django-registration-redux?** 61 | Full instructions are available in :ref:`the quick start guide `. 62 | 63 | **Do I need to put a copy of django-registration-redux in every project I use it in?** 64 | No; putting applications in your project directory is a very bad 65 | habit, and you should stop doing it. If you followed the 66 | instructions mentioned above, |project| was installed 67 | into a location that's on your Python import path, so you'll only 68 | ever need to add ``registration`` to your ``INSTALLED_APPS`` 69 | setting (in any project, or in any number of projects), and it 70 | will work. 71 | 72 | 73 | Configuration 74 | ------------- 75 | 76 | **Do I need to rewrite the views to change the way they behave?** 77 | 78 | Not always. Any behavior controlled by an attribute on a 79 | class-based view can be changed by passing a different value for 80 | that attribute in the URLConf. See `Django's class-based view 81 | documentation 82 | `_ 83 | for examples of this. 84 | 85 | For more complex or fine-grained control, you will likely want to 86 | subclass :class:`~registration.views.RegistrationView` or 87 | :class:`~registration.views.ActivationView`, or both, add your 88 | custom logic to your subclasses, and then create a URLConf which 89 | makes use of your subclasses. 90 | 91 | **I don't want to write my own URLconf because I don't want to write patterns for all the auth views!** 92 | You're in luck, then; |project| provides a URLconf which 93 | *only* contains the patterns for the auth views, and which you can 94 | include in your own URLconf anywhere you'd like; it lives at 95 | ``registration.auth_urls``. 96 | 97 | **I don't like the names you've given to the URL patterns!** 98 | In that case, you should feel free to set up your own URLconf 99 | which uses the names you want. 100 | 101 | 102 | Troubleshooting 103 | --------------- 104 | 105 | **I've got functions listening for the registration/activation signals, but they're not getting called!** 106 | 107 | The most common cause of this is placing |project| in a 108 | sub-directory that's on your Python import path, rather than 109 | installing it directly onto the import path as normal. Importing 110 | from |project| in that case can cause various issues, 111 | including incorrectly connecting signal handlers. For example, if 112 | you were to place |project| inside a directory named 113 | ``django_apps``, and refer to it in that manner, you would end up 114 | with a situation where your code does this:: 115 | 116 | from django_apps.registration.signals import user_registered 117 | 118 | But |project| will be doing:: 119 | 120 | from registration.signals import user_registered 121 | 122 | From Python's point of view, these import statements refer to two 123 | different objects in two different modules, and so signal handlers 124 | connected to the signal from the first import will not be called 125 | when the signal is sent using the second import. 126 | 127 | To avoid this problem, follow the standard practice of installing 128 | |project| directly on your import path and always 129 | referring to it by its own module name: ``registration`` (and in 130 | general, it is always a good idea to follow normal Python 131 | practices for installing and using Django applications). 132 | 133 | **I want to use custom templates, but django keeps using the admin templates instead of mine!** 134 | 135 | To fix this, make sure that in the ``INSTALLED_APPS`` of your 136 | ``settings.py`` the entry for the ``registration`` app is placed 137 | above ``django.contrib.admin``. 138 | 139 | Tips and tricks 140 | --------------- 141 | 142 | **How do I log a user in immediately after registration or activation?** 143 | Take a look at the implementation of :ref:`the simple backend 144 | `, which logs a user in immediately after 145 | registration. 146 | 147 | **How do I re-send an activation email?** 148 | Assuming you're using :ref:`the default backend 149 | `, a `custom admin action 150 | `_ 151 | is provided for this; in the admin for the 152 | :class:`~registration.models.RegistrationProfile` model, simply 153 | click the checkbox for the user(s) you'd like to re-send the email 154 | for, then select the "Re-send activation emails" action. 155 | 156 | **How do I manually activate a user?** 157 | In the default backend, a custom admin action is provided for 158 | this. In the admin for the ``RegistrationProfile`` model, click 159 | the checkbox for the user(s) you'd like to activate, then select 160 | the "Activate users" action. 161 | 162 | **How do I send an email with html content?** 163 | By default, the email content will only be plain text. To allow HTML 164 | content to be sent, you should add a url pattern before including 165 | ``django-registration``'s urls (i.e ``registration.backends.default.urls``). 166 | For example, if your email template is ``registration/password_reset_email.html``, 167 | your ``urls.py`` could look like:: 168 | 169 | from django.conf.urls import include 170 | from django.contrib.auth import views as auth_views 171 | from django.urls import path, reverse_lazy 172 | 173 | path( 174 | 'password/reset/', 175 | auth_views.PasswordResetView.as_view( 176 | success_url=reverse_lazy('auth_password_reset_done'), 177 | html_email_template_name='registration/password_reset_email.html' 178 | ), 179 | name='auth_password_reset', 180 | ), 181 | 182 | # other url patterns 183 | 184 | # last the default registration backends 185 | path('', include('registration.backends.default.urls')), 186 | 187 | We provide default HTML templates in the 188 | ``registration/templates/registration`` directory. 189 | -------------------------------------------------------------------------------- /docs/forms.rst: -------------------------------------------------------------------------------- 1 | .. _forms: 2 | .. module:: registration.forms 3 | 4 | Forms for user registration 5 | =========================== 6 | 7 | Several form classes are provided with |project|, covering 8 | common cases for gathering account information and implementing common 9 | constraints for user registration. These forms were designed with 10 | |project|'s :ref:`default backend ` in 11 | mind, but may also be useful in other situations. 12 | 13 | 14 | .. class:: RegistrationForm 15 | 16 | A simple form for registering an account. Has the following fields, 17 | all of which are required: 18 | 19 | ``username`` 20 | The username to use for the new account. This is represented as 21 | a text input which validates that the username is unique, 22 | consists entirely of alphanumeric characters and underscores 23 | and is at most 30 characters in length. 24 | 25 | ``email`` 26 | The email address to use for the new account. This is 27 | represented as a text input which accepts email addresses up to 28 | 75 characters in length. 29 | 30 | ``password1`` 31 | The password to use for the new account. This represented as a 32 | password input (``input type="password"`` in the rendered HTML). 33 | 34 | ``password2`` 35 | The password to use for the new account. This represented as a 36 | password input (``input type="password"`` in the rendered HTML). 37 | Password mismatches are recorded as errors of ``password2``. 38 | 39 | The constraints on usernames and email addresses match those 40 | enforced by Django's default authentication backend for instances 41 | of ``django.contrib.auth.models.User``. The repeated entry of the 42 | password serves to catch typos. 43 | 44 | 45 | .. class:: RegistrationFormTermsOfService 46 | 47 | A subclass of :class:`RegistrationForm` which adds one additional, 48 | required field: 49 | 50 | ``tos`` 51 | A checkbox indicating agreement to the site's terms of 52 | service/user agreement. 53 | 54 | 55 | .. class:: RegistrationFormUniqueEmail 56 | 57 | A subclass of :class:`RegistrationForm` which enforces uniqueness 58 | of email addresses in addition to uniqueness of usernames. 59 | 60 | 61 | .. class:: RegistrationFormNoFreeEmail 62 | 63 | A subclass of :class:`RegistrationForm` which disallows 64 | registration using addresses from some common free email 65 | providers. This can, in some cases, cut down on automated 66 | registration by spambots. 67 | 68 | By default, the following domains are disallowed for email 69 | addresses: 70 | 71 | * ``aim.com`` 72 | 73 | * ``aol.com`` 74 | 75 | * ``email.com`` 76 | 77 | * ``gmail.com`` 78 | 79 | * ``googlemail.com`` 80 | 81 | * ``hotmail.com`` 82 | 83 | * ``hushmail.com`` 84 | 85 | * ``msn.com`` 86 | 87 | * ``mail.ru`` 88 | 89 | * ``mailinator.com`` 90 | 91 | * ``live.com`` 92 | 93 | * ``yahoo.com`` 94 | 95 | * ``outlook.com`` 96 | 97 | To change this, subclass this form and set the class attribute 98 | ``bad_domains`` to a list of domains you wish to disallow. 99 | 100 | 101 | Multiple Form Inheritance 102 | ------------------------- 103 | 104 | Multiple :class:`RegistrationForm` subclasses can be inherited into one class. 105 | For instance, if your project requires a terms of service and a unique email 106 | upon registration, those subclasses can be inherited into a single class. That 107 | would look like this:: 108 | 109 | # myapp/forms.py 110 | class CustomForm(RegistrationFormTermsOfService, RegistrationFormUniqueEmail): 111 | pass 112 | 113 | NOTE: If inheriting both :class:`RegistrationFormNoFreeEmail` and 114 | :class:`RegistrationFormUniqueEmail`. :class:`RegistrationFormNoFreeEmail` 115 | must be inherited first, like this:: 116 | 117 | # myapp/forms.py 118 | class CustomForm(RegistrationFormNoFreeEmail, RegistrationFormUniqueEmail): 119 | pass 120 | 121 | You can also add any customization to the form, to add additional fields for 122 | example. Once you have built your form you must update the 123 | ``REGISTRATION_FORM`` reflect the string dotted path to the form you wish 124 | to use. For our example in ``settings.py`` you would change 125 | ``REGISTRATION_FORM = 'myapp.forms.CustomForm'``. 126 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-registration-redux documentation main file, created by 2 | sphinx-quickstart on Mon Jun 22 02:57:42 2009. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | |project| |version| documentation 7 | ================================= 8 | 9 | This documentation covers the |version| release of 10 | |project|, a simple but extensible application providing 11 | user registration functionality for `Django 12 | `_ powered websites. 13 | 14 | Although nearly all aspects of the registration process are customizable, 15 | out-of-the-box support is provided for the following use cases: 16 | 17 | * One-phase registration, where a user signs up and is immediately 18 | active and logged in. 19 | 20 | * Two-phase registration, consisting of initial signup followed by a 21 | confirmation email with instructions for activating the new account. 22 | 23 | * Three-phase registration, where a user signs up, confirms their account via 24 | email, and then an admin approves the account allowing the user to login. 25 | 26 | To get up and running quickly, consult the :ref:`quick-start guide 27 | `, which describes all the necessary steps to install 28 | |project| and configure it for the default workflow. For 29 | more detailed information, including how to customize the registration 30 | process (and support for alternate registration systems), read through 31 | the documentation listed below. 32 | 33 | If you are upgrading from a previous release, please read the `CHANGELOG 34 | `_ for 35 | information on what's changed. 36 | 37 | Contents: 38 | 39 | .. toctree:: 40 | :maxdepth: 1 41 | 42 | quickstart 43 | release-notes 44 | upgrade 45 | default-backend 46 | simple-backend 47 | admin-approval-backend 48 | forms 49 | views 50 | urls 51 | signals 52 | faq 53 | 54 | .. seealso:: 55 | 56 | * `Django's authentication documentation 57 | `_; Django's 58 | authentication system is used by |project|'s default 59 | configuration. 60 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . 7 | if NOT "%PAPER%" == "" ( 8 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 9 | ) 10 | 11 | if "%1" == "" goto help 12 | 13 | if "%1" == "help" ( 14 | :help 15 | echo.Please use `make ^` where ^ is one of 16 | echo. html to make standalone HTML files 17 | echo. dirhtml to make HTML files named index.html in directories 18 | echo. pickle to make pickle files 19 | echo. json to make JSON files 20 | echo. htmlhelp to make HTML files and a HTML help project 21 | echo. qthelp to make HTML files and a qthelp project 22 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 23 | echo. changes to make an overview over all changed/added/deprecated items 24 | echo. linkcheck to check all external links for integrity 25 | echo. doctest to run all doctests embedded in the documentation if enabled 26 | goto end 27 | ) 28 | 29 | if "%1" == "clean" ( 30 | for /d %%i in (_build\*) do rmdir /q /s %%i 31 | del /q /s _build\* 32 | goto end 33 | ) 34 | 35 | if "%1" == "html" ( 36 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html 37 | echo. 38 | echo.Build finished. The HTML pages are in _build/html. 39 | goto end 40 | ) 41 | 42 | if "%1" == "dirhtml" ( 43 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml 44 | echo. 45 | echo.Build finished. The HTML pages are in _build/dirhtml. 46 | goto end 47 | ) 48 | 49 | if "%1" == "pickle" ( 50 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle 51 | echo. 52 | echo.Build finished; now you can process the pickle files. 53 | goto end 54 | ) 55 | 56 | if "%1" == "json" ( 57 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json 58 | echo. 59 | echo.Build finished; now you can process the JSON files. 60 | goto end 61 | ) 62 | 63 | if "%1" == "htmlhelp" ( 64 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp 65 | echo. 66 | echo.Build finished; now you can run HTML Help Workshop with the ^ 67 | .hhp project file in _build/htmlhelp. 68 | goto end 69 | ) 70 | 71 | if "%1" == "qthelp" ( 72 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp 73 | echo. 74 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 75 | .qhcp project file in _build/qthelp, like this: 76 | echo.^> qcollectiongenerator _build\qthelp\django-registration.qhcp 77 | echo.To view the help file: 78 | echo.^> assistant -collectionFile _build\qthelp\django-registration.ghc 79 | goto end 80 | ) 81 | 82 | if "%1" == "latex" ( 83 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex 84 | echo. 85 | echo.Build finished; the LaTeX files are in _build/latex. 86 | goto end 87 | ) 88 | 89 | if "%1" == "changes" ( 90 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes 91 | echo. 92 | echo.The overview file is in _build/changes. 93 | goto end 94 | ) 95 | 96 | if "%1" == "linkcheck" ( 97 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck 98 | echo. 99 | echo.Link check complete; look for any errors in the above output ^ 100 | or in _build/linkcheck/output.txt. 101 | goto end 102 | ) 103 | 104 | if "%1" == "doctest" ( 105 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest 106 | echo. 107 | echo.Testing of doctests in the sources finished, look at the ^ 108 | results in _build/doctest/output.txt. 109 | goto end 110 | ) 111 | 112 | :end 113 | -------------------------------------------------------------------------------- /docs/release-notes.rst: -------------------------------------------------------------------------------- 1 | .. _release-notes: 2 | 3 | Release notes 4 | ============= 5 | 6 | The |version| release of |project| supports Python 3.8, 3.9, 3.10, 3.11, 3.12 7 | and Django 4.2 and 5.0 8 | -------------------------------------------------------------------------------- /docs/signals.rst: -------------------------------------------------------------------------------- 1 | .. _signals: 2 | .. module:: registration.signals 3 | 4 | 5 | Custom signals used by |project| 6 | ================================ 7 | 8 | Much of |project|'s customizability comes through the 9 | ability to write and use registration backends 10 | implementing different workflows for user registration. However, there 11 | are many cases where only a small bit of additional logic needs to be 12 | injected into the registration process, and writing a custom backend 13 | to support this represents an unnecessary amount of work. A more 14 | lightweight customization option is provided through two custom 15 | signals which backends are required to send at specific points during 16 | the registration process; functions listening for these signals can 17 | then add whatever logic is needed. 18 | 19 | For general documentation on signals and the Django dispatcher, 20 | consult `Django's signals documentation 21 | `_. This 22 | documentation assumes that you are familiar with how signals work and 23 | the process of writing and connecting functions which will listen for 24 | signals. 25 | 26 | 27 | .. data:: user_activated 28 | 29 | Sent when a user account is activated (not applicable to all 30 | backends). Provides the following arguments: 31 | 32 | ``sender`` 33 | The backend class used to activate the user. 34 | 35 | ``user`` 36 | An instance of ``django.contrib.auth.models.User`` 37 | representing the activated account. 38 | 39 | ``request`` 40 | The ``HttpRequest`` in which the account was activated. 41 | 42 | 43 | .. data:: user_registered 44 | 45 | Sent when a new user account is registered. Provides the following 46 | arguments: 47 | 48 | ``sender`` 49 | The backend class used to register the account. 50 | 51 | ``user`` 52 | An instance of ``django.contrib.auth.models.User`` 53 | representing the new account. 54 | 55 | ``request`` 56 | The ``HttpRequest`` in which the new account was registered. 57 | -------------------------------------------------------------------------------- /docs/simple-backend.rst: -------------------------------------------------------------------------------- 1 | .. _simple-backend: 2 | .. module:: registration.backends.simple 3 | 4 | The "simple" (one-step) backend 5 | =============================== 6 | 7 | As an alternative to :ref:`the default backend `, and 8 | an example of writing alternate workflows, |project| bundles 9 | a one-step registration system in 10 | ``registration.backend.simple``. This backend's workflow is 11 | deliberately as simple as possible: 12 | 13 | 1. A user signs up by filling out a registration form. 14 | 15 | 2. The user's account is created and is active immediately, with no 16 | intermediate confirmation or activation step. 17 | 18 | 3. The new user is logged in immediately. 19 | 20 | 21 | Configuration 22 | ------------- 23 | 24 | To use this backend, simply include the URLconf 25 | ``registration.backends.simple.urls`` somewhere in your site's own URL 26 | configuration. For example:: 27 | 28 | path('accounts/', include('registration.backends.simple.urls')), 29 | 30 | No additional settings are required, but two optional settings are 31 | supported: 32 | 33 | ``REGISTRATION_OPEN`` 34 | A boolean (either ``True`` or ``False``) indicating whether 35 | registration of new accounts is currently permitted. A default of 36 | ``True`` will be assumed if this setting is not supplied. 37 | 38 | ``SIMPLE_BACKEND_REDIRECT_URL`` 39 | Redirection url after successful registration. 40 | Default value is ``/`` 41 | 42 | The default form class used for account registration will be 43 | :class:`registration.forms.RegistrationForm`, although this can be 44 | overridden by supplying a custom URL pattern for the registration view 45 | and passing the keyword argument ``form_class``, or by subclassing 46 | ``registration.backends.simple.views.RegistrationView`` and either 47 | overriding ``form_class`` or implementing 48 | :meth:`~registration.views.RegistrationView.get_form_class()`. 49 | -------------------------------------------------------------------------------- /docs/upgrade.rst: -------------------------------------------------------------------------------- 1 | .. _upgrade: 2 | 3 | Upgrade guide 4 | ============= 5 | 6 | The |version| release of |project| is not compatible with the legacy 7 | django-registration (previously maintained by James Bennett). Major backwards 8 | incompatible changes will be recorded here, but for a full list of changes 9 | between versions you should refer to the `CHANGELOG 10 | `_. 11 | 12 | Django version requirement 13 | -------------------------- 14 | 15 | As of |version|, |project| requires Django 3.1 or newer; 16 | older Django releases may work, but are officially unsupported. Additionally, 17 | |project| officially supports Python 3.7, 3.8, 3.9, 3.10. 18 | 19 | 20 | Backwards-incompatible changes 21 | ------------------------------ 22 | 23 | Version 2.11 24 | ``````````` 25 | 26 | - None 27 | 28 | Version 2.10 29 | ``````````` 30 | 31 | - Removed support for Django < 3.1 and Python 3.5. 32 | 33 | Version 2.9 34 | ``````````` 35 | 36 | - Removed support for Django 1.11 and Python 2.7. 37 | 38 | Version 2.8 39 | ``````````` 40 | 41 | - None 42 | 43 | Version 2.7 44 | ``````````` 45 | 46 | - None 47 | 48 | Version 2.6 49 | ``````````` 50 | 51 | - None 52 | 53 | Version 2.5 54 | ``````````` 55 | 56 | - None 57 | 58 | Version 2.4 59 | ``````````` 60 | 61 | - None 62 | 63 | Version 2.3 64 | ``````````` 65 | 66 | - None 67 | 68 | 69 | Version 2.2 70 | ``````````` 71 | 72 | - None 73 | 74 | 75 | Version 2.1 76 | ``````````` 77 | 78 | - None 79 | 80 | 81 | Version 2.0 82 | ``````````` 83 | 84 | - Removed support for Django < 1.11. 85 | - Removed `registration/urls.py` in favor of 86 | `registration/backends/default/urls.py` 87 | 88 | 89 | Version 1.9 90 | ``````````` 91 | - Change of return signature of 92 | ``RegistrationProfileManager.activate_user``. A tuple containing the 93 | User instance and a boolean of whether or not said user was activated 94 | is now returned. 95 | 96 | 97 | Version 1.8 98 | ``````````` 99 | 100 | - None 101 | 102 | Version 1.7 103 | ``````````` 104 | 105 | - None 106 | 107 | Version 1.6 108 | ``````````` 109 | 110 | - None 111 | 112 | Version 1.5 113 | ``````````` 114 | 115 | - Support for Django 1.7 is removed, and Django 1.8 or newer is required. 116 | - Change signature of ``RegistrationProfileManager.activate_user``. 117 | ``site`` is now a required positional argument. 118 | See `#244 `_. 119 | 120 | Version 1.4 121 | ``````````` 122 | 123 | - Remove unnecessary `_RequestPassingFormView`. 124 | See `#56 `_. Please 125 | ensure that you update any subclassed views to reference ``self.request`` 126 | instead of accepting ``request`` as an argument. 127 | 128 | Version 1.3 129 | ``````````` 130 | - Django 1.7 or newer is required. Please ensure you upgrade your Django 131 | version before upgrading. 132 | 133 | Version 1.2 134 | ``````````` 135 | - **Native migration support breaks South compatibility**: An initial native 136 | migration for Django > 1.7 has been provided. South users will need to 137 | configure a null migration with (`SOUTH_MIGRATION_MODULES`) in 138 | `settings.py` as shown below: 139 | 140 | :: 141 | 142 | SOUTH_MIGRATION_MODULES = { 143 | 'registration': 'registration.south_migrations', 144 | 145 | - **register method in RegistrationView has different parameters**: The 146 | parameters of the`register` method in RegistrationView have changed. 147 | 148 | Version 1.1 149 | ``````````` 150 | 151 | - **base.html template required**: A `base.html` template is now assumed to 152 | exist. Please ensure that your project provides one for |project| to inherit 153 | from. 154 | - **HTML email templates**: |project| now uses HTML email templates. If you 155 | previously customized text email templates, you need to do the same with 156 | the new HTML templates. 157 | -------------------------------------------------------------------------------- /docs/urls.rst: -------------------------------------------------------------------------------- 1 | .. _urls: 2 | .. module:: registration.urls 3 | 4 | Registration urls 5 | ================== 6 | 7 | If you do not wish to configure all of your own URLs for the various registration views, there are several default definitions that you can include in your ``urls.py`` file. 8 | 9 | There is an URL file for :ref:`each of the default backends `. These files provide all the URLs that you will need for normal interaction. 10 | 11 | If you wish to include just the authentication URLs, either because you want to expose them under a different path, or because you want to manually configure the URLs for the other views, there is a separate :ref:`include for that `. 12 | 13 | .. _basic-urls: 14 | 15 | Basic URLs 16 | ~~~~~~~~~~ 17 | 18 | These URLs are provided by any of the following: 19 | * ``registration.backends.default.urls`` 20 | * ``registration.backends.admin_approval.urls`` 21 | * ``registration.backends.simple.urls`` 22 | 23 | **login/** 24 | * View: ``django.contrib.auth.views.LoginView`` 25 | 26 | **logout/** 27 | * View: ``django.contrib.auth.views.LogoutView`` 28 | 29 | **password/change/** 30 | * View: ``django.contrib.auth.views.PasswordChangeDoneView`` 31 | 32 | **password/change/done/** 33 | * View: ``django.contrib.auth.views.PasswordResetView`` 34 | 35 | **password/reset/** 36 | * View: ``django.contrib.auth.views.PasswordResetView`` 37 | 38 | **password/reset/complete/** 39 | * View: ``django.contrib.auth.views.PasswordResetCompleteView`` 40 | 41 | **password/reset/done/** 42 | * View: ``django.contrib.auth.views.PasswordResetDoneView`` 43 | 44 | **password/reset/confirm/{token}/** 45 | * View: ``django.contrib.auth.views.PasswordResetConfirmView`` 46 | 47 | **register/** 48 | * View: :py:class:`registration.views.RegistrationView` 49 | * Template: :ref:`registration_form.html` 50 | 51 | **register/closed/** 52 | * Template: :ref:`registration_closed.html` 53 | 54 | .. _authentication-urls: 55 | 56 | Authentication URLs 57 | ~~~~~~~~~~~~~~~~~~~~ 58 | 59 | Provided by ``registration.auth_urls``, or any of the above includes. 60 | 61 | **activate/complete/** 62 | * Template: :ref:`activation_complete.html` 63 | 64 | **activate/resend/** 65 | * View: :py:class:`registration.views.ResendActivationView` 66 | * Template: :ref:`resend_activation_complete.html` 67 | 68 | **activate/{key}/** 69 | * View: :py:class:`registration.views.ActivationView` 70 | * Template: :ref:`activate.html` 71 | 72 | **register/complete/** 73 | * Template: :ref:`registration_complete.html` 74 | 75 | Admin approval backend 76 | ~~~~~~~~~~~~~~~~~~~~~~ 77 | 78 | This URL is only provided by ``registration.backends.admin_approval.urls``. 79 | 80 | **approve/{profile}/** 81 | * View: :py:class:`registration.backends.admin_approval.views.ApprovalView` 82 | * Template: ``registration/admin_approve.html`` 83 | 84 | -------------------------------------------------------------------------------- /docs/views.rst: -------------------------------------------------------------------------------- 1 | .. _views: 2 | .. module:: registration.views 3 | 4 | Registration views 5 | ================== 6 | 7 | In order to allow the utmost flexibility in customizing and supporting 8 | different workflows, |project| makes use of Django's support 9 | for `class-based views 10 | `_. Included 11 | in |project| are two base classes which can be subclassed to 12 | implement whatever workflow is required. 13 | 14 | .. class:: RegistrationView 15 | 16 | A subclass of Django's `FormView 17 | `_, 18 | which provides the infrastructure for supporting user registration. 19 | 20 | Useful places to override or customize on a ``RegistrationView`` 21 | subclass are: 22 | 23 | .. attribute:: disallowed_url 24 | 25 | The URL to redirect to when registration is disallowed. Should 26 | be a string, `the name of a URL pattern 27 | `_. Default 28 | value is ``registration_disallowed``. 29 | 30 | .. attribute:: form_class 31 | 32 | The form class to use for user registration. Can be overridden 33 | on a per-request basis (see below). Should be the actual class 34 | object; by default, this class is 35 | :class:`registration.forms.RegistrationForm`. 36 | 37 | .. attribute:: success_url 38 | 39 | The URL to redirect to after successful registration. Should be 40 | a string, the name of a URL pattern, or a 3-tuple of arguments 41 | suitable for passing to Django's `redirect shortcut 42 | `_. Can 43 | be overridden on a per-request basis (see below). Default value 44 | is ``None``, so that per-request customization is used instead. 45 | 46 | .. attribute:: template_name 47 | 48 | The template to use for user registration. Should be a 49 | string. Default value is 50 | ``registration/registration_form.html``. 51 | 52 | .. method:: get_form_class() 53 | 54 | Select a form class to use on a per-request basis. If not 55 | overridden, will use :attr:`~form_class`. Should be the actual 56 | class object. 57 | 58 | .. method:: get_success_url(user) 59 | 60 | Return a URL to redirect to after successful registration, on a 61 | per-request or per-user basis. If not overridden, will use 62 | :attr:`~success_url`. Should be a string, the name of a URL 63 | pattern, or a 3-tuple of arguments suitable for passing to 64 | Django's ``redirect`` shortcut. 65 | 66 | .. method:: registration_allowed() 67 | 68 | Should return a boolean indicating whether user registration is 69 | allowed, either in general or for this specific request. 70 | 71 | .. method:: register(form) 72 | 73 | Actually perform the business of registering a new user. Receives the 74 | registration ``form``. Should return the new user who was just 75 | registered. 76 | 77 | 78 | .. class:: ActivationView 79 | 80 | A subclass of Django's `TemplateView 81 | `_ 82 | which provides support for a separate account-activation step, in 83 | workflows which require that. 84 | 85 | Useful places to override or customize on an ``ActivationView`` 86 | subclass are: 87 | 88 | .. attribute:: template_name 89 | 90 | The template to use for user activation. Should be a 91 | string. Default value is ``registration/activate.html``. 92 | 93 | .. method:: activate(*args, **kwargs) 94 | 95 | Actually perform the business of activating a user account. Receives any 96 | positional or keyword arguments passed to the view. Should return the 97 | activated user account if activation is successful, or any value 98 | which evaluates ``False`` in boolean context if activation is 99 | unsuccessful. 100 | 101 | .. method:: get_success_url(user) 102 | 103 | Return a URL to redirect to after successful registration, on a 104 | per-request or per-user basis. If not overridden, will use 105 | :attr:`~success_url`. Should be a string, the name of a URL 106 | pattern, or a 3-tuple of arguments suitable for passing to 107 | Django's ``redirect`` shortcut. 108 | 109 | .. class:: ResendActivationView 110 | 111 | A subclass of Django's `FormView 112 | `_ 113 | which provides support for resending an activation email to a user. 114 | 115 | Useful places to override or customize on an ``ResendActivationView`` 116 | subclass are: 117 | 118 | .. attribute:: template_name 119 | 120 | The template to use for user activation. Should be a 121 | string. Default value is ``registration/resend_activation_form.html``. 122 | 123 | .. method:: resend_activation(self, form) 124 | 125 | Given an email, look up user by email and resend activation key 126 | if user is not already activated or previous activation key has 127 | not expired. Note that if multiple users exist with the given 128 | email, no emails will be sent. Returns True if activation key 129 | was successfully sent, False otherwise. 130 | 131 | 132 | .. method:: render_form_submitted_template(self, form) 133 | 134 | Renders resend activation complete template with the submitted 135 | email. 136 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /registration/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (2, 13, 0, 'final', 0) 2 | 3 | # for Django-3.1 and prior 4 | default_app_config = 'registration.apps.RegistrationConfig' 5 | 6 | 7 | def get_version(): 8 | "Returns a PEP 386-compliant version number from VERSION." 9 | assert len(VERSION) == 5 10 | assert VERSION[3] in ('alpha', 'beta', 'rc', 'final') 11 | 12 | # Now build the two parts of the version number: 13 | # main = X.Y[.Z] 14 | # sub = .devN - for pre-alpha releases 15 | # | {a|b|c}N - for alpha, beta and rc releases 16 | 17 | parts = 2 if VERSION[2] == 0 else 3 18 | main = '.'.join(str(x) for x in VERSION[:parts]) 19 | 20 | sub = '' 21 | if VERSION[3] != 'final': 22 | mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'} 23 | sub = mapping[VERSION[3]] + str(VERSION[4]) 24 | 25 | return str(main + sub) 26 | -------------------------------------------------------------------------------- /registration/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.sites.shortcuts import get_current_site 3 | 4 | from .models import RegistrationProfile 5 | from .users import UsernameField 6 | from .utils import _ 7 | 8 | 9 | class RegistrationAdmin(admin.ModelAdmin): 10 | actions = ['activate_users', 'resend_activation_email'] 11 | list_display = ('user', 'activation_key_expired') 12 | raw_id_fields = ['user'] 13 | search_fields = ('user__{0}'.format(UsernameField()), 14 | 'user__first_name', 'user__last_name') 15 | 16 | def activate_users(self, request, queryset): 17 | """ 18 | Activates the selected users, if they are not already 19 | activated. 20 | 21 | """ 22 | 23 | site = get_current_site(request) 24 | for profile in queryset: 25 | RegistrationProfile.objects.activate_user(profile.activation_key, site) 26 | activate_users.short_description = _("Activate users") 27 | 28 | def resend_activation_email(self, request, queryset): 29 | """ 30 | Re-sends activation emails for the selected users. 31 | 32 | Note that this will *only* send activation emails for users 33 | who are eligible to activate; emails will not be sent to users 34 | whose activation keys have expired or who have already 35 | activated. 36 | 37 | """ 38 | 39 | site = get_current_site(request) 40 | for profile in queryset: 41 | user = profile.user 42 | RegistrationProfile.objects.resend_activation_mail(user.email, site, request) 43 | 44 | resend_activation_email.short_description = _("Re-send activation emails") 45 | 46 | 47 | admin.site.register(RegistrationProfile, RegistrationAdmin) 48 | -------------------------------------------------------------------------------- /registration/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RegistrationConfig(AppConfig): 5 | name = 'registration' 6 | verbose_name = "Registration" 7 | default_auto_field = 'django.db.models.AutoField' 8 | -------------------------------------------------------------------------------- /registration/auth_urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL patterns for the views included in ``django.contrib.auth``. 3 | 4 | Including these URLs (via the ``include()`` directive) will set up the 5 | following patterns based at whatever URL prefix they are included 6 | under: 7 | 8 | * User login at ``login/``. 9 | 10 | * User logout at ``logout/``. 11 | 12 | * The two-step password change at ``password/change/`` and 13 | ``password/change/done/``. 14 | 15 | * The four-step password reset at ``password/reset/``, 16 | ``password/reset/confirm/``, ``password/reset/complete/`` and 17 | ``password/reset/done/``. 18 | 19 | The default registration backend already has an ``include()`` for 20 | these URLs, so under the default setup it is not necessary to manually 21 | include these views. Other backends may or may not include them; 22 | consult a specific backend's documentation for details. 23 | 24 | """ 25 | from django.contrib.auth import views as auth_views 26 | from django.urls import path 27 | from django.urls import reverse_lazy 28 | 29 | urlpatterns = [ 30 | path('login/', 31 | auth_views.LoginView.as_view( 32 | template_name='registration/login.html'), 33 | name='auth_login'), 34 | path('logout/', 35 | auth_views.LogoutView.as_view( 36 | template_name='registration/logout.html'), 37 | name='auth_logout'), 38 | path('password/change/', 39 | auth_views.PasswordChangeView.as_view( 40 | success_url=reverse_lazy('auth_password_change_done')), 41 | name='auth_password_change'), 42 | path('password/change/done/', 43 | auth_views.PasswordChangeDoneView.as_view(), 44 | name='auth_password_change_done'), 45 | path('password/reset/', 46 | auth_views.PasswordResetView.as_view( 47 | success_url=reverse_lazy('auth_password_reset_done')), 48 | name='auth_password_reset'), 49 | path('password/reset/complete/', 50 | auth_views.PasswordResetCompleteView.as_view(), 51 | name='auth_password_reset_complete'), 52 | path('password/reset/done/', 53 | auth_views.PasswordResetDoneView.as_view(), 54 | name='auth_password_reset_done'), 55 | path('password/reset/confirm///', 56 | auth_views.PasswordResetConfirmView.as_view( 57 | success_url=reverse_lazy('auth_password_reset_complete')), 58 | name='auth_password_reset_confirm'), 59 | ] 60 | -------------------------------------------------------------------------------- /registration/backends/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/backends/__init__.py -------------------------------------------------------------------------------- /registration/backends/admin_approval/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/backends/admin_approval/__init__.py -------------------------------------------------------------------------------- /registration/backends/admin_approval/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URLconf for registration and activation, using django-registration's 3 | admin approval backend. 4 | 5 | If the default behavior of these views is acceptable to you, simply 6 | use a line like this in your root URLconf to set up the default URLs 7 | for registration:: 8 | 9 | (r'^accounts/', include('registration.backends.admin_approval.urls')), 10 | 11 | This will also automatically set up the views in 12 | ``django.contrib.auth`` at sensible default locations. 13 | 14 | If you'd like to customize registration behavior, feel free to set up 15 | your own URL patterns for these views instead. 16 | 17 | """ 18 | 19 | 20 | from django.conf import settings 21 | from django.conf.urls import include 22 | from django.contrib.auth.decorators import permission_required 23 | from django.urls import path 24 | from django.views.generic.base import TemplateView 25 | 26 | from .views import ActivationView 27 | from .views import ApprovalView 28 | from .views import RegistrationView 29 | 30 | from registration.backends.admin_approval.views import ResendActivationView 31 | 32 | urlpatterns = [ 33 | path('activate/resend/', 34 | ResendActivationView.as_view(), 35 | name='registration_resend_activation'), 36 | path('activate/complete/', 37 | TemplateView.as_view( 38 | template_name='registration/activation_complete_admin_pending.html' 39 | ), 40 | name='registration_activation_complete'), 41 | # Activation keys get matched by \w+ instead of the more specific 42 | # [a-fA-F0-9]{40} because a bad activation key should still get to the view; 43 | # that way it can return a sensible "invalid key" message instead of a 44 | # confusing 404. 45 | 46 | path('activate//', 47 | ActivationView.as_view(), 48 | name='registration_activate'), 49 | path('approve/complete/', 50 | TemplateView.as_view( 51 | template_name='registration/admin_approve_complete.html'), 52 | name='registration_approve_complete'), 53 | path('approve//', 54 | permission_required('is_superuser')(ApprovalView.as_view()), 55 | name='registration_admin_approve'), 56 | path('register/complete/', 57 | TemplateView.as_view( 58 | template_name='registration/registration_complete.html'), 59 | name='registration_complete'), 60 | path('register/closed/', 61 | TemplateView.as_view( 62 | template_name='registration/registration_closed.html'), 63 | name='registration_disallowed'), 64 | ] 65 | 66 | 67 | if getattr(settings, 'INCLUDE_REGISTER_URL', True): 68 | urlpatterns += [ 69 | path('register/', 70 | RegistrationView.as_view(), 71 | name='registration_register'), 72 | ] 73 | 74 | if getattr(settings, 'INCLUDE_AUTH_URLS', True): 75 | urlpatterns += [ 76 | path('', include('registration.auth_urls')), 77 | ] 78 | -------------------------------------------------------------------------------- /registration/backends/admin_approval/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sites.shortcuts import get_current_site 2 | 3 | from ... import signals 4 | from ...models import SupervisedRegistrationProfile 5 | from ...views import ApprovalView as BaseApprovalView 6 | from ..default.views import ActivationView as BaseActivationView 7 | from ..default.views import RegistrationView as BaseRegistrationView 8 | from ..default.views import ResendActivationView as BaseResendActivationView 9 | 10 | 11 | class RegistrationView(BaseRegistrationView): 12 | 13 | """ 14 | Follows the exact logic of 15 | ``registration.backends.default.views.RegistrationView`` but uses 16 | ``SupervisedRegistrationProfile`` instead of ``RegistrationProfile`` 17 | 18 | """ 19 | 20 | registration_profile = SupervisedRegistrationProfile 21 | 22 | 23 | class ActivationView(BaseActivationView): 24 | 25 | """ 26 | Follows the exact logic of 27 | ``registration.backends.default.views.ActivationView`` but uses 28 | ``SupervisedRegistrationProfile`` instead of ``RegistrationProfile`` 29 | 30 | """ 31 | 32 | registration_profile = SupervisedRegistrationProfile 33 | 34 | 35 | class ResendActivationView(BaseResendActivationView): 36 | 37 | """ 38 | Follows the exact logic of 39 | ``registration.backends.default.views.ResendActivationView`` but uses 40 | ``SupervisedRegistrationProfile`` instead of ``RegistrationProfile`` 41 | 42 | """ 43 | 44 | registration_profile = SupervisedRegistrationProfile 45 | 46 | 47 | class ApprovalView(BaseApprovalView): 48 | def approve(self, *args, **kwargs): 49 | """ 50 | Given a user id, look up and approve the user account 51 | corresponding to that key (if possible). 52 | 53 | After successful approval, the signal 54 | ``registration.signals.user_approved`` will be sent, with the 55 | newly approved ``User`` as the keyword argument ``user`` and 56 | the class of this backend as the sender. 57 | 58 | """ 59 | user_id = kwargs.get('profile_id', '') 60 | approved_user = ( 61 | SupervisedRegistrationProfile.objects.admin_approve_user( 62 | user_id, get_current_site(self.request))) 63 | if approved_user: 64 | signals.user_approved.send( 65 | sender=self.__class__, 66 | user=approved_user, 67 | request=self.request 68 | ) 69 | return approved_user 70 | 71 | def get_success_url(self, user): 72 | return ('registration_approve_complete', (), {}) 73 | -------------------------------------------------------------------------------- /registration/backends/default/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/backends/default/__init__.py -------------------------------------------------------------------------------- /registration/backends/default/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URLconf for registration and activation, using django-registration's 3 | default backend. 4 | 5 | If the default behavior of these views is acceptable to you, simply 6 | use a line like this in your root URLconf to set up the default URLs 7 | for registration:: 8 | 9 | (r'^accounts/', include('registration.backends.default.urls')), 10 | 11 | This will also automatically set up the views in 12 | ``django.contrib.auth`` at sensible default locations. 13 | 14 | If you'd like to customize registration behavior, feel free to set up 15 | your own URL patterns for these views instead. 16 | 17 | """ 18 | 19 | 20 | from django.conf import settings 21 | from django.conf.urls import include 22 | from django.urls import path 23 | from django.views.generic.base import TemplateView 24 | 25 | from .views import ActivationView 26 | from .views import RegistrationView 27 | from .views import ResendActivationView 28 | 29 | urlpatterns = [ 30 | path('activate/complete/', 31 | TemplateView.as_view(template_name='registration/activation_complete.html'), 32 | name='registration_activation_complete'), 33 | path('activate/resend/', 34 | ResendActivationView.as_view(), 35 | name='registration_resend_activation'), 36 | # Activation keys get matched by \w+ instead of the more specific 37 | # [a-fA-F0-9]{40} because a bad activation key should still get to the view; 38 | # that way it can return a sensible "invalid key" message instead of a 39 | # confusing 404. 40 | path('activate//', 41 | ActivationView.as_view(), 42 | name='registration_activate'), 43 | path('register/complete/', 44 | TemplateView.as_view(template_name='registration/registration_complete.html'), 45 | name='registration_complete'), 46 | path('register/closed/', 47 | TemplateView.as_view(template_name='registration/registration_closed.html'), 48 | name='registration_disallowed'), 49 | ] 50 | 51 | if getattr(settings, 'INCLUDE_REGISTER_URL', True): 52 | urlpatterns += [ 53 | path('register/', 54 | RegistrationView.as_view(), 55 | name='registration_register'), 56 | ] 57 | 58 | if getattr(settings, 'INCLUDE_AUTH_URLS', True): 59 | urlpatterns += [ 60 | path('', include('registration.auth_urls')), 61 | ] 62 | -------------------------------------------------------------------------------- /registration/backends/default/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.sites.shortcuts import get_current_site 3 | from django.shortcuts import render 4 | 5 | from ... import signals 6 | from ...models import RegistrationProfile 7 | from ...users import UserModel 8 | from ...views import ActivationView as BaseActivationView 9 | from ...views import RegistrationView as BaseRegistrationView 10 | from ...views import ResendActivationView as BaseResendActivationView 11 | 12 | 13 | class RegistrationView(BaseRegistrationView): 14 | """ 15 | A registration backend which follows a simple workflow: 16 | 17 | 1. User signs up, inactive account is created. 18 | 19 | 2. Email is sent to user with activation link. 20 | 21 | 3. User clicks activation link, account is now active. 22 | 23 | Using this backend requires that 24 | 25 | * ``registration`` be listed in the ``INSTALLED_APPS`` setting 26 | (since this backend makes use of models defined in this 27 | application). 28 | 29 | * The setting ``ACCOUNT_ACTIVATION_DAYS`` be supplied, specifying 30 | (as an integer) the number of days from registration during 31 | which a user may activate their account (after that period 32 | expires, activation will be disallowed). 33 | 34 | * The creation of the templates 35 | ``registration/activation_email_subject.txt`` and 36 | ``registration/activation_email.txt``, which will be used for 37 | the activation email. See the notes for this backends 38 | ``register`` method for details regarding these templates. 39 | 40 | When subclassing this view, you can set the ``SEND_ACTIVATION_EMAIL`` 41 | class variable to False to skip sending the new user a confirmation 42 | email or set ``SEND_ACTIVATION_EMAIL`` to ``False``. Doing so implies 43 | that you will have to activate the user manually from the admin site or 44 | send an activation by some other method. For example, by listening for 45 | the ``user_registered`` signal. 46 | 47 | Additionally, registration can be temporarily closed by adding the 48 | setting ``REGISTRATION_OPEN`` and setting it to 49 | ``False``. Omitting this setting, or setting it to ``True``, will 50 | be interpreted as meaning that registration is currently open and 51 | permitted. 52 | 53 | Internally, this is accomplished via storing an activation key in 54 | an instance of ``registration.models.RegistrationProfile``. See 55 | that model and its custom manager for full documentation of its 56 | fields and supported operations. 57 | 58 | """ 59 | SEND_ACTIVATION_EMAIL = getattr(settings, 'SEND_ACTIVATION_EMAIL', True) 60 | success_url = 'registration_complete' 61 | 62 | registration_profile = RegistrationProfile 63 | 64 | def register(self, form): 65 | """ 66 | Given a username, email address and password, register a new 67 | user account, which will initially be inactive. 68 | 69 | Along with the new ``User`` object, a new 70 | ``registration.models.RegistrationProfile`` will be created, 71 | tied to that ``User``, containing the activation key which 72 | will be used for this account. 73 | 74 | An email will be sent to the supplied email address; this 75 | email should contain an activation link. The email will be 76 | rendered using two templates. See the documentation for 77 | ``RegistrationProfile.send_activation_email()`` for 78 | information about these templates and the contexts provided to 79 | them. 80 | 81 | After the ``User`` and ``RegistrationProfile`` are created and 82 | the activation email is sent, the signal 83 | ``registration.signals.user_registered`` will be sent, with 84 | the new ``User`` as the keyword argument ``user`` and the 85 | class of this backend as the sender. 86 | 87 | """ 88 | site = get_current_site(self.request) 89 | 90 | if hasattr(form, 'save'): 91 | new_user_instance = form.save(commit=False) 92 | else: 93 | new_user_instance = (UserModel().objects 94 | .create_user(**form.cleaned_data)) 95 | 96 | new_user = self.registration_profile.objects.create_inactive_user( 97 | new_user=new_user_instance, 98 | site=site, 99 | send_email=self.SEND_ACTIVATION_EMAIL, 100 | request=self.request, 101 | ) 102 | signals.user_registered.send(sender=self.__class__, 103 | user=new_user, 104 | request=self.request) 105 | return new_user 106 | 107 | def registration_allowed(self): 108 | """ 109 | Indicate whether account registration is currently permitted, 110 | based on the value of the setting ``REGISTRATION_OPEN``. This 111 | is determined as follows: 112 | 113 | * If ``REGISTRATION_OPEN`` is not specified in settings, or is 114 | set to ``True``, registration is permitted. 115 | 116 | * If ``REGISTRATION_OPEN`` is both specified and set to 117 | ``False``, registration is not permitted. 118 | 119 | """ 120 | return getattr(settings, 'REGISTRATION_OPEN', True) 121 | 122 | 123 | class ActivationView(BaseActivationView): 124 | 125 | registration_profile = RegistrationProfile 126 | 127 | def activate(self, *args, **kwargs): 128 | """ 129 | Given an an activation key, look up and activate the user 130 | account corresponding to that key (if possible). 131 | 132 | After successful activation, the signal 133 | ``registration.signals.user_activated`` will be sent, with the 134 | newly activated ``User`` as the keyword argument ``user`` and 135 | the class of this backend as the sender. 136 | 137 | """ 138 | activation_key = kwargs.get('activation_key', '') 139 | site = get_current_site(self.request) 140 | user, activated = self.registration_profile.objects.activate_user( 141 | activation_key, site) 142 | if activated: 143 | signals.user_activated.send(sender=self.__class__, 144 | user=user, 145 | request=self.request) 146 | return user 147 | 148 | def get_success_url(self, user): 149 | return ('registration_activation_complete', (), {}) 150 | 151 | 152 | class ResendActivationView(BaseResendActivationView): 153 | 154 | registration_profile = RegistrationProfile 155 | 156 | def resend_activation(self, form): 157 | """ 158 | Given an email, look up user by email and resend activation key 159 | if user is not already activated or previous activation key has 160 | not expired. Note that if multiple users exist with the given 161 | email, no emails will be sent. 162 | 163 | Returns True if activation key was successfully sent, False otherwise. 164 | 165 | """ 166 | site = get_current_site(self.request) 167 | email = form.cleaned_data['email'] 168 | return self.registration_profile.objects.resend_activation_mail( 169 | email, site, self.request) 170 | 171 | def render_form_submitted_template(self, form): 172 | """ 173 | Renders resend activation complete template with the submitted email. 174 | 175 | """ 176 | email = form.cleaned_data['email'] 177 | context = {'email': email} 178 | return render(self.request, 179 | 'registration/resend_activation_complete.html', 180 | context) 181 | -------------------------------------------------------------------------------- /registration/backends/simple/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/backends/simple/__init__.py -------------------------------------------------------------------------------- /registration/backends/simple/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URLconf for registration and activation, using django-registration's 3 | one-step backend. 4 | 5 | If the default behavior of these views is acceptable to you, simply 6 | use a line like this in your root URLconf to set up the default URLs 7 | for registration:: 8 | 9 | (r'^accounts/', include('registration.backends.simple.urls')), 10 | 11 | This will also automatically set up the views in 12 | ``django.contrib.auth`` at sensible default locations. 13 | 14 | If you'd like to customize registration behavior, feel free to set up 15 | your own URL patterns for these views instead. 16 | 17 | """ 18 | 19 | 20 | from django.conf import settings 21 | from django.conf.urls import include 22 | from django.urls import path 23 | from django.views.generic.base import TemplateView 24 | 25 | from .views import RegistrationView 26 | 27 | urlpatterns = [ 28 | path('register/closed/', 29 | TemplateView.as_view(template_name='registration/registration_closed.html'), 30 | name='registration_disallowed'), 31 | ] 32 | 33 | if getattr(settings, 'INCLUDE_REGISTER_URL', True): 34 | urlpatterns += [ 35 | path('register/', 36 | RegistrationView.as_view( 37 | success_url=getattr(settings, 'SIMPLE_BACKEND_REDIRECT_URL', '/'), 38 | ), 39 | name='registration_register'), 40 | ] 41 | 42 | if getattr(settings, 'INCLUDE_AUTH_URLS', True): 43 | urlpatterns += [ 44 | path('', include('registration.auth_urls')), 45 | ] 46 | -------------------------------------------------------------------------------- /registration/backends/simple/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth import authenticate 3 | from django.contrib.auth import login 4 | 5 | from ... import signals 6 | from ...views import RegistrationView as BaseRegistrationView 7 | 8 | 9 | class RegistrationView(BaseRegistrationView): 10 | """ 11 | A registration backend which implements the simplest possible 12 | workflow: a user supplies a username, email address and password 13 | (the bare minimum for a useful account), and is immediately signed 14 | up and logged in). 15 | 16 | """ 17 | success_url = 'registration_complete' 18 | 19 | def register(self, form): 20 | new_user = form.save() 21 | username_field = getattr(new_user, 'USERNAME_FIELD', 'username') 22 | new_user = authenticate( 23 | username=getattr(new_user, username_field), 24 | password=form.cleaned_data['password1'] 25 | ) 26 | 27 | login(self.request, new_user) 28 | signals.user_registered.send(sender=self.__class__, 29 | user=new_user, 30 | request=self.request) 31 | return new_user 32 | 33 | def registration_allowed(self): 34 | """ 35 | Indicate whether account registration is currently permitted, 36 | based on the value of the setting ``REGISTRATION_OPEN``. This 37 | is determined as follows: 38 | 39 | * If ``REGISTRATION_OPEN`` is not specified in settings, or is 40 | set to ``True``, registration is permitted. 41 | 42 | * If ``REGISTRATION_OPEN`` is both specified and set to 43 | ``False``, registration is not permitted. 44 | 45 | """ 46 | return getattr(settings, 'REGISTRATION_OPEN', True) 47 | -------------------------------------------------------------------------------- /registration/forms.py: -------------------------------------------------------------------------------- 1 | """ 2 | Forms and validation code for user registration. 3 | 4 | Note that all of these forms assume Django's bundle default ``User`` 5 | model; since it's not possible for a form to anticipate in advance the 6 | needs of custom user models, you will need to write your own forms if 7 | you're using a custom model. 8 | 9 | """ 10 | from django import forms 11 | from django.contrib.auth.forms import UserCreationForm 12 | 13 | from .users import UserModel 14 | from .users import UsernameField 15 | from .utils import _ 16 | 17 | User = UserModel() 18 | 19 | 20 | class RegistrationForm(UserCreationForm): 21 | """ 22 | Form for registering a new user account. 23 | 24 | Validates that the requested username is not already in use, and 25 | requires the password to be entered twice to catch typos. 26 | 27 | Subclasses should feel free to add any additional validation they 28 | need, but should avoid defining a ``save()`` method -- the actual 29 | saving of collected user data is delegated to the active 30 | registration backend. 31 | 32 | """ 33 | required_css_class = 'required' 34 | email = forms.EmailField(label=_("E-mail")) 35 | 36 | class Meta: 37 | model = User 38 | fields = (UsernameField(), "email") 39 | 40 | 41 | class RegistrationFormUsernameLowercase(RegistrationForm): 42 | """ 43 | A subclass of :class:`RegistrationForm` which enforces unique case insensitive 44 | usernames, make all usernames to lower case. 45 | 46 | """ 47 | def clean_username(self): 48 | username = self.cleaned_data.get('username', '').lower() 49 | if User.objects.filter(**{UsernameField(): username}).exists(): 50 | raise forms.ValidationError(_('A user with that username already exists.')) 51 | 52 | return username 53 | 54 | 55 | class RegistrationFormTermsOfService(RegistrationForm): 56 | """ 57 | Subclass of ``RegistrationForm`` which adds a required checkbox 58 | for agreeing to a site's Terms of Service. 59 | 60 | """ 61 | tos = forms.BooleanField(widget=forms.CheckboxInput, 62 | label=_('I have read and agree to the Terms of Service'), 63 | error_messages={'required': _("You must agree to the terms to register")}) 64 | 65 | 66 | class RegistrationFormUniqueEmail(RegistrationForm): 67 | """ 68 | Subclass of ``RegistrationForm`` which enforces uniqueness of 69 | email addresses. 70 | 71 | """ 72 | def clean_email(self): 73 | """ 74 | Validate that the supplied email address is unique for the 75 | site. 76 | 77 | """ 78 | if User.objects.filter(email__iexact=self.cleaned_data['email']): 79 | raise forms.ValidationError(_("This email address is already in use. Please supply a different email address.")) 80 | return self.cleaned_data['email'] 81 | 82 | 83 | class RegistrationFormNoFreeEmail(RegistrationForm): 84 | """ 85 | Subclass of ``RegistrationForm`` which disallows registration with 86 | email addresses from popular free webmail services; moderately 87 | useful for preventing automated spam registrations. 88 | 89 | To change the list of banned domains, subclass this form and 90 | override the attribute ``bad_domains``. 91 | 92 | """ 93 | bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com', 94 | 'googlemail.com', 'hotmail.com', 'hushmail.com', 95 | 'msn.com', 'mail.ru', 'mailinator.com', 'live.com', 96 | 'yahoo.com', 'outlook.com'] 97 | 98 | def clean_email(self): 99 | """ 100 | Check the supplied email address against a list of known free 101 | webmail domains. 102 | 103 | """ 104 | email_domain = self.cleaned_data['email'].split('@')[1] 105 | if email_domain in self.bad_domains: 106 | raise forms.ValidationError(_("Registration using free email addresses is prohibited. Please supply a different email address.")) 107 | return self.cleaned_data['email'] 108 | 109 | 110 | class ResendActivationForm(forms.Form): 111 | required_css_class = 'required' 112 | email = forms.EmailField(label=_("E-mail")) 113 | -------------------------------------------------------------------------------- /registration/locale/ar/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/ar/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/bg/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/bg/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/ca/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/ca/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/cs/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/cs/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/da/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/da/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/el/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/el/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2017-10-17 11:22-0400\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: admin.py:26 21 | msgid "Activate users" 22 | msgstr "" 23 | 24 | #: admin.py:44 25 | msgid "Re-send activation emails" 26 | msgstr "" 27 | 28 | #: forms.py:36 forms.py:114 29 | msgid "E-mail" 30 | msgstr "" 31 | 32 | #: forms.py:52 33 | msgid "A user with that username already exists." 34 | msgstr "" 35 | 36 | #: forms.py:64 37 | msgid "I have read and agree to the Terms of Service" 38 | msgstr "" 39 | 40 | #: forms.py:65 41 | msgid "You must agree to the terms to register" 42 | msgstr "" 43 | 44 | #: forms.py:81 45 | msgid "" 46 | "This email address is already in use. Please supply a different email " 47 | "address." 48 | msgstr "" 49 | 50 | #: forms.py:108 51 | msgid "" 52 | "Registration using free email addresses is prohibited. Please supply a " 53 | "different email address." 54 | msgstr "" 55 | 56 | #: models.py:274 57 | msgid "user" 58 | msgstr "" 59 | 60 | #: models.py:276 61 | msgid "activation key" 62 | msgstr "" 63 | 64 | #: models.py:282 65 | msgid "registration profile" 66 | msgstr "" 67 | 68 | #: models.py:283 69 | msgid "registration profiles" 70 | msgstr "" 71 | 72 | #: templates/registration/activate.html:4 73 | msgid "Activation Failure" 74 | msgstr "" 75 | 76 | #: templates/registration/activate.html:7 77 | msgid "Account activation failed." 78 | msgstr "" 79 | 80 | #: templates/registration/activation_complete.html:4 81 | #: templates/registration/activation_complete_admin_pending.html:4 82 | msgid "Account Activated" 83 | msgstr "" 84 | 85 | #: templates/registration/activation_complete.html:8 86 | #: templates/registration/activation_complete_admin_pending.html:8 87 | msgid "Your account is now activated." 88 | msgstr "" 89 | 90 | #: templates/registration/activation_complete.html:10 91 | msgid "You can log in." 92 | msgstr "" 93 | 94 | #: templates/registration/activation_complete_admin_pending.html:10 95 | msgid "Once a site administrator activates your account you can login." 96 | msgstr "" 97 | 98 | #: templates/registration/activation_email.html:6 99 | #: templates/registration/admin_approve_email.html:6 100 | msgid "registration" 101 | msgstr "" 102 | 103 | #: templates/registration/activation_email.html:11 104 | #, python-format 105 | msgid "" 106 | "\n" 107 | " You (or someone pretending to be you) have asked to register an account " 108 | "at\n" 109 | " %(site_name)s. If this wasn't you, please ignore this email\n" 110 | " and your address will be removed from our records.\n" 111 | " " 112 | msgstr "" 113 | 114 | #: templates/registration/activation_email.html:18 115 | #, python-format 116 | msgid "" 117 | "\n" 118 | " To activate this account, please click the following link within the " 119 | "next\n" 120 | " %(expiration_days)s days:\n" 121 | " " 122 | msgstr "" 123 | 124 | #: templates/registration/activation_email.html:30 125 | #: templates/registration/admin_approve_email.html:23 126 | #, python-format 127 | msgid "" 128 | "\n" 129 | " Sincerely,\n" 130 | " %(site_name)s Management\n" 131 | " " 132 | msgstr "" 133 | 134 | #: templates/registration/activation_email.txt:2 135 | #, python-format 136 | msgid "" 137 | "\n" 138 | "You (or someone pretending to be you) have asked to register an account at\n" 139 | "%(site_name)s. If this wasn't you, please ignore this email\n" 140 | "and your address will be removed from our records.\n" 141 | msgstr "" 142 | 143 | #: templates/registration/activation_email.txt:7 144 | #, python-format 145 | msgid "" 146 | "\n" 147 | "To activate this account, please click the following link within the next\n" 148 | "%(expiration_days)s days:\n" 149 | msgstr "" 150 | 151 | #: templates/registration/activation_email.txt:14 152 | #, python-format 153 | msgid "" 154 | "\n" 155 | "Sincerely,\n" 156 | "%(site_name)s Management\n" 157 | msgstr "" 158 | 159 | #: templates/registration/activation_email_subject.txt:1 160 | #: templates/registration/admin_approve_complete_email_subject.txt:1 161 | msgid "Account activation on" 162 | msgstr "" 163 | 164 | #: templates/registration/admin_approve.html:4 165 | msgid "Approval Failure" 166 | msgstr "" 167 | 168 | #: templates/registration/admin_approve.html:7 169 | msgid "Account Approval failed." 170 | msgstr "" 171 | 172 | #: templates/registration/admin_approve_complete.html:4 173 | msgid "Account Approved" 174 | msgstr "" 175 | 176 | #: templates/registration/admin_approve_complete.html:8 177 | msgid "The user's account is now approved." 178 | msgstr "" 179 | 180 | #: templates/registration/admin_approve_complete_email.html:6 181 | msgid "admin approval" 182 | msgstr "" 183 | 184 | #: templates/registration/admin_approve_complete_email.html:11 185 | msgid "" 186 | "\n" 187 | " Your account is now approved. You can \n" 188 | " " 189 | msgstr "" 190 | 191 | #: templates/registration/admin_approve_complete_email.html:14 192 | msgid "log in." 193 | msgstr "" 194 | 195 | #: templates/registration/admin_approve_complete_email.txt:2 196 | msgid "" 197 | "\n" 198 | "Your account is now approved. You can log in using the following link\n" 199 | msgstr "" 200 | 201 | #: templates/registration/admin_approve_email.html:11 202 | #, python-format 203 | msgid "" 204 | "\n" 205 | " The following user (%(user)s) has asked to register an account at\n" 206 | " %(site_name)s.\n" 207 | " " 208 | msgstr "" 209 | 210 | #: templates/registration/admin_approve_email.html:17 211 | msgid "" 212 | "\n" 213 | " To approve this, please\n" 214 | " " 215 | msgstr "" 216 | 217 | #: templates/registration/admin_approve_email.html:20 218 | msgid "click here" 219 | msgstr "" 220 | 221 | #: templates/registration/admin_approve_email.txt:2 222 | #, python-format 223 | msgid "" 224 | "\n" 225 | "The following user (%(user)s) has asked to register an account at\n" 226 | "%(site_name)s.\n" 227 | msgstr "" 228 | 229 | #: templates/registration/admin_approve_email.txt:6 230 | msgid "" 231 | "\n" 232 | "To approve this, please click the following link.\n" 233 | msgstr "" 234 | 235 | #: templates/registration/admin_approve_email_subject.txt:1 236 | msgid "Account approval on" 237 | msgstr "" 238 | 239 | #: templates/registration/login.html:4 templates/registration/login.html:10 240 | msgid "Log in" 241 | msgstr "" 242 | 243 | #: templates/registration/login.html:14 244 | msgid "Forgot your password?" 245 | msgstr "" 246 | 247 | #: templates/registration/login.html:14 248 | msgid "Reset it" 249 | msgstr "" 250 | 251 | #: templates/registration/login.html:15 252 | msgid "Not a member?" 253 | msgstr "" 254 | 255 | #: templates/registration/login.html:15 256 | msgid "Register" 257 | msgstr "" 258 | 259 | #: templates/registration/logout.html:4 260 | msgid "Logged out" 261 | msgstr "" 262 | 263 | #: templates/registration/logout.html:7 264 | msgid "Successfully logged out" 265 | msgstr "" 266 | 267 | #: templates/registration/password_change_done.html:4 268 | msgid "Password changed" 269 | msgstr "" 270 | 271 | #: templates/registration/password_change_done.html:7 272 | msgid "Password successfully changed!" 273 | msgstr "" 274 | 275 | #: templates/registration/password_change_form.html:4 276 | #: templates/registration/password_change_form.html:10 277 | msgid "Change password" 278 | msgstr "" 279 | 280 | #: templates/registration/password_reset_complete.html:4 281 | msgid "Password reset complete" 282 | msgstr "" 283 | 284 | #: templates/registration/password_reset_complete.html:8 285 | msgid "Your password has been reset!" 286 | msgstr "" 287 | 288 | #: templates/registration/password_reset_complete.html:9 289 | #, python-format 290 | msgid "You may now log in" 291 | msgstr "" 292 | 293 | #: templates/registration/password_reset_confirm.html:10 294 | msgid "Confirm password reset" 295 | msgstr "" 296 | 297 | #: templates/registration/password_reset_confirm.html:14 298 | msgid "Enter your new password below to reset your password:" 299 | msgstr "" 300 | 301 | #: templates/registration/password_reset_confirm.html:18 302 | msgid "Set password" 303 | msgstr "" 304 | 305 | #: templates/registration/password_reset_done.html:4 306 | msgid "Password reset" 307 | msgstr "" 308 | 309 | #: templates/registration/password_reset_done.html:8 310 | msgid "" 311 | "\n" 312 | " We have sent you an email with a link to reset your password. Please " 313 | "check\n" 314 | " your email and click the link to continue.\n" 315 | " " 316 | msgstr "" 317 | 318 | #: templates/registration/password_reset_email.html:3 319 | msgid "Greetings" 320 | msgstr "" 321 | 322 | #: templates/registration/password_reset_email.html:5 323 | #, python-format 324 | msgid "" 325 | "\n" 326 | "You are receiving this email because you (or someone pretending to be you)\n" 327 | "requested that your password be reset on the %(domain)s site. If you do not\n" 328 | "wish to reset your password, please ignore this message.\n" 329 | msgstr "" 330 | 331 | #: templates/registration/password_reset_email.html:11 332 | msgid "" 333 | "\n" 334 | "To reset your password, please click the following link, or copy and paste " 335 | "it\n" 336 | "into your web browser:\n" 337 | msgstr "" 338 | 339 | #: templates/registration/password_reset_email.html:20 340 | msgid "Your username, in case you've forgotten:" 341 | msgstr "" 342 | 343 | #: templates/registration/password_reset_email.html:23 344 | msgid "Best regards" 345 | msgstr "" 346 | 347 | #: templates/registration/password_reset_email.html:24 348 | msgid "Management" 349 | msgstr "" 350 | 351 | #: templates/registration/password_reset_form.html:4 352 | #: templates/registration/password_reset_form.html:15 353 | msgid "Reset password" 354 | msgstr "" 355 | 356 | #: templates/registration/password_reset_form.html:8 357 | msgid "" 358 | "\n" 359 | " Forgot your password? Enter your email in the form below and we'll send " 360 | "you instructions for creating a new one.\n" 361 | " " 362 | msgstr "" 363 | 364 | #: templates/registration/registration_closed.html:4 365 | msgid "Registration is closed" 366 | msgstr "" 367 | 368 | #: templates/registration/registration_closed.html:7 369 | msgid "Sorry, but registration is closed at this moment. Come back later." 370 | msgstr "" 371 | 372 | #: templates/registration/registration_complete.html:4 373 | msgid "Activation email sent" 374 | msgstr "" 375 | 376 | #: templates/registration/registration_complete.html:7 377 | msgid "Please check your email to complete the registration process." 378 | msgstr "" 379 | 380 | #: templates/registration/registration_form.html:4 381 | msgid "Register for an account" 382 | msgstr "" 383 | 384 | #: templates/registration/registration_form.html:10 385 | #: templates/registration/resend_activation_form.html:10 386 | msgid "Submit" 387 | msgstr "" 388 | 389 | #: templates/registration/resend_activation_complete.html:4 390 | msgid "Account Activation Resent" 391 | msgstr "" 392 | 393 | #: templates/registration/resend_activation_complete.html:8 394 | #, python-format 395 | msgid "" 396 | "\n" 397 | " We have sent an email to %(email)s with further instructions.\n" 398 | " " 399 | msgstr "" 400 | 401 | #: templates/registration/resend_activation_form.html:4 402 | msgid "Resend Activation Email" 403 | msgstr "" 404 | -------------------------------------------------------------------------------- /registration/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/es_AR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/es_AR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/fa/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/fa/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/he/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/he/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/hr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/hr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/hu/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/hu/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/is/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/is/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/ja/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/ja/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/ko/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/ko/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/nb/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/nb/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/pt/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/pt/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/sl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/sl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/sr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/sr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/sv/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/sv/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/tr_TR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/tr_TR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/zh_CN/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/zh_CN/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/locale/zh_CN/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PACKAGE VERSION\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2017-10-17 11:22-0400\n" 11 | "PO-Revision-Date: 2008-03-20 23:22+0800\n" 12 | "Last-Translator: hutuworm \n" 13 | "Language-Team: LANGUAGE \n" 14 | "Language: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: admin.py:26 20 | msgid "Activate users" 21 | msgstr "" 22 | 23 | #: admin.py:44 24 | #, fuzzy 25 | #| msgid "activation key" 26 | msgid "Re-send activation emails" 27 | msgstr "激活密钥" 28 | 29 | #: forms.py:36 forms.py:114 30 | msgid "E-mail" 31 | msgstr "" 32 | 33 | #: forms.py:52 34 | msgid "A user with that username already exists." 35 | msgstr "" 36 | 37 | #: forms.py:64 38 | msgid "I have read and agree to the Terms of Service" 39 | msgstr "我已阅读并同意该服务条款" 40 | 41 | #: forms.py:65 42 | msgid "You must agree to the terms to register" 43 | msgstr "您必须同意注册条款" 44 | 45 | #: forms.py:81 46 | msgid "" 47 | "This email address is already in use. Please supply a different email " 48 | "address." 49 | msgstr "该 Email 地址已有人使用,请提供一个另外的 Email 地址。" 50 | 51 | #: forms.py:108 52 | msgid "" 53 | "Registration using free email addresses is prohibited. Please supply a " 54 | "different email address." 55 | msgstr "禁止使用免费 Email 地址注册,请提供一个另外的 Email 地址。" 56 | 57 | #: models.py:274 58 | msgid "user" 59 | msgstr "用户" 60 | 61 | #: models.py:276 62 | msgid "activation key" 63 | msgstr "激活密钥" 64 | 65 | #: models.py:282 66 | msgid "registration profile" 67 | msgstr "注册信息" 68 | 69 | #: models.py:283 70 | msgid "registration profiles" 71 | msgstr "注册信息" 72 | 73 | #: templates/registration/activate.html:4 74 | #, fuzzy 75 | #| msgid "activation key" 76 | msgid "Activation Failure" 77 | msgstr "激活密钥" 78 | 79 | #: templates/registration/activate.html:7 80 | #, fuzzy 81 | #| msgid "activation key" 82 | msgid "Account activation failed." 83 | msgstr "激活密钥" 84 | 85 | #: templates/registration/activation_complete.html:4 86 | #: templates/registration/activation_complete_admin_pending.html:4 87 | msgid "Account Activated" 88 | msgstr "" 89 | 90 | #: templates/registration/activation_complete.html:8 91 | #: templates/registration/activation_complete_admin_pending.html:8 92 | msgid "Your account is now activated." 93 | msgstr "" 94 | 95 | #: templates/registration/activation_complete.html:10 96 | msgid "You can log in." 97 | msgstr "" 98 | 99 | #: templates/registration/activation_complete_admin_pending.html:10 100 | msgid "Once a site administrator activates your account you can login." 101 | msgstr "" 102 | 103 | #: templates/registration/activation_email.html:6 104 | #: templates/registration/admin_approve_email.html:6 105 | #, fuzzy 106 | #| msgid "registration profile" 107 | msgid "registration" 108 | msgstr "注册信息" 109 | 110 | #: templates/registration/activation_email.html:11 111 | #, python-format 112 | msgid "" 113 | "\n" 114 | " You (or someone pretending to be you) have asked to register an account " 115 | "at\n" 116 | " %(site_name)s. If this wasn't you, please ignore this email\n" 117 | " and your address will be removed from our records.\n" 118 | " " 119 | msgstr "" 120 | 121 | #: templates/registration/activation_email.html:18 122 | #, python-format 123 | msgid "" 124 | "\n" 125 | " To activate this account, please click the following link within the " 126 | "next\n" 127 | " %(expiration_days)s days:\n" 128 | " " 129 | msgstr "" 130 | 131 | #: templates/registration/activation_email.html:30 132 | #: templates/registration/admin_approve_email.html:23 133 | #, python-format 134 | msgid "" 135 | "\n" 136 | " Sincerely,\n" 137 | " %(site_name)s Management\n" 138 | " " 139 | msgstr "" 140 | 141 | #: templates/registration/activation_email.txt:2 142 | #, python-format 143 | msgid "" 144 | "\n" 145 | "You (or someone pretending to be you) have asked to register an account at\n" 146 | "%(site_name)s. If this wasn't you, please ignore this email\n" 147 | "and your address will be removed from our records.\n" 148 | msgstr "" 149 | 150 | #: templates/registration/activation_email.txt:7 151 | #, python-format 152 | msgid "" 153 | "\n" 154 | "To activate this account, please click the following link within the next\n" 155 | "%(expiration_days)s days:\n" 156 | msgstr "" 157 | 158 | #: templates/registration/activation_email.txt:14 159 | #, python-format 160 | msgid "" 161 | "\n" 162 | "Sincerely,\n" 163 | "%(site_name)s Management\n" 164 | msgstr "" 165 | 166 | #: templates/registration/activation_email_subject.txt:1 167 | #: templates/registration/admin_approve_complete_email_subject.txt:1 168 | #, fuzzy 169 | #| msgid "activation key" 170 | msgid "Account activation on" 171 | msgstr "激活密钥" 172 | 173 | #: templates/registration/admin_approve.html:4 174 | msgid "Approval Failure" 175 | msgstr "" 176 | 177 | #: templates/registration/admin_approve.html:7 178 | msgid "Account Approval failed." 179 | msgstr "" 180 | 181 | #: templates/registration/admin_approve_complete.html:4 182 | msgid "Account Approved" 183 | msgstr "" 184 | 185 | #: templates/registration/admin_approve_complete.html:8 186 | msgid "The user's account is now approved." 187 | msgstr "" 188 | 189 | #: templates/registration/admin_approve_complete_email.html:6 190 | msgid "admin approval" 191 | msgstr "" 192 | 193 | #: templates/registration/admin_approve_complete_email.html:11 194 | msgid "" 195 | "\n" 196 | " Your account is now approved. You can \n" 197 | " " 198 | msgstr "" 199 | 200 | #: templates/registration/admin_approve_complete_email.html:14 201 | msgid "log in." 202 | msgstr "" 203 | 204 | #: templates/registration/admin_approve_complete_email.txt:2 205 | msgid "" 206 | "\n" 207 | "Your account is now approved. You can log in using the following link\n" 208 | msgstr "" 209 | 210 | #: templates/registration/admin_approve_email.html:11 211 | #, python-format 212 | msgid "" 213 | "\n" 214 | " The following user (%(user)s) has asked to register an account at\n" 215 | " %(site_name)s.\n" 216 | " " 217 | msgstr "" 218 | 219 | #: templates/registration/admin_approve_email.html:17 220 | msgid "" 221 | "\n" 222 | " To approve this, please\n" 223 | " " 224 | msgstr "" 225 | 226 | #: templates/registration/admin_approve_email.html:20 227 | msgid "click here" 228 | msgstr "" 229 | 230 | #: templates/registration/admin_approve_email.txt:2 231 | #, python-format 232 | msgid "" 233 | "\n" 234 | "The following user (%(user)s) has asked to register an account at\n" 235 | "%(site_name)s.\n" 236 | msgstr "" 237 | 238 | #: templates/registration/admin_approve_email.txt:6 239 | msgid "" 240 | "\n" 241 | "To approve this, please click the following link.\n" 242 | msgstr "" 243 | 244 | #: templates/registration/admin_approve_email_subject.txt:1 245 | msgid "Account approval on" 246 | msgstr "" 247 | 248 | #: templates/registration/login.html:4 templates/registration/login.html:10 249 | msgid "Log in" 250 | msgstr "" 251 | 252 | #: templates/registration/login.html:14 253 | msgid "Forgot your password?" 254 | msgstr "" 255 | 256 | #: templates/registration/login.html:14 257 | msgid "Reset it" 258 | msgstr "" 259 | 260 | #: templates/registration/login.html:15 261 | msgid "Not a member?" 262 | msgstr "" 263 | 264 | #: templates/registration/login.html:15 265 | msgid "Register" 266 | msgstr "" 267 | 268 | #: templates/registration/logout.html:4 269 | msgid "Logged out" 270 | msgstr "" 271 | 272 | #: templates/registration/logout.html:7 273 | msgid "Successfully logged out" 274 | msgstr "" 275 | 276 | #: templates/registration/password_change_done.html:4 277 | #, fuzzy 278 | #| msgid "password (again)" 279 | msgid "Password changed" 280 | msgstr "密码(重复)" 281 | 282 | #: templates/registration/password_change_done.html:7 283 | msgid "Password successfully changed!" 284 | msgstr "" 285 | 286 | #: templates/registration/password_change_form.html:4 287 | #: templates/registration/password_change_form.html:10 288 | #, fuzzy 289 | #| msgid "password" 290 | msgid "Change password" 291 | msgstr "密码" 292 | 293 | #: templates/registration/password_reset_complete.html:4 294 | msgid "Password reset complete" 295 | msgstr "" 296 | 297 | #: templates/registration/password_reset_complete.html:8 298 | msgid "Your password has been reset!" 299 | msgstr "" 300 | 301 | #: templates/registration/password_reset_complete.html:9 302 | #, python-format 303 | msgid "You may now log in" 304 | msgstr "" 305 | 306 | #: templates/registration/password_reset_confirm.html:10 307 | msgid "Confirm password reset" 308 | msgstr "" 309 | 310 | #: templates/registration/password_reset_confirm.html:14 311 | msgid "Enter your new password below to reset your password:" 312 | msgstr "" 313 | 314 | #: templates/registration/password_reset_confirm.html:18 315 | #, fuzzy 316 | #| msgid "password" 317 | msgid "Set password" 318 | msgstr "密码" 319 | 320 | #: templates/registration/password_reset_done.html:4 321 | #, fuzzy 322 | #| msgid "password" 323 | msgid "Password reset" 324 | msgstr "密码" 325 | 326 | #: templates/registration/password_reset_done.html:8 327 | msgid "" 328 | "\n" 329 | " We have sent you an email with a link to reset your password. Please " 330 | "check\n" 331 | " your email and click the link to continue.\n" 332 | " " 333 | msgstr "" 334 | 335 | #: templates/registration/password_reset_email.html:3 336 | msgid "Greetings" 337 | msgstr "" 338 | 339 | #: templates/registration/password_reset_email.html:5 340 | #, python-format 341 | msgid "" 342 | "\n" 343 | "You are receiving this email because you (or someone pretending to be you)\n" 344 | "requested that your password be reset on the %(domain)s site. If you do not\n" 345 | "wish to reset your password, please ignore this message.\n" 346 | msgstr "" 347 | 348 | #: templates/registration/password_reset_email.html:11 349 | msgid "" 350 | "\n" 351 | "To reset your password, please click the following link, or copy and paste " 352 | "it\n" 353 | "into your web browser:\n" 354 | msgstr "" 355 | 356 | #: templates/registration/password_reset_email.html:20 357 | msgid "Your username, in case you've forgotten:" 358 | msgstr "" 359 | 360 | #: templates/registration/password_reset_email.html:23 361 | msgid "Best regards" 362 | msgstr "" 363 | 364 | #: templates/registration/password_reset_email.html:24 365 | msgid "Management" 366 | msgstr "" 367 | 368 | #: templates/registration/password_reset_form.html:4 369 | #: templates/registration/password_reset_form.html:15 370 | #, fuzzy 371 | #| msgid "password" 372 | msgid "Reset password" 373 | msgstr "密码" 374 | 375 | #: templates/registration/password_reset_form.html:8 376 | msgid "" 377 | "\n" 378 | " Forgot your password? Enter your email in the form below and we'll send " 379 | "you instructions for creating a new one.\n" 380 | " " 381 | msgstr "" 382 | 383 | #: templates/registration/registration_closed.html:4 384 | #, fuzzy 385 | #| msgid "registration profile" 386 | msgid "Registration is closed" 387 | msgstr "注册信息" 388 | 389 | #: templates/registration/registration_closed.html:7 390 | msgid "Sorry, but registration is closed at this moment. Come back later." 391 | msgstr "" 392 | 393 | #: templates/registration/registration_complete.html:4 394 | #, fuzzy 395 | #| msgid "activation key" 396 | msgid "Activation email sent" 397 | msgstr "激活密钥" 398 | 399 | #: templates/registration/registration_complete.html:7 400 | msgid "Please check your email to complete the registration process." 401 | msgstr "" 402 | 403 | #: templates/registration/registration_form.html:4 404 | msgid "Register for an account" 405 | msgstr "" 406 | 407 | #: templates/registration/registration_form.html:10 408 | #: templates/registration/resend_activation_form.html:10 409 | msgid "Submit" 410 | msgstr "" 411 | 412 | #: templates/registration/resend_activation_complete.html:4 413 | msgid "Account Activation Resent" 414 | msgstr "" 415 | 416 | #: templates/registration/resend_activation_complete.html:8 417 | #, python-format 418 | msgid "" 419 | "\n" 420 | " We have sent an email to %(email)s with further instructions.\n" 421 | " " 422 | msgstr "" 423 | 424 | #: templates/registration/resend_activation_form.html:4 425 | msgid "Resend Activation Email" 426 | msgstr "" 427 | 428 | #~ msgid "username" 429 | #~ msgstr "用户名" 430 | 431 | #~ msgid "email address" 432 | #~ msgstr "Email 地址" 433 | 434 | #~ msgid "Usernames can only contain letters, numbers and underscores" 435 | #~ msgstr "用户名只能包含字母、数字和下划线" 436 | 437 | #~ msgid "This username is already taken. Please choose another." 438 | #~ msgstr "该用户名已被占用,请另选一个。" 439 | 440 | #~ msgid "You must type the same password each time" 441 | #~ msgstr "您必须输入两遍同样的密码" 442 | -------------------------------------------------------------------------------- /registration/locale/zh_TW/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/locale/zh_TW/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /registration/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/management/__init__.py -------------------------------------------------------------------------------- /registration/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/management/commands/__init__.py -------------------------------------------------------------------------------- /registration/management/commands/cleanupregistration.py: -------------------------------------------------------------------------------- 1 | """ 2 | A management command which deletes expired accounts (e.g., 3 | accounts which signed up but never activated) from the database. 4 | 5 | Calls ``RegistrationProfile.objects.delete_expired_users()``, which 6 | contains the actual logic for determining which accounts are deleted. 7 | 8 | """ 9 | 10 | from django.core.management.base import BaseCommand 11 | 12 | from ...models import RegistrationProfile 13 | 14 | 15 | class Command(BaseCommand): 16 | help = "Delete expired user registrations from the database" 17 | 18 | def handle(self, *args, **options): 19 | self.stdout.write('Running cleanupregistration.') 20 | deleted_count = RegistrationProfile.objects.delete_expired_users() 21 | if deleted_count == 0: 22 | self.stdout.write('cleanupregistration completed. There is no user that has to be deleted.') 23 | else: 24 | self.stdout.write('cleanupregistration completed. Deleted user count=%d' % deleted_count) 25 | -------------------------------------------------------------------------------- /registration/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import migrations 3 | from django.db import models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='RegistrationProfile', 15 | fields=[ 16 | ('id', models.AutoField(serialize=False, verbose_name='ID', primary_key=True, auto_created=True)), 17 | ('activation_key', models.CharField(verbose_name='activation key', max_length=40)), 18 | ('user', models.OneToOneField(on_delete=models.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')), 19 | ], 20 | options={ 21 | 'verbose_name': 'registration profile', 22 | 'verbose_name_plural': 'registration profiles', 23 | }, 24 | bases=(models.Model,), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /registration/migrations/0002_registrationprofile_activated.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | from django.db import models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ('registration', '0001_initial'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name='registrationprofile', 14 | name='activated', 15 | field=models.BooleanField(default=False), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /registration/migrations/0003_migrate_activatedstatus.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | from django.db import models 3 | 4 | 5 | def migrate_activated_status(apps, schema_editor): 6 | # We can't directly import the RegistrationProfile model 7 | # as it may be a different version than this migration expects. 8 | RegistrationProfile = apps.get_model('registration', 'RegistrationProfile') 9 | # Filter the queryset to only fetch already activated profiles. 10 | # Note, we don't use the string constant `ACTIVATED` because we are using 11 | # the actual model, not necessarily the Python class which has said attribute. 12 | db_alias = schema_editor.connection.alias 13 | for rp in RegistrationProfile.objects.using(db_alias).filter(activation_key='ALREADY_ACTIVATED').iterator(): 14 | # Note, it's impossible to get the original activation key, so just 15 | # leave the ALREADY_ACTIVATED string. 16 | rp.activated = True 17 | rp.save() 18 | 19 | 20 | class Migration(migrations.Migration): 21 | 22 | dependencies = [ 23 | ('registration', '0002_registrationprofile_activated'), 24 | ] 25 | 26 | operations = [migrations.RunPython(migrate_activated_status, migrations.RunPython.noop)] 27 | -------------------------------------------------------------------------------- /registration/migrations/0004_supervisedregistrationprofile.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | from django.db import models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ('registration', '0003_migrate_activatedstatus'), 9 | ] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name='SupervisedRegistrationProfile', 14 | fields=[ 15 | ('registrationprofile_ptr', models.OneToOneField( 16 | parent_link=True, 17 | auto_created=True, 18 | on_delete=models.CASCADE, 19 | primary_key=True, 20 | serialize=False, 21 | to='registration.RegistrationProfile') 22 | ), 23 | ], 24 | bases=('registration.registrationprofile',), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /registration/migrations/0005_activation_key_sha256.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | from django.db import models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ('registration', '0004_supervisedregistrationprofile'), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name='registrationprofile', 14 | name='activation_key', 15 | field=models.CharField(max_length=64, verbose_name='activation key'), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /registration/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macropin/django-registration/9d6286bf7e2995da0a5e697771873d3f734fa02c/registration/migrations/__init__.py -------------------------------------------------------------------------------- /registration/signals.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth import get_backends 3 | from django.contrib.auth import login 4 | from django.dispatch import Signal 5 | 6 | # An admin has approved a user's account 7 | user_approved = Signal() 8 | 9 | # A new user has registered. 10 | user_registered = Signal() 11 | 12 | # A user has activated his or her account. 13 | user_activated = Signal() 14 | 15 | 16 | def login_user(sender, user, request, **kwargs): 17 | """ Automatically authenticate the user when activated """ 18 | backend = get_backends()[0] # Hack to bypass `authenticate()`. 19 | user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) 20 | login(request, user) 21 | request.session['REGISTRATION_AUTO_LOGIN'] = True 22 | request.session.modified = True 23 | 24 | 25 | if getattr(settings, 'REGISTRATION_AUTO_LOGIN', False): 26 | user_activated.connect(login_user) 27 | -------------------------------------------------------------------------------- /registration/templates/registration/activate.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Activation Failure" %}{% endblock %} 5 | 6 | {% block content %} 7 |

{% trans "Account activation failed." %}

8 | {% endblock %} 9 | 10 | 11 | {% comment %} 12 | **registration/activate.html** 13 | 14 | Used if account activation fails. With the default setup, has the following context: 15 | 16 | ``activation_key`` 17 | The activation key used during the activation attempt. 18 | {% endcomment %} 19 | -------------------------------------------------------------------------------- /registration/templates/registration/activation_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Account Activated" %}{% endblock %} 5 | 6 | {% block content %} 7 |

8 | {% trans "Your account is now activated." %} 9 | {% if not user.is_authenticated %} 10 | {% trans "You can log in." %} 11 | {% endif %} 12 |

13 | {% endblock %} 14 | 15 | 16 | {% comment %} 17 | **registration/activation_complete.html** 18 | 19 | Used after successful account activation. This template has no context 20 | variables of its own, and should simply inform the user that their 21 | account is now active. 22 | {% endcomment %} 23 | -------------------------------------------------------------------------------- /registration/templates/registration/activation_complete_admin_pending.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Account Activated" %}{% endblock %} 5 | 6 | {% block content %} 7 |

8 | {% trans "Your account is now activated." %} 9 | {% if not user.is_authenticated %} 10 | {% trans "Once a site administrator activates your account you can login." %} 11 | {% endif %} 12 |

13 | {% endblock %} 14 | 15 | 16 | {% comment %} 17 | **registration/activation_complete.html** 18 | 19 | Used after successful account activation. This template has no context 20 | variables of its own, and should simply inform the user that their 21 | account is now active. 22 | {% endcomment %} 23 | -------------------------------------------------------------------------------- /registration/templates/registration/activation_email.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 | 5 | 6 | {{ site.name }} {% trans "registration" %} 7 | 8 | 9 | 10 |

11 | {% blocktrans with site_name=site.name %} 12 | You (or someone pretending to be you) have asked to register an account at 13 | {{ site_name }}. If this wasn't you, please ignore this email 14 | and your address will be removed from our records. 15 | {% endblocktrans %} 16 |

17 |

18 | {% blocktrans %} 19 | To activate this account, please click the following link within the next 20 | {{ expiration_days }} days: 21 | {% endblocktrans %} 22 |

23 | 24 |

25 | 26 | {{site.domain}}{% url 'registration_activate' activation_key %} 27 | 28 |

29 |

30 | {% blocktrans with site_name=site.name %} 31 | Sincerely, 32 | {{ site_name }} Management 33 | {% endblocktrans %} 34 |

35 | 36 | 37 | 38 | 39 | 40 | {% comment %} 41 | **registration/activation_email.html** 42 | 43 | Used to generate the html body of the activation email. Should display a 44 | link the user can click to activate the account. This template has the 45 | following context: 46 | 47 | ``activation_key`` 48 | The activation key for the new account. 49 | 50 | ``expiration_days`` 51 | The number of days remaining during which the account may be 52 | activated. 53 | 54 | ``site`` 55 | An object representing the site on which the user registered; 56 | depending on whether ``django.contrib.sites`` is installed, this 57 | may be an instance of either ``django.contrib.sites.models.Site`` 58 | (if the sites application is installed) or 59 | ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the 60 | documentation for the Django sites framework 61 | `_ for 62 | details regarding these objects' interfaces. 63 | 64 | ``user`` 65 | The new user account 66 | 67 | ``request`` 68 | ``HttpRequest`` instance for better flexibility. 69 | For example it can be used to compute absolute register URL: 70 | 71 | {{ request.scheme }}://{{ request.get_host }}{% url 'registration_activate' activation_key %} 72 | {% endcomment %} 73 | -------------------------------------------------------------------------------- /registration/templates/registration/activation_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans with site_name=site.name %} 3 | You (or someone pretending to be you) have asked to register an account at 4 | {{ site_name }}. If this wasn't you, please ignore this email 5 | and your address will be removed from our records. 6 | {% endblocktrans %} 7 | {% blocktrans %} 8 | To activate this account, please click the following link within the next 9 | {{ expiration_days }} days: 10 | {% endblocktrans %} 11 | 12 | http://{{site.domain}}{% url 'registration_activate' activation_key %} 13 | 14 | {% blocktrans with site_name=site.name %} 15 | Sincerely, 16 | {{ site_name }} Management 17 | {% endblocktrans %} 18 | 19 | 20 | {% comment %} 21 | **registration/activation_email.txt** 22 | 23 | Used to generate the text body of the activation email. Should display a 24 | link the user can click to activate the account. This template has the 25 | following context: 26 | 27 | ``activation_key`` 28 | The activation key for the new account. 29 | 30 | ``expiration_days`` 31 | The number of days remaining during which the account may be 32 | activated. 33 | 34 | ``site`` 35 | An object representing the site on which the user registered; 36 | depending on whether ``django.contrib.sites`` is installed, this 37 | may be an instance of either ``django.contrib.sites.models.Site`` 38 | (if the sites application is installed) or 39 | ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the 40 | documentation for the Django sites framework 41 | `_ for 42 | details regarding these objects' interfaces. 43 | 44 | ``user`` 45 | The new user account 46 | 47 | ``request`` 48 | ``HttpRequest`` instance for better flexibility. 49 | For example it can be used to compute absolute register URL: 50 | 51 | {{ request.scheme }}://{{ request.get_host }}{% url 'registration_activate' activation_key %} 52 | {% endcomment %} 53 | -------------------------------------------------------------------------------- /registration/templates/registration/activation_email_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% trans "Account activation on" %} {{ site.name }} 2 | 3 | 4 | {% comment %} 5 | **registration/activation_email_subject.txt** 6 | 7 | Used to generate the subject line of the activation email. Because the 8 | subject line of an email must be a single line of text, any output 9 | from this template will be forcibly condensed to a single line before 10 | being used. This template has the following context: 11 | 12 | ``activation_key`` 13 | The activation key for the new account. 14 | 15 | ``expiration_days`` 16 | The number of days remaining during which the account may be 17 | activated. 18 | 19 | ``site`` 20 | An object representing the site on which the user registered; 21 | depending on whether ``django.contrib.sites`` is installed, this 22 | may be an instance of either ``django.contrib.sites.models.Site`` 23 | (if the sites application is installed) or 24 | ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the 25 | documentation for the Django sites framework 26 | `_ for 27 | details regarding these objects' interfaces. 28 | {% endcomment %} 29 | -------------------------------------------------------------------------------- /registration/templates/registration/admin_approve.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Approval Failure" %}{% endblock %} 5 | 6 | {% block content %} 7 |

{% trans "Account Approval failed." %}

8 | {% endblock %} 9 | 10 | 11 | {% comment %} 12 | **registration/admin_approve.html** 13 | 14 | Used if account activation fails. With the default setup, has the following context: 15 | 16 | ``activation_key`` 17 | The activation key used during the activation attempt. 18 | {% endcomment %} 19 | -------------------------------------------------------------------------------- /registration/templates/registration/admin_approve_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Account Approved" %}{% endblock %} 5 | 6 | {% block content %} 7 |

8 | {% trans "The user's account is now approved." %} 9 |

10 | {% endblock %} 11 | 12 | 13 | {% comment %} 14 | **registration/admin_approve_complete.html** 15 | 16 | Used to generate the html body of the admin activation email. Should display a 17 | link for an admin to approve activation of the account. This template has the 18 | following context: 19 | 20 | ``site`` 21 | An object representing the site on which the user registered; 22 | depending on whether ``django.contrib.sites`` is installed, this 23 | may be an instance of either ``django.contrib.sites.models.Site`` 24 | (if the sites application is installed) or 25 | ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the 26 | documentation for the Django sites framework 27 | `_ for 28 | details regarding these objects' interfaces. 29 | 30 | ``user`` 31 | The new user account 32 | 33 | {% endcomment %} 34 | -------------------------------------------------------------------------------- /registration/templates/registration/admin_approve_complete_email.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 | 5 | 6 | {{ site.name }} {% trans "admin approval" %} 7 | 8 | 9 | 10 |

11 | {% blocktrans %} 12 | Your account is now approved. You can 13 | {% endblocktrans %} 14 | {% trans "log in." %} 15 |

16 | 17 | 18 | 19 | 20 | 21 | {% comment %} 22 | **registration/admin_approve_complete_email.html** 23 | 24 | Used after successful account activation. This template has no context 25 | variables of its own, and should simply inform the user that their 26 | account is now active. 27 | {% endcomment %} 28 | -------------------------------------------------------------------------------- /registration/templates/registration/admin_approve_complete_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %} 3 | Your account is now approved. You can log in using the following link 4 | {% endblocktrans %} 5 | http://{{site.domain}}{% url 'auth_login' %} 6 | 7 | {% comment %} 8 | **registration/admin_approve_complete_email.txt** 9 | 10 | Used after successful account activation. This template has no context 11 | variables of its own, and should simply inform the user that their 12 | account is now active. 13 | {% endcomment %} 14 | -------------------------------------------------------------------------------- /registration/templates/registration/admin_approve_complete_email_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% trans "Account activation on" %} {{ site.name }} 2 | 3 | 4 | {% comment %} 5 | **registration/admin_approve_complete_email_subject.txt** 6 | 7 | Used to generate the subject line of the admin approval complete email. Because 8 | the subject line of an email must be a single line of text, any output 9 | from this template will be forcibly condensed to a single line before 10 | being used. This template has the following context: 11 | 12 | ``site`` 13 | An object representing the site on which the user registered; 14 | depending on whether ``django.contrib.sites`` is installed, this 15 | may be an instance of either ``django.contrib.sites.models.Site`` 16 | (if the sites application is installed) or 17 | ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the 18 | documentation for the Django sites framework 19 | `_ for 20 | details regarding these objects' interfaces. 21 | {% endcomment %} 22 | -------------------------------------------------------------------------------- /registration/templates/registration/admin_approve_email.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 | 5 | 6 | {{ site.name }} {% trans "registration" %} 7 | 8 | 9 | 10 |

11 | {% blocktrans with site_name=site.name %} 12 | The following user ({{ user }}) has asked to register an account at 13 | {{ site_name }}. 14 | {% endblocktrans %} 15 |

16 |

17 | {% blocktrans %} 18 | To approve this, please 19 | {% endblocktrans %} 20 | {% trans "click here" %}. 21 |

22 |

23 | {% blocktrans with site_name=site.name %} 24 | Sincerely, 25 | {{ site_name }} Management 26 | {% endblocktrans %} 27 |

28 | 29 | 30 | 31 | 32 | {% comment %} 33 | **registration/admin_approve_email.html** 34 | 35 | Used to generate the html body of the admin activation email. Should display a 36 | link for an admin to approve activation of the account. This template has the 37 | following context: 38 | 39 | ``profile_id`` 40 | The id of the registration profile requesting approval 41 | 42 | ``site`` 43 | An object representing the site on which the user registered; 44 | depending on whether ``django.contrib.sites`` is installed, this 45 | may be an instance of either ``django.contrib.sites.models.Site`` 46 | (if the sites application is installed) or 47 | ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the 48 | documentation for the Django sites framework 49 | `_ for 50 | details regarding these objects' interfaces. 51 | 52 | ``user`` 53 | The new user account 54 | 55 | ``request`` 56 | ``HttpRequest`` instance for better flexibility. 57 | For example it can be used to compute absolute approval URL: 58 | 59 | {{ request.scheme }}://{{ request.get_host }}{% url 'registration_admin_approve' profile_id %} 60 | {% endcomment %} 61 | -------------------------------------------------------------------------------- /registration/templates/registration/admin_approve_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans with site_name=site.name %} 3 | The following user ({{ user }}) has asked to register an account at 4 | {{ site_name }}. 5 | {% endblocktrans %} 6 | {% blocktrans %} 7 | To approve this, please click the following link. 8 | {% endblocktrans %} 9 | 10 | http://{{site.domain}}{% url 'registration_admin_approve' profile_id %} 11 | -------------------------------------------------------------------------------- /registration/templates/registration/admin_approve_email_subject.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %}{% trans "Account approval on" %} {{ site.name }} 2 | -------------------------------------------------------------------------------- /registration/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Log in" %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 | 12 |
13 | 14 |

{% trans "Forgot your password?" %} {% trans "Reset it" %}.

15 |

{% trans "Not a member?" %} {% trans "Register" %}.

16 | {% endblock %} 17 | 18 | 19 | {% comment %} 20 | **registration/login.html** 21 | 22 | It's your responsibility to provide the login form in a template called 23 | registration/login.html by default. This template gets passed four 24 | template context variables: 25 | 26 | ``form`` 27 | A Form object representing the login form. See the forms 28 | documentation for more on Form objects. 29 | 30 | ``next`` 31 | The URL to redirect to after successful login. This may contain a 32 | query string, too. 33 | 34 | ``site`` 35 | The current Site, according to the SITE_ID setting. If you don't 36 | have the site framework installed, this will be set to an instance 37 | of RequestSite, which derives the site name and domain from the 38 | current HttpRequest. 39 | 40 | ``site_name`` 41 | An alias for site.name. If you don't have the site framework 42 | installed, this will be set to the value of 43 | request.META['SERVER_NAME']. For more on sites, see The 44 | "sites" framework. 45 | {% endcomment %} 46 | -------------------------------------------------------------------------------- /registration/templates/registration/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Logged out" %}{% endblock %} 5 | 6 | {% block content %} 7 |

{% trans "Successfully logged out" %}.

8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /registration/templates/registration/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Password changed" %}{% endblock %} 5 | 6 | {% block content %} 7 |

{% trans "Password successfully changed!" %}

8 | {% endblock %} 9 | 10 | 11 | {# This is used by django.contrib.auth #} 12 | -------------------------------------------------------------------------------- /registration/templates/registration/password_change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Change password" %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 |
12 | {% endblock %} 13 | 14 | 15 | {# This is used by django.contrib.auth #} 16 | -------------------------------------------------------------------------------- /registration/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Password reset complete" %}{% endblock %} 5 | 6 | {% block content %} 7 |

8 | {% trans "Your password has been reset!" %} 9 | {% blocktrans %}You may now log in{% endblocktrans %}. 10 | 11 |

12 | {% endblock %} 13 | 14 | 15 | {# This is used by django.contrib.auth #} 16 | -------------------------------------------------------------------------------- /registration/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block meta %} 5 | 7 | 8 | {% endblock %} 9 | 10 | {% block title %}{% trans "Confirm password reset" %}{% endblock %} 11 | 12 | {% block content %} 13 | {% if validlink %} 14 |

{% trans "Enter your new password below to reset your password:" %}

15 |
16 | {% csrf_token %} 17 | {{ form.as_p }} 18 | 19 |
20 | {% else %} 21 | Password reset unsuccessful. Please try again. 22 | {% endif %} 23 | {% endblock %} 24 | 25 | 26 | {# This is used by django.contrib.auth #} 27 | -------------------------------------------------------------------------------- /registration/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Password reset" %}{% endblock %} 5 | 6 | {% block content %} 7 |

8 | {% blocktrans %} 9 | We have sent you an email with a link to reset your password. Please check 10 | your email and click the link to continue. 11 | {% endblocktrans %} 12 |

13 | {% endblock %} 14 | 15 | 16 | {# This is used by django.contrib.auth #} 17 | -------------------------------------------------------------------------------- /registration/templates/registration/password_reset_email.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% blocktrans %}Greetings{% endblocktrans %} {% if user.get_full_name %}{{ user.get_full_name }}{% else %}{{ user }}{% endif %}, 4 | 5 | {% blocktrans %} 6 | You are receiving this email because you (or someone pretending to be you) 7 | requested that your password be reset on the {{ domain }} site. If you do not 8 | wish to reset your password, please ignore this message. 9 | {% endblocktrans %} 10 | 11 | {% blocktrans %} 12 | To reset your password, please click the following link, or copy and paste it 13 | into your web browser: 14 | {% endblocktrans %} 15 | 16 | 17 | {{ protocol }}://{{ domain }}{% url 'auth_password_reset_confirm' uid token %} 18 | 19 | 20 | {% blocktrans %}Your username, in case you've forgotten:{% endblocktrans %} {{ user.get_username }} 21 | 22 | 23 | {% blocktrans %}Best regards{% endblocktrans %}, 24 | {{ site_name }} {% blocktrans %}Management{% endblocktrans %} 25 | 26 | 27 | {# This is used by django.contrib.auth #} 28 | -------------------------------------------------------------------------------- /registration/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Reset password" %}{% endblock %} 5 | 6 | {% block content %} 7 |

8 | {% blocktrans %} 9 | Forgot your password? Enter your email in the form below and we'll send you instructions for creating a new one. 10 | {% endblocktrans %} 11 |

12 |
13 | {% csrf_token %} 14 | {{ form.as_p }} 15 | 16 |
17 | {% endblock %} 18 | 19 | 20 | {# This is used by django.contrib.auth #} 21 | -------------------------------------------------------------------------------- /registration/templates/registration/registration_base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | -------------------------------------------------------------------------------- /registration/templates/registration/registration_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Registration is closed" %}{% endblock %} 5 | 6 | {% block content %} 7 |

{% trans "Sorry, but registration is closed at this moment. Come back later." %}

8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /registration/templates/registration/registration_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Activation email sent" %}{% endblock %} 5 | 6 | {% block content %} 7 |

8 | {% blocktrans with email=request.session.registration_email %} 9 | Please check your email, {{ email }}, to complete the registration process. 10 | {% endblocktrans %} 11 |

12 | {% endblock %} 13 | 14 | 15 | {% comment %} 16 | **registration/registration_complete.html** 17 | 18 | Used after successful completion of the registration form. This 19 | template has no context variables of its own, and should simply inform 20 | the user that an email containing account-activation information has 21 | been sent. 22 | {% endcomment %} 23 | -------------------------------------------------------------------------------- /registration/templates/registration/registration_form.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Register for an account" %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 |
12 | {% endblock %} 13 | 14 | 15 | {% comment %} 16 | **registration/registration_form.html** 17 | Used to show the form users will fill out to register. By default, has 18 | the following context: 19 | 20 | ``form`` 21 | The registration form. This will be an instance of some subclass 22 | of ``django.forms.Form``; consult `Django's forms documentation 23 | `_ for 24 | information on how to display this in a template. 25 | {% endcomment %} 26 | -------------------------------------------------------------------------------- /registration/templates/registration/resend_activation_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Account Activation Resent" %}{% endblock %} 5 | 6 | {% block content %} 7 |

8 | {% blocktrans %} 9 | We have sent an email to {{ email }} with further instructions. 10 | {% endblocktrans %} 11 |

12 | {% endblock %} 13 | 14 | 15 | {% comment %} 16 | **registration/resend_activation_complete.html** 17 | Used after form for resending account activation is submitted. By default has 18 | the following context: 19 | 20 | ``email`` 21 | The email address submitted in the resend activation form. 22 | {% endcomment %} 23 | -------------------------------------------------------------------------------- /registration/templates/registration/resend_activation_form.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/registration_base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Resend Activation Email" %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 |
12 | {% endblock %} 13 | 14 | 15 | {% comment %} 16 | **registration/resend_activation_form.html** 17 | Used to show the form users will fill out to resend the activation email. By 18 | default, has the following context: 19 | 20 | ``form`` 21 | The registration form. This will be an instance of some subclass 22 | of ``django.forms.Form``; consult `Django's forms documentation 23 | `_ for 24 | information on how to display this in a template. 25 | {% endcomment %} 26 | -------------------------------------------------------------------------------- /registration/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from registration import admin 2 | from registration.backends.default import urls 3 | 4 | 5 | def test(): 6 | assert admin 7 | assert urls 8 | -------------------------------------------------------------------------------- /registration/tests/admin_actions.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin import helpers 2 | from django.core import mail 3 | from django.test import TestCase 4 | from django.test.client import Client 5 | from django.test.utils import override_settings 6 | from django.urls import reverse 7 | 8 | from registration.models import RegistrationProfile 9 | from registration.users import UserModel 10 | 11 | 12 | @override_settings(ACCOUNT_ACTIVATION_DAYS=7, 13 | REGISTRATION_DEFAULT_FROM_EMAIL='registration@email.com', 14 | REGISTRATION_EMAIL_HTML=True, 15 | DEFAULT_FROM_EMAIL='django@email.com') 16 | class AdminCustomActionsTestCase(TestCase): 17 | """ 18 | Test the available admin custom actions 19 | """ 20 | 21 | def setUp(self): 22 | self.client = Client() 23 | admin_user = UserModel().objects.create_superuser( 24 | 'admin', 'admin@test.com', 'admin') 25 | self.client.login(username=admin_user.get_username(), password=admin_user) 26 | 27 | self.user_info = {'username': 'alice', 28 | 'password': 'swordfish', 29 | 'email': 'alice@example.com'} 30 | 31 | def test_activate_users(self): 32 | """ 33 | Test the admin custom command 'activate users' 34 | 35 | """ 36 | new_user = UserModel().objects.create_user(**self.user_info) 37 | profile = RegistrationProfile.objects.create_profile(new_user) 38 | 39 | self.assertFalse(profile.activated) 40 | 41 | registrationprofile_list = reverse( 42 | 'admin:registration_registrationprofile_changelist') 43 | post_data = { 44 | 'action': 'activate_users', 45 | helpers.ACTION_CHECKBOX_NAME: [profile.pk], 46 | } 47 | self.client.post(registrationprofile_list, post_data, follow=True) 48 | 49 | profile = RegistrationProfile.objects.get(user=new_user) 50 | self.assertTrue(profile.activated) 51 | 52 | def test_resend_activation_email(self): 53 | """ 54 | Test the admin custom command 'resend activation email' 55 | """ 56 | new_user = UserModel().objects.create_user(**self.user_info) 57 | profile = RegistrationProfile.objects.create_profile(new_user) 58 | 59 | registrationprofile_list = reverse( 60 | 'admin:registration_registrationprofile_changelist') 61 | post_data = { 62 | 'action': 'resend_activation_email', 63 | helpers.ACTION_CHECKBOX_NAME: [profile.pk], 64 | } 65 | self.client.post(registrationprofile_list, post_data, follow=True) 66 | 67 | self.assertEqual(len(mail.outbox), 1) 68 | self.assertEqual(mail.outbox[0].to, [self.user_info['email']]) 69 | -------------------------------------------------------------------------------- /registration/tests/admin_approval_backend.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core import mail 3 | from django.test.utils import override_settings 4 | from django.urls import reverse 5 | 6 | from .default_backend import DefaultBackendViewTests 7 | 8 | from registration.backends.admin_approval.views import RegistrationView 9 | from registration.models import SupervisedRegistrationProfile 10 | from registration.users import UserModel 11 | 12 | 13 | def get_registration_admins(): 14 | """ 15 | Mock function for testing the admin getter functionality 16 | 17 | """ 18 | return [ 19 | ("Functional admin 1", "func_admin1@fakemail.com"), 20 | ("Functional admin 2", "func_admin2@fakemail.com") 21 | ] 22 | 23 | 24 | @override_settings(ROOT_URLCONF='test_app.urls_admin_approval') 25 | class AdminApprovalBackendViewTests(DefaultBackendViewTests): 26 | """ 27 | Test the admin_approval registration backend. 28 | 29 | Running these tests successfully will require two templates to be 30 | created for the sending of activation emails; details on these 31 | templates and their contexts may be found in the documentation for 32 | the default backend. 33 | 34 | """ 35 | 36 | registration_profile = SupervisedRegistrationProfile 37 | 38 | registration_view = RegistrationView 39 | 40 | def test_approval(self): 41 | """ 42 | Approval of an account functions properly. 43 | 44 | """ 45 | resp = self.client.post(reverse('registration_register'), 46 | data={'username': 'bob', 47 | 'email': 'bob@example.com', 48 | 'password1': 'secret', 49 | 'password2': 'secret'}) 50 | 51 | profile = self.registration_profile.objects.get(user__username='bob') 52 | self.assertFalse(profile.user.is_active) 53 | 54 | resp = self.client.get( 55 | reverse('registration_activate', 56 | args=(), 57 | kwargs={'activation_key': profile.activation_key})) 58 | 59 | admin_user = UserModel().objects.create_superuser('admin', 'admin@test.com', 'admin') 60 | self.client.login(username=admin_user.get_username(), password=admin_user) 61 | 62 | resp = self.client.get( 63 | reverse('registration_admin_approve', 64 | args=(), 65 | kwargs={'profile_id': profile.id})) 66 | profile.user.refresh_from_db() 67 | self.assertTrue(profile.user.is_active) 68 | self.assertRedirects(resp, reverse('registration_approve_complete')) 69 | 70 | @override_settings( 71 | REGISTRATION_ADMINS=[ 72 | ("The admin", "admin_alpha@fakemail.com"), 73 | ("The other admin", "admin_two@fakemail.com") 74 | ] 75 | ) 76 | def test_admins_when_is_list(self): 77 | """ 78 | Admins are pulled from the REGISTRATION_ADMINS list setting 79 | """ 80 | resp = self.client.post(reverse('registration_register'), 81 | data={'username': 'bob', 82 | 'email': 'bob@example.com', 83 | 'password1': 'secret', 84 | 'password2': 'secret'}) 85 | 86 | profile = self.registration_profile.objects.get(user__username='bob') 87 | 88 | resp = self.client.get( 89 | reverse('registration_activate', 90 | args=(), 91 | kwargs={'activation_key': profile.activation_key})) 92 | self.assertRedirects(resp, reverse('registration_activation_complete')) 93 | admins_mail = mail.outbox[1] 94 | self.assertEqual(admins_mail.to, [to[1] for to in settings.REGISTRATION_ADMINS]) 95 | 96 | @override_settings( 97 | REGISTRATION_ADMINS="registration.tests.admin_approval_backend.get_registration_admins" 98 | ) 99 | def test_admins_when_is_getter(self): 100 | """ 101 | Admins are pulled from the REGISTRATION_ADMINS string setting 102 | """ 103 | resp = self.client.post(reverse('registration_register'), 104 | data={'username': 'bob', 105 | 'email': 'bob@example.com', 106 | 'password1': 'secret', 107 | 'password2': 'secret'}) 108 | 109 | profile = self.registration_profile.objects.get(user__username='bob') 110 | 111 | resp = self.client.get( 112 | reverse('registration_activate', 113 | args=(), 114 | kwargs={'activation_key': profile.activation_key})) 115 | self.assertRedirects(resp, reverse('registration_activation_complete')) 116 | admins_mail = mail.outbox[1] 117 | self.assertEqual(admins_mail.to, [to[1] for to in get_registration_admins()]) 118 | -------------------------------------------------------------------------------- /registration/tests/default_backend.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.models import AnonymousUser 5 | from django.contrib.sessions.middleware import SessionMiddleware 6 | from django.core import mail 7 | from django.db import DatabaseError 8 | from django.test import TransactionTestCase 9 | from django.test.client import RequestFactory 10 | from django.test.utils import override_settings 11 | from django.urls import reverse 12 | from mock import patch 13 | 14 | from registration.backends.default.views import RegistrationView 15 | from registration.forms import RegistrationForm 16 | from registration.models import RegistrationProfile 17 | from registration.users import UserModel 18 | 19 | 20 | @override_settings(ROOT_URLCONF='test_app.urls_default', 21 | ACCOUNT_ACTIVATION_DAYS=7) 22 | class DefaultBackendViewTests(TransactionTestCase): 23 | """ 24 | Test the default registration backend. 25 | 26 | Running these tests successfully will require two templates to be 27 | created for the sending of activation emails; details on these 28 | templates and their contexts may be found in the documentation for 29 | the default backend. 30 | 31 | """ 32 | 33 | registration_profile = RegistrationProfile 34 | 35 | registration_view = RegistrationView 36 | 37 | @override_settings(REGISTRATION_OPEN=True) 38 | def test_registration_open(self): 39 | """ 40 | The setting ``REGISTRATION_OPEN`` appropriately controls 41 | whether registration is permitted. 42 | 43 | """ 44 | resp = self.client.get(reverse('registration_register')) 45 | self.assertEqual(200, resp.status_code) 46 | 47 | @override_settings(REGISTRATION_OPEN=False) 48 | def test_registration_closed(self): 49 | 50 | # Now all attempts to hit the register view should redirect to 51 | # the 'registration is closed' message. 52 | resp = self.client.get(reverse('registration_register')) 53 | self.assertRedirects(resp, reverse('registration_disallowed')) 54 | 55 | resp = self.client.post(reverse('registration_register'), 56 | data={'username': 'bob', 57 | 'email': 'bob@example.com', 58 | 'password1': 'secret', 59 | 'password2': 'secret'}) 60 | self.assertRedirects(resp, reverse('registration_disallowed')) 61 | 62 | def test_registration_get(self): 63 | """ 64 | HTTP ``GET`` to the registration view uses the appropriate 65 | template and populates a registration form into the context. 66 | 67 | """ 68 | resp = self.client.get(reverse('registration_register')) 69 | self.assertEqual(200, resp.status_code) 70 | self.assertTemplateUsed(resp, 71 | 'registration/registration_form.html') 72 | self.assertIsInstance(resp.context['form'], RegistrationForm) 73 | 74 | def test_registration(self): 75 | """ 76 | Registration creates a new inactive account and a new profile 77 | with activation key, populates the correct account data and 78 | sends an activation email. 79 | 80 | """ 81 | resp = self.client.post(reverse('registration_register'), 82 | data={'username': 'bob', 83 | 'email': 'bob@example.com', 84 | 'password1': 'secret', 85 | 'password2': 'secret'}) 86 | self.assertRedirects(resp, reverse('registration_complete')) 87 | 88 | new_user = UserModel().objects.get(username='bob') 89 | 90 | self.assertTrue(new_user.check_password('secret')) 91 | self.assertEqual(new_user.email, 'bob@example.com') 92 | 93 | # New user must not be active. 94 | self.assertFalse(new_user.is_active) 95 | 96 | # A registration profile was created, and an activation email 97 | # was sent. 98 | self.assertEqual(self.registration_profile.objects.count(), 1) 99 | self.assertEqual(len(mail.outbox), 1) 100 | 101 | def test_registration_no_email(self): 102 | """ 103 | Overridden Registration view does not send an activation email if the 104 | associated class variable is set to ``False`` 105 | 106 | """ 107 | class RegistrationNoEmailView(self.registration_view): 108 | SEND_ACTIVATION_EMAIL = False 109 | 110 | request_factory = RequestFactory() 111 | view = RegistrationNoEmailView.as_view() 112 | request = request_factory.post('/', data={ 113 | 'username': 'bob', 114 | 'email': 'bob@example.com', 115 | 'password1': 'secret', 116 | 'password2': 'secret'}) 117 | request.user = AnonymousUser() 118 | 119 | def dummy_get_response(request): # pragma: no cover 120 | return None 121 | 122 | middleware = SessionMiddleware(dummy_get_response) 123 | middleware.process_request(request) 124 | view(request) 125 | 126 | UserModel().objects.get(username='bob') 127 | # A registration profile was created, and no activation email was sent. 128 | self.assertEqual(self.registration_profile.objects.count(), 1) 129 | self.assertEqual(len(mail.outbox), 0) 130 | self.assertEqual(request.session.get('registration_email'), "bob@example.com") 131 | 132 | @override_settings( 133 | INSTALLED_APPS=('django.contrib.auth', 'registration',) 134 | ) 135 | def test_registration_no_sites(self): 136 | """ 137 | Registration still functions properly when 138 | ``django.contrib.sites`` is not installed; the fallback will 139 | be a ``RequestSite`` instance. 140 | 141 | """ 142 | resp = self.client.post(reverse('registration_register'), 143 | data={'username': 'bob', 144 | 'email': 'bob@example.com', 145 | 'password1': 'secret', 146 | 'password2': 'secret'}) 147 | self.assertEqual(302, resp.status_code) 148 | 149 | new_user = UserModel().objects.get(username='bob') 150 | 151 | self.assertTrue(new_user.check_password('secret')) 152 | self.assertEqual(new_user.email, 'bob@example.com') 153 | 154 | self.assertFalse(new_user.is_active) 155 | 156 | self.assertEqual(self.registration_profile.objects.count(), 1) 157 | self.assertEqual(len(mail.outbox), 1) 158 | 159 | def test_registration_failure(self): 160 | """ 161 | Registering with invalid data fails. 162 | 163 | """ 164 | resp = self.client.post(reverse('registration_register'), 165 | data={'username': 'bob', 166 | 'email': 'bob@example.com', 167 | 'password1': 'secret', 168 | 'password2': 'notsecret'}) 169 | self.assertEqual(200, resp.status_code) 170 | self.assertFalse(resp.context['form'].is_valid()) 171 | self.assertEqual(0, len(mail.outbox)) 172 | 173 | @patch('registration.models.RegistrationManager.create_inactive_user') 174 | def test_registration_exception(self, create_inactive_user): 175 | """ 176 | User is not created beforehand if an exception occurred at 177 | creating registration profile. 178 | """ 179 | create_inactive_user.side_effect = DatabaseError() 180 | valid_data = {'username': 'bob', 181 | 'email': 'bob@example.com', 182 | 'password1': 'secret', 183 | 'password2': 'secret'} 184 | with self.assertRaises(DatabaseError): 185 | self.client.post(reverse('registration_register'), 186 | data=valid_data) 187 | assert not UserModel().objects.filter(username='bob').exists() 188 | 189 | def test_activation(self): 190 | """ 191 | Activation of an account functions properly. 192 | 193 | """ 194 | resp = self.client.post(reverse('registration_register'), 195 | data={'username': 'bob', 196 | 'email': 'bob@example.com', 197 | 'password1': 'secret', 198 | 'password2': 'secret'}) 199 | 200 | profile = self.registration_profile.objects.get(user__username='bob') 201 | 202 | resp = self.client.get( 203 | reverse('registration_activate', 204 | args=(), 205 | kwargs={'activation_key': profile.activation_key})) 206 | self.assertRedirects(resp, reverse('registration_activation_complete')) 207 | 208 | def test_activation_expired(self): 209 | """ 210 | An expired account can't be activated. 211 | 212 | """ 213 | resp = self.client.post(reverse('registration_register'), 214 | data={'username': 'bob', 215 | 'email': 'bob@example.com', 216 | 'password1': 'secret', 217 | 'password2': 'secret'}) 218 | 219 | profile = self.registration_profile.objects.get(user__username='bob') 220 | user = profile.user 221 | user.date_joined -= datetime.timedelta( 222 | days=settings.ACCOUNT_ACTIVATION_DAYS) 223 | user.save() 224 | 225 | resp = self.client.get( 226 | reverse('registration_activate', 227 | args=(), 228 | kwargs={'activation_key': profile.activation_key})) 229 | 230 | self.assertEqual(200, resp.status_code) 231 | self.assertTemplateUsed(resp, 'registration/activate.html') 232 | user = UserModel().objects.get(username='bob') 233 | self.assertFalse(user.is_active) 234 | 235 | def test_resend_activation(self): 236 | """ 237 | Resend activation functions properly. 238 | 239 | """ 240 | resp = self.client.post(reverse('registration_register'), 241 | data={'username': 'bob', 242 | 'email': 'bob@example.com', 243 | 'password1': 'secret', 244 | 'password2': 'secret'}) 245 | 246 | profile = self.registration_profile.objects.get(user__username='bob') 247 | 248 | resp = self.client.post(reverse('registration_resend_activation'), 249 | data={'email': profile.user.email}) 250 | self.assertTemplateUsed(resp, 251 | 'registration/resend_activation_complete.html') 252 | self.assertEqual(resp.context['email'], profile.user.email) 253 | 254 | def test_resend_activation_invalid_email(self): 255 | """ 256 | Calling resend with an invalid email shows the same template. 257 | 258 | """ 259 | resp = self.client.post(reverse('registration_resend_activation'), 260 | data={'email': 'invalid@example.com'}) 261 | self.assertTemplateUsed(resp, 262 | 'registration/resend_activation_complete.html') 263 | -------------------------------------------------------------------------------- /registration/tests/forms.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.test import TestCase 3 | 4 | from registration import forms 5 | from registration.users import UserModel 6 | 7 | 8 | class RegistrationFormTests(TestCase): 9 | """ 10 | Test the default registration forms. 11 | 12 | """ 13 | 14 | def test_registration_form(self): 15 | """ 16 | Test that ``RegistrationForm`` enforces username constraints 17 | and matching passwords. 18 | 19 | """ 20 | # Create a user so we can verify that duplicate usernames aren't 21 | # permitted. 22 | UserModel().objects.create_user('alice', 'alice@example.com', 'secret') 23 | bad_username_error = ( 24 | 'Enter a valid username. This value may contain only letters, ' 25 | 'numbers, and @/./+/-/_ characters.' 26 | ) 27 | password_didnt_match_error = "The two password fields didn't match." 28 | if django.VERSION >= (3, 0): 29 | password_didnt_match_error = "The two password fields didn’t match." 30 | 31 | invalid_data_dicts = [ 32 | # Non-alphanumeric username. 33 | {'data': {'username': 'foo/bar', 34 | 'email': 'foo@example.com', 35 | 'password1': 'foo', 36 | 'password2': 'foo'}, 37 | 'error': ('username', [bad_username_error])}, 38 | # Already-existing username. 39 | {'data': {'username': 'alice', 40 | 'email': 'alice@example.com', 41 | 'password1': 'secret', 42 | 'password2': 'secret'}, 43 | 'error': ('username', ["A user with that username already exists."])}, 44 | # Mismatched passwords. 45 | {'data': {'username': 'foo', 46 | 'email': 'foo@example.com', 47 | 'password1': 'foo', 48 | 'password2': 'bar'}, 49 | 'error': ('password2', [password_didnt_match_error])}, 50 | ] 51 | 52 | for invalid_dict in invalid_data_dicts: 53 | form = forms.RegistrationForm(data=invalid_dict['data']) 54 | self.assertFalse(form.is_valid()) 55 | self.assertEqual(form.errors[invalid_dict['error'][0]], 56 | invalid_dict['error'][1]) 57 | 58 | form = forms.RegistrationForm(data={'username': 'foo', 59 | 'email': 'foo@example.com', 60 | 'password1': 'foo', 61 | 'password2': 'foo'}) 62 | self.assertTrue(form.is_valid()) 63 | 64 | def test_registration_form_username_lowercase(self): 65 | """ 66 | Test that ``RegistrationFormUniqueEmail`` validates uniqueness 67 | of email addresses. 68 | 69 | """ 70 | # Create a user so we can verify that duplicate addresses 71 | # aren't permitted. 72 | UserModel().objects.create_user('alice', 'alice@example.com', 'secret') 73 | 74 | form = forms.RegistrationFormUsernameLowercase(data={'username': 'Alice', 75 | 'email': 'alice@example.com', 76 | 'password1': 'foo', 77 | 'password2': 'foo'}) 78 | self.assertFalse(form.is_valid()) 79 | self.assertEqual(form.errors['username'], 80 | ["A user with that username already exists."]) 81 | 82 | form = forms.RegistrationFormUsernameLowercase(data={'username': 'foo', 83 | 'email': 'alice@example.com', 84 | 'password1': 'foo', 85 | 'password2': 'foo'}) 86 | self.assertTrue(form.is_valid()) 87 | 88 | def test_registration_form_tos(self): 89 | """ 90 | Test that ``RegistrationFormTermsOfService`` requires 91 | agreement to the terms of service. 92 | 93 | """ 94 | form = forms.RegistrationFormTermsOfService(data={'username': 'foo', 95 | 'email': 'foo@example.com', 96 | 'password1': 'foo', 97 | 'password2': 'foo'}) 98 | self.assertFalse(form.is_valid()) 99 | self.assertEqual(form.errors['tos'], 100 | ["You must agree to the terms to register"]) 101 | 102 | form = forms.RegistrationFormTermsOfService(data={'username': 'foo', 103 | 'email': 'foo@example.com', 104 | 'password1': 'foo', 105 | 'password2': 'foo', 106 | 'tos': 'on'}) 107 | self.assertTrue(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 | # Create a user so we can verify that duplicate addresses 116 | # aren't permitted. 117 | UserModel().objects.create_user('alice', 'alice@example.com', 'secret') 118 | 119 | form = forms.RegistrationFormUniqueEmail(data={'username': 'foo', 120 | 'email': 'alice@example.com', 121 | 'password1': 'foo', 122 | 'password2': 'foo'}) 123 | self.assertFalse(form.is_valid()) 124 | self.assertEqual(form.errors['email'], 125 | ["This email address is already in use. Please supply a different email address."]) 126 | 127 | form = forms.RegistrationFormUniqueEmail(data={'username': 'foo', 128 | 'email': 'foo@example.com', 129 | 'password1': 'foo', 130 | 'password2': 'foo'}) 131 | self.assertTrue(form.is_valid()) 132 | 133 | def test_registration_form_no_free_email(self): 134 | """ 135 | Test that ``RegistrationFormNoFreeEmail`` disallows 136 | registration with free email addresses. 137 | 138 | """ 139 | base_data = {'username': 'foo', 140 | 'password1': 'foo', 141 | 'password2': 'foo'} 142 | for domain in forms.RegistrationFormNoFreeEmail.bad_domains: 143 | invalid_data = base_data.copy() 144 | invalid_data['email'] = "foo@%s" % domain 145 | form = forms.RegistrationFormNoFreeEmail(data=invalid_data) 146 | self.assertFalse(form.is_valid()) 147 | self.assertEqual(form.errors['email'], 148 | ["Registration using free email addresses is prohibited. Please supply a different email address."]) 149 | 150 | base_data['email'] = 'foo@example.com' 151 | form = forms.RegistrationFormNoFreeEmail(data=base_data) 152 | self.assertTrue(form.is_valid()) 153 | -------------------------------------------------------------------------------- /registration/tests/forms_custom_user.py: -------------------------------------------------------------------------------- 1 | from importlib import reload 2 | 3 | from django.test import TestCase 4 | from django.test.utils import override_settings 5 | 6 | from registration import forms 7 | from registration.users import UsernameField 8 | 9 | 10 | @override_settings(AUTH_USER_MODEL='test_app.CustomUser') 11 | class RegistrationFormTests(TestCase): 12 | """ 13 | Test the default registration forms. 14 | 15 | """ 16 | 17 | def setUp(self): 18 | # The form's Meta class is created on import. We have to reload() 19 | # to apply the new AUTH_USER_MODEL to the Meta class. 20 | reload(forms) 21 | 22 | def test_registration_form_adds_custom_user_name_field(self): 23 | """ 24 | Test that ``RegistrationForm`` adds custom username 25 | field and does not raise errors 26 | 27 | """ 28 | 29 | form = forms.RegistrationForm() 30 | 31 | self.assertTrue(UsernameField() in form.fields) 32 | 33 | def test_registration_form_subclass_is_valid(self): 34 | """ 35 | Test that ``RegistrationForm`` subclasses can save 36 | 37 | """ 38 | data = {'new_field': 'custom username', 39 | 'email': 'foo@example.com', 40 | 'password1': 'foo', 41 | 'password2': 'foo'} 42 | 43 | form = forms.RegistrationForm(data=data) 44 | 45 | self.assertTrue(form.is_valid()) 46 | -------------------------------------------------------------------------------- /registration/tests/simple_backend.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.test import TestCase 3 | from django.test import override_settings 4 | from django.urls import reverse 5 | 6 | from registration.forms import RegistrationForm 7 | from registration.users import UserModel 8 | 9 | 10 | @override_settings(ROOT_URLCONF='test_app.urls_simple') 11 | class SimpleBackendViewTests(TestCase): 12 | 13 | @override_settings(REGISTRATION_OPEN=True) 14 | def test_registration_open(self): 15 | """ 16 | The setting ``REGISTRATION_OPEN`` appropriately controls 17 | whether registration is permitted. 18 | 19 | """ 20 | resp = self.client.get(reverse('registration_register')) 21 | self.assertEqual(200, resp.status_code) 22 | 23 | @override_settings(REGISTRATION_OPEN=False) 24 | def test_registration_closed(self): 25 | 26 | # Now all attempts to hit the register view should redirect to 27 | # the 'registration is closed' message. 28 | resp = self.client.get(reverse('registration_register')) 29 | self.assertRedirects(resp, reverse('registration_disallowed')) 30 | 31 | resp = self.client.post(reverse('registration_register'), 32 | data={'username': 'bob', 33 | 'email': 'bob@example.com', 34 | 'password1': 'secret', 35 | 'password2': 'secret'}) 36 | self.assertRedirects(resp, reverse('registration_disallowed')) 37 | 38 | def test_registration_get(self): 39 | """ 40 | HTTP ``GET`` to the registration view uses the appropriate 41 | template and populates a registration form into the context. 42 | 43 | """ 44 | resp = self.client.get(reverse('registration_register')) 45 | self.assertEqual(200, resp.status_code) 46 | self.assertTemplateUsed(resp, 47 | 'registration/registration_form.html') 48 | self.assertIsInstance(resp.context['form'], RegistrationForm) 49 | 50 | def test_registration(self): 51 | """ 52 | Registration creates a new account and logs the user in. 53 | 54 | """ 55 | resp = self.client.post(reverse('registration_register'), 56 | data={'username': 'bob', 57 | 'email': 'bob@example.com', 58 | 'password1': 'secret', 59 | 'password2': 'secret'}) 60 | new_user = UserModel().objects.get(username='bob') 61 | self.assertEqual(302, resp.status_code) 62 | self.assertIn(getattr(settings, 'SIMPLE_BACKEND_REDIRECT_URL', '/'), 63 | resp['Location']) 64 | 65 | self.assertTrue(new_user.check_password('secret')) 66 | self.assertEqual(new_user.email, 'bob@example.com') 67 | 68 | # New user must be active. 69 | self.assertTrue(new_user.is_active) 70 | 71 | # New user must be logged in. 72 | resp = self.client.get(reverse('registration_register'), follow=True) 73 | self.assertTrue(resp.context['user'].is_authenticated) 74 | 75 | def test_registration_failure(self): 76 | """ 77 | Registering with invalid data fails. 78 | 79 | """ 80 | resp = self.client.post(reverse('registration_register'), 81 | data={'username': 'bob', 82 | 'email': 'bob@example.com', 83 | 'password1': 'secret', 84 | 'password2': 'notsecret'}) 85 | self.assertEqual(200, resp.status_code) 86 | self.assertFalse(resp.context['form'].is_valid()) 87 | -------------------------------------------------------------------------------- /registration/tests/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URLs used in the unit tests for django-registration. 3 | 4 | You should not attempt to use these URLs in any sort of real or 5 | development environment; instead, use 6 | ``registration/backends/default/urls.py``. This URLconf includes those 7 | URLs, and also adds several additional URLs which serve no purpose 8 | other than to test that optional keyword arguments are properly 9 | handled. 10 | 11 | """ 12 | 13 | from django.conf.urls import include 14 | from django.urls import path 15 | from django.views.generic import TemplateView 16 | 17 | from registration.views import ActivationView 18 | from registration.views import RegistrationView 19 | 20 | urlpatterns = [ 21 | # Test the 'activate' view with custom template 22 | # name. 23 | path('activate-with-template-name//', 24 | ActivationView.as_view(), 25 | {'template_name': 'registration/test_template_name.html', 26 | 'backend': 'registration.backends.default.DefaultBackend'}, 27 | name='registration_test_activate_template_name'), 28 | # Test the 'activate' view with 29 | # extra_context_argument. 30 | path('activate-extra-context//', 31 | ActivationView.as_view(), 32 | {'extra_context': {'foo': 'bar', 'callable': lambda: 'called'}, 33 | 'backend': 'registration.backends.default.DefaultBackend'}, 34 | name='registration_test_activate_extra_context'), 35 | # Test the 'activate' view with success_url argument. 36 | path('activate-with-success-url//', 37 | ActivationView.as_view(), 38 | {'success_url': 'registration_test_custom_success_url', 39 | 'backend': 'registration.backends.default.DefaultBackend'}, 40 | name='registration_test_activate_success_url'), 41 | # Test the 'register' view with custom template 42 | # name. 43 | path('register-with-template-name/', 44 | RegistrationView.as_view(), 45 | {'template_name': 'registration/test_template_name.html', 46 | 'backend': 'registration.backends.default.DefaultBackend'}, 47 | name='registration_test_register_template_name'), 48 | # Test the'register' view with extra_context 49 | # argument. 50 | path('register-extra-context/', 51 | RegistrationView.as_view(), 52 | {'extra_context': {'foo': 'bar', 'callable': lambda: 'called'}, 53 | 'backend': 'registration.backends.default.DefaultBackend'}, 54 | name='registration_test_register_extra_context'), 55 | # Test the 'register' view with custom URL for 56 | # closed registration. 57 | path('register-with-disallowed-url/', 58 | RegistrationView.as_view(), 59 | {'disallowed_url': 'registration_test_custom_disallowed', 60 | 'backend': 'registration.backends.default.DefaultBackend'}, 61 | name='registration_test_register_disallowed_url'), 62 | # Set up a pattern which will correspond to the 63 | # custom 'disallowed_url' above. 64 | path('custom-disallowed/', 65 | TemplateView.as_view(template_name='registration/registration_closed.html'), 66 | name='registration_test_custom_disallowed'), 67 | # Test the 'register' view with custom redirect 68 | # on successful registration. 69 | path('register-with-success_url/', 70 | RegistrationView.as_view(), 71 | {'success_url': 'registration_test_custom_success_url', 72 | 'backend': 'registration.backends.default.DefaultBackend'}, 73 | name='registration_test_register_success_url'), 74 | # Pattern for custom redirect set above. 75 | path('custom-success/', 76 | TemplateView.as_view(template_name='registration/test_template_name.html'), 77 | name='registration_test_custom_success_url'), 78 | path('', include('registration.backends.default.urls')), 79 | ] 80 | -------------------------------------------------------------------------------- /registration/users.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth import get_user_model 3 | 4 | UserModel = get_user_model 5 | 6 | 7 | def UserModelString(): 8 | try: 9 | return settings.AUTH_USER_MODEL 10 | except AttributeError: 11 | return 'auth.User' 12 | 13 | 14 | def UsernameField(): 15 | return getattr(UserModel(), 'USERNAME_FIELD', 'username') 16 | -------------------------------------------------------------------------------- /registration/utils.py: -------------------------------------------------------------------------------- 1 | from django import VERSION as DJANGO_VERSION 2 | 3 | if DJANGO_VERSION[0] < 3: 4 | from django.utils.translation import ugettext_lazy as _ # noqa 5 | else: 6 | from django.utils.translation import gettext_lazy as _ # noqa 7 | -------------------------------------------------------------------------------- /registration/views.py: -------------------------------------------------------------------------------- 1 | """ 2 | Views which allow users to create and activate accounts. 3 | 4 | """ 5 | 6 | from django.conf import settings 7 | from django.shortcuts import redirect 8 | from django.utils.decorators import method_decorator 9 | from django.utils.module_loading import import_string 10 | from django.views.decorators.debug import sensitive_post_parameters 11 | from django.views.generic.base import TemplateView 12 | from django.views.generic.edit import FormView 13 | 14 | from registration.forms import ResendActivationForm 15 | 16 | REGISTRATION_FORM_PATH = getattr(settings, 'REGISTRATION_FORM', 17 | 'registration.forms.RegistrationForm') 18 | REGISTRATION_FORM = import_string(REGISTRATION_FORM_PATH) 19 | ACCOUNT_AUTHENTICATED_REGISTRATION_REDIRECTS = getattr( 20 | settings, 'ACCOUNT_AUTHENTICATED_REGISTRATION_REDIRECTS', True) 21 | 22 | 23 | class RegistrationView(FormView): 24 | """ 25 | Base class for user registration views. 26 | 27 | """ 28 | disallowed_url = 'registration_disallowed' 29 | form_class = REGISTRATION_FORM 30 | http_method_names = ['get', 'post', 'head', 'options', 'trace'] 31 | success_url = None 32 | template_name = 'registration/registration_form.html' 33 | 34 | @method_decorator(sensitive_post_parameters('password1', 'password2')) 35 | def dispatch(self, request, *args, **kwargs): 36 | """ 37 | Check that user signup is allowed and if user is logged in before even bothering to 38 | dispatch or do other processing. 39 | 40 | """ 41 | if ACCOUNT_AUTHENTICATED_REGISTRATION_REDIRECTS: 42 | if self.request.user.is_authenticated: 43 | if settings.LOGIN_REDIRECT_URL is not None: 44 | return redirect(settings.LOGIN_REDIRECT_URL) 45 | else: 46 | raise Exception(( 47 | 'You must set a URL with LOGIN_REDIRECT_URL in ' 48 | 'settings.py or set ' 49 | 'ACCOUNT_AUTHENTICATED_REGISTRATION_REDIRECTS=False')) 50 | 51 | if not self.registration_allowed(): 52 | return redirect(self.disallowed_url) 53 | return super().dispatch(request, *args, **kwargs) 54 | 55 | def form_valid(self, form): 56 | new_user = self.register(form) 57 | success_url = self.get_success_url(new_user) 58 | 59 | if hasattr(self.request, "session"): 60 | self.request.session['registration_email'] = form.cleaned_data['email'] 61 | 62 | # success_url may be a simple string, or a tuple providing the 63 | # full argument set for redirect(). Attempting to unpack it 64 | # tells us which one it is. 65 | try: 66 | to, args, kwargs = success_url 67 | except ValueError: 68 | return redirect(success_url) 69 | else: 70 | return redirect(to, *args, **kwargs) 71 | 72 | def registration_allowed(self): 73 | """ 74 | Override this to enable/disable user registration, either 75 | globally or on a per-request basis. 76 | 77 | """ 78 | return True 79 | 80 | def register(self, form): 81 | """ 82 | Implement user-registration logic here. 83 | 84 | """ 85 | raise NotImplementedError 86 | 87 | def get_success_url(self, user=None): 88 | """ 89 | Use the new user when constructing success_url. 90 | 91 | """ 92 | return super().get_success_url() 93 | 94 | 95 | class ActivationView(TemplateView): 96 | """ 97 | Base class for user activation views. 98 | 99 | """ 100 | http_method_names = ['get'] 101 | template_name = 'registration/activate.html' 102 | 103 | def get(self, request, *args, **kwargs): 104 | activated_user = self.activate(*args, **kwargs) 105 | if activated_user: 106 | success_url = self.get_success_url(activated_user) 107 | try: 108 | to, args, kwargs = success_url 109 | except ValueError: 110 | return redirect(success_url) 111 | else: 112 | return redirect(to, *args, **kwargs) 113 | return super().get(request, *args, **kwargs) 114 | 115 | def activate(self, *args, **kwargs): 116 | """ 117 | Implement account-activation logic here. 118 | 119 | """ 120 | raise NotImplementedError 121 | 122 | def get_success_url(self, user): 123 | raise NotImplementedError 124 | 125 | 126 | class ResendActivationView(FormView): 127 | """ 128 | Base class for resending activation views. 129 | """ 130 | form_class = ResendActivationForm 131 | template_name = 'registration/resend_activation_form.html' 132 | 133 | def form_valid(self, form): 134 | """ 135 | Regardless if resend_activation is successful, display the same 136 | confirmation template. 137 | 138 | """ 139 | self.resend_activation(form) 140 | return self.render_form_submitted_template(form) 141 | 142 | def resend_activation(self, form): 143 | """ 144 | Implement resend activation key logic here. 145 | """ 146 | raise NotImplementedError 147 | 148 | def render_form_submitted_template(self, form): 149 | """ 150 | Implement rendering of confirmation template here. 151 | 152 | """ 153 | raise NotImplementedError 154 | 155 | 156 | class ApprovalView(TemplateView): 157 | 158 | http_method_names = ['get'] 159 | template_name = 'registration/admin_approve.html' 160 | 161 | def get(self, request, *args, **kwargs): 162 | approved_user = self.approve(*args, **kwargs) 163 | if approved_user: 164 | success_url = self.get_success_url(approved_user) 165 | try: 166 | to, args, kwargs = success_url 167 | except ValueError: 168 | return redirect(success_url) 169 | else: 170 | return redirect(to, *args, **kwargs) 171 | return super().get(request, *args, **kwargs) 172 | 173 | def approve(self, *args, **kwargs): 174 | """ 175 | Implement admin-approval logic here. 176 | 177 | """ 178 | raise NotImplementedError 179 | 180 | def get_success_url(self, user): 181 | raise NotImplementedError 182 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -rtest-requirements.txt 2 | Sphinx 3 | twine 4 | pytest>3.2.1 5 | Django>3.0 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | DJANGO_SETTINGS_MODULE=test_app.settings_test 3 | python_files=registration/tests/*.py 4 | 5 | [easy_install] 6 | zip_ok = False 7 | 8 | [flake8] 9 | exclude = docs,env,build,migrations,.eggs 10 | ignore = E501 11 | 12 | [isort] 13 | force_single_line = True 14 | forced_separate=registration 15 | known_first_party=django,mock 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from setuptools import find_packages 4 | from setuptools import setup 5 | from setuptools.command.test import test as TestCommand 6 | 7 | from registration import get_version 8 | 9 | 10 | class PyTest(TestCommand): 11 | def finalize_options(self): 12 | TestCommand.finalize_options(self) 13 | self.test_args = [] 14 | self.test_suite = True 15 | 16 | def run_tests(self): 17 | # import here, cause outside the eggs aren't loaded 18 | import pytest 19 | 20 | errno = pytest.main(self.test_args) 21 | sys.exit(errno) 22 | 23 | 24 | setup( 25 | name="django-registration-redux", 26 | version=get_version().replace(" ", "-"), 27 | description="An extensible user-registration application for Django", 28 | long_description=open("README.rst").read(), 29 | author="Andrew Cutler", 30 | author_email="macropin@gmail.com", 31 | url="https://github.com/macropin/django-registration", 32 | package_dir={"registration": "registration"}, 33 | packages=find_packages(exclude="test_app"), 34 | tests_require=["pytest-django"], 35 | cmdclass={"test": PyTest}, 36 | include_package_data=True, 37 | classifiers=[ 38 | "Development Status :: 5 - Production/Stable", 39 | "Environment :: Web Environment", 40 | "Framework :: Django", 41 | "Framework :: Django :: 4.2", 42 | "Framework :: Django :: 5.0", 43 | "Intended Audience :: Developers", 44 | "License :: OSI Approved :: BSD License", 45 | "Operating System :: OS Independent", 46 | "Programming Language :: Python", 47 | "Programming Language :: Python :: 3", 48 | "Programming Language :: Python :: 3.8", 49 | "Programming Language :: Python :: 3.9", 50 | "Programming Language :: Python :: 3.10", 51 | "Programming Language :: Python :: 3.11", 52 | "Programming Language :: Python :: 3.12", 53 | "Topic :: Software Development :: Libraries :: Python Modules", 54 | "Topic :: Utilities", 55 | ], 56 | ) 57 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | from invoke import run 2 | from invoke import task 3 | 4 | 5 | @task 6 | def clean(ctx, all=False): 7 | if all: 8 | flag = "--all" 9 | else: 10 | flag = "" 11 | run("python setup.py clean {}".format(flag)) 12 | 13 | 14 | @task 15 | def build(ctx, docs=False): 16 | run("python setup.py build") 17 | if docs: 18 | run("sphinx-build docs docs/_build") 19 | 20 | 21 | @task 22 | def test(ctx): 23 | run("python setup.py test") 24 | 25 | 26 | @task 27 | def lint(ctx): 28 | run("rst2html.py README.rst > /dev/null") 29 | run("flake8 registration") 30 | run("isort --recursive --check-only registration") 31 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | flake8 2 | coveralls 3 | isort==4.3.21 4 | setuptools 3 | 4 | 5 | 6 | Django Registration Test App 7 | 8 | 9 | 10 | 23 | 24 |
25 |

{% block title %}{% endblock %}

26 | {% block content %} 27 | 28 | {% endblock content %} 29 |
30 | 31 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test_app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}Django Registration Test App{% endblock %} 5 | 6 | {% block content %} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /test_app/templates/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}Account Profile{% endblock %} 5 | 6 | {% block content %} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /test_app/urls_admin_approval.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include 2 | from django.contrib import admin 3 | from django.contrib.auth import views as auth_views 4 | from django.urls import path 5 | from django.views.generic import TemplateView 6 | 7 | urlpatterns = [ 8 | 9 | path('', 10 | TemplateView.as_view(template_name='index.html'), 11 | name='index'), 12 | 13 | path('accounts/', 14 | include('registration.backends.admin_approval.urls')), 15 | 16 | path('accounts/profile/', 17 | TemplateView.as_view(template_name='profile.html'), 18 | name='profile'), 19 | 20 | path('login/', 21 | auth_views.LoginView.as_view( 22 | template_name='registration/login.html'), 23 | name='login'), 24 | 25 | path('admin/', 26 | admin.site.urls, 27 | name='admin'), 28 | ] 29 | -------------------------------------------------------------------------------- /test_app/urls_default.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include 2 | from django.contrib import admin 3 | from django.contrib.auth import views as auth_views 4 | from django.urls import path 5 | from django.views.generic import TemplateView 6 | 7 | urlpatterns = [ 8 | 9 | path('', 10 | TemplateView.as_view(template_name='index.html'), 11 | name='index'), 12 | 13 | path('accounts/', 14 | include('registration.backends.default.urls')), 15 | 16 | path('accounts/profile/', 17 | TemplateView.as_view(template_name='profile.html'), 18 | name='profile'), 19 | 20 | path('login/', 21 | auth_views.LoginView.as_view( 22 | template_name='registration/login.html'), 23 | name='login'), 24 | 25 | path('admin/', 26 | admin.site.urls, 27 | name='admin'), 28 | ] 29 | -------------------------------------------------------------------------------- /test_app/urls_simple.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include 2 | from django.contrib import admin 3 | from django.contrib.auth import views as auth_views 4 | from django.urls import path 5 | from django.views.generic import TemplateView 6 | 7 | urlpatterns = [ 8 | 9 | path('', 10 | TemplateView.as_view(template_name='index.html'), 11 | name='index'), 12 | 13 | path('accounts/', 14 | include('registration.backends.simple.urls')), 15 | 16 | path('accounts/profile/', 17 | TemplateView.as_view(template_name='profile.html'), 18 | name='profile'), 19 | 20 | path('login/', 21 | auth_views.LoginView.as_view( 22 | template_name='registration/login.html'), 23 | name='login'), 24 | 25 | path('admin/', 26 | admin.site.urls, 27 | name='admin'), 28 | ] 29 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (https://tox.readthedocs.io/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = 8 | py3{8,9,10,11}-django42, 9 | py3{10,11,12}-django50, 10 | skip_missing_interpreters=True 11 | [gh-actions] 12 | python = 13 | 3.8: py38 14 | 3.9: py39 15 | 3.10: py310 16 | 3.11: py311 17 | 3.12: py312 18 | 19 | [testenv] 20 | commands = 21 | coverage run --source=registration setup.py test 22 | deps = 23 | -rtest-requirements.txt 24 | django42: Django>=4.2,<4.3 25 | django50: Django>=5.0,<5.1 26 | --------------------------------------------------------------------------------