django-pfx 1.4.dev22__tar.gz → 1.4.dev24__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/PKG-INFO +1 -1
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/django_pfx.egg-info/PKG-INFO +1 -1
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/django_pfx.egg-info/SOURCES.txt +2 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/default_settings.py +1 -0
- django-pfx-1.4.dev24/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +89 -24
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/otp_user_mixin.py +27 -2
- django-pfx-1.4.dev24/pfx/pfxcore/templates/registration/otp_code_email.txt +12 -0
- django-pfx-1.4.dev24/pfx/pfxcore/templates/registration/otp_code_subject.txt +3 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/urls.py +2 -1
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/__init__.py +1 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/authentication_views.py +108 -5
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_auth_api.py +116 -0
- django-pfx-1.4.dev22/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/.gitignore +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/.gitlab-ci.yml +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/.pre-commit-config.yaml +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/LICENSE +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/MANIFEST.in +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/README.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/django-admin-test +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/django_pfx.egg-info/requires.txt +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/django_pfx.egg-info/top_level.txt +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/Makefile +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/conf.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/index.rst +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/api.views.rst +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/authentication.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/decorator.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/generate_openapi.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/getting_started.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/internationalisation.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/model.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/pfx_views.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/profiling.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/settings.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/doc/source/testing.md +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/img/pfx.png +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/img/pfx.svg +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/apidoc/parameters.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/apps.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/decorator/rest.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/exceptions.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/fields.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/http/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/http/json_response.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/management/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/management/commands/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/middleware/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/middleware/locale.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/login_ban.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/pfx_models.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/serializers/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/serializers/json.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/settings.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/shortcuts.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/storage/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/test.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/fields.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/filters_views.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/locale_views.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/groups.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/rest_views.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pyproject.toml +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/requirements.txt +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/runtest.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/serve-doc +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/setup.cfg +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/setup.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/models.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/settings/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/settings/ci.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/settings/common.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/settings/dev.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/settings/dev_custom_example.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/settings/dev_default.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/__init__.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/basic_api_errors.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/basic_api_test.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_api_doc.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_body_mixin.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_cache.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_client.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_fields.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_filters.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_locale_api.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_perm_tests.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_perms_api.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_profiling_middleware.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_shortcuts.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_timezone_middleware.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_tools.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_user_queryset.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_view_decorators.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/tests/test_view_fields.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/urls.py +0 -0
- {django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/tests/views.py +0 -0
|
@@ -72,6 +72,8 @@ pfx/pfxcore/serializers/__init__.py
|
|
|
72
72
|
pfx/pfxcore/serializers/json.py
|
|
73
73
|
pfx/pfxcore/storage/__init__.py
|
|
74
74
|
pfx/pfxcore/storage/s3_storage.py
|
|
75
|
+
pfx/pfxcore/templates/registration/otp_code_email.txt
|
|
76
|
+
pfx/pfxcore/templates/registration/otp_code_subject.txt
|
|
75
77
|
pfx/pfxcore/templates/registration/password_reset_email.txt
|
|
76
78
|
pfx/pfxcore/templates/registration/password_reset_subject.txt
|
|
77
79
|
pfx/pfxcore/templates/registration/welcome_email.txt
|
|
Binary file
|
|
@@ -7,7 +7,7 @@ msgid ""
|
|
|
7
7
|
msgstr ""
|
|
8
8
|
"Project-Id-Version: \n"
|
|
9
9
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date: 2024-
|
|
10
|
+
"POT-Creation-Date: 2024-04-08 13:51+0200\n"
|
|
11
11
|
"PO-Revision-Date: 2021-06-22 23:31+0200\n"
|
|
12
12
|
"Last-Translator: \n"
|
|
13
13
|
"Language-Team: \n"
|
|
@@ -59,14 +59,42 @@ msgstr ""
|
|
|
59
59
|
"Format non valide, il peut s’agir d’un nombre en heures, « 1:05 », « :05 », "
|
|
60
60
|
"« 1h 5m », « 1.5h » ou « 30m »."
|
|
61
61
|
|
|
62
|
-
#: models/
|
|
62
|
+
#: models/login_ban.py:45
|
|
63
|
+
msgid "Username"
|
|
64
|
+
msgstr "Nom d’utilisateur"
|
|
65
|
+
|
|
66
|
+
#: models/login_ban.py:46
|
|
67
|
+
msgid "Failed counter"
|
|
68
|
+
msgstr "Compteur d’échec"
|
|
69
|
+
|
|
70
|
+
#: models/login_ban.py:47
|
|
71
|
+
msgid "Last failed"
|
|
72
|
+
msgstr "Dernier échec"
|
|
73
|
+
|
|
74
|
+
#: models/login_ban.py:52
|
|
75
|
+
msgid "Login ban"
|
|
76
|
+
msgstr "Login banni"
|
|
77
|
+
|
|
78
|
+
#: models/login_ban.py:53
|
|
79
|
+
msgid "Login bans"
|
|
80
|
+
msgstr "Login bannis"
|
|
81
|
+
|
|
82
|
+
#: models/otp_user_mixin.py:10
|
|
63
83
|
msgid "OTP secret token"
|
|
64
84
|
msgstr "Jeton secret OTP"
|
|
65
85
|
|
|
66
|
-
#: models/otp_user_mixin.py:
|
|
86
|
+
#: models/otp_user_mixin.py:13
|
|
67
87
|
msgid "Temporary OTP secret token"
|
|
68
88
|
msgstr "Jeton secret OTP temporaire"
|
|
69
89
|
|
|
90
|
+
#: models/otp_user_mixin.py:14
|
|
91
|
+
msgid "HOTP count"
|
|
92
|
+
msgstr "Compte HOTP"
|
|
93
|
+
|
|
94
|
+
#: models/otp_user_mixin.py:15
|
|
95
|
+
msgid "HOTP expiry"
|
|
96
|
+
msgstr "Expiration HOTP"
|
|
97
|
+
|
|
70
98
|
#: models/pfx_models.py:14
|
|
71
99
|
#, python-format
|
|
72
100
|
msgid "%(model_name)s with this %(field_labels)s already exists."
|
|
@@ -92,6 +120,41 @@ msgstr "{key} doit être une date."
|
|
|
92
120
|
msgid "{key} must be “true”, “false”, “1”, “0” or empty."
|
|
93
121
|
msgstr "{key} doit être « true », « false », « 1 », « 0 » ou vide"
|
|
94
122
|
|
|
123
|
+
#: templates/registration/otp_code_email.txt:2
|
|
124
|
+
#, python-format
|
|
125
|
+
msgid ""
|
|
126
|
+
"You're receiving this email because you requested an authentication code at "
|
|
127
|
+
"%(site_name)s."
|
|
128
|
+
msgstr ""
|
|
129
|
+
"Vous recevez cet e-mail parce que vous avez demandé un code d’authentication "
|
|
130
|
+
"sur %(site_name)s."
|
|
131
|
+
|
|
132
|
+
#: templates/registration/otp_code_email.txt:4
|
|
133
|
+
msgid "Authentication code:"
|
|
134
|
+
msgstr "Code d’authentication :"
|
|
135
|
+
|
|
136
|
+
#: templates/registration/otp_code_email.txt:6
|
|
137
|
+
#, python-format
|
|
138
|
+
msgid "This code is valid for %(otp_validity)s minutes."
|
|
139
|
+
msgstr "Ce code est valable durant %(otp_validity)s minutes."
|
|
140
|
+
|
|
141
|
+
#: templates/registration/otp_code_email.txt:8
|
|
142
|
+
#: templates/registration/password_reset_email.txt:10
|
|
143
|
+
#: templates/registration/welcome_email.txt:10
|
|
144
|
+
msgid "Thanks for using our site!"
|
|
145
|
+
msgstr "Merci d'utiliser notre site !"
|
|
146
|
+
|
|
147
|
+
#: templates/registration/otp_code_email.txt:10
|
|
148
|
+
#: templates/registration/password_reset_email.txt:12
|
|
149
|
+
#: templates/registration/welcome_email.txt:12
|
|
150
|
+
#, python-format
|
|
151
|
+
msgid "The %(site_name)s team"
|
|
152
|
+
msgstr "L'équipe de %(site_name)s"
|
|
153
|
+
|
|
154
|
+
#: templates/registration/otp_code_subject.txt:2
|
|
155
|
+
msgid "New authentication code for %(site_name)s"
|
|
156
|
+
msgstr "Nouveau code d’authentication pour %(site_name)s"
|
|
157
|
+
|
|
95
158
|
#: templates/registration/password_reset_email.txt:2
|
|
96
159
|
#, python-format
|
|
97
160
|
msgid ""
|
|
@@ -113,17 +176,6 @@ msgstr ""
|
|
|
113
176
|
msgid "Your username, in case you've forgotten:"
|
|
114
177
|
msgstr "Votre nom d'utilisateur, au cas où vous l'auriez oublié :"
|
|
115
178
|
|
|
116
|
-
#: templates/registration/password_reset_email.txt:10
|
|
117
|
-
#: templates/registration/welcome_email.txt:10
|
|
118
|
-
msgid "Thanks for using our site!"
|
|
119
|
-
msgstr "Merci d'utiliser notre site !"
|
|
120
|
-
|
|
121
|
-
#: templates/registration/password_reset_email.txt:12
|
|
122
|
-
#: templates/registration/welcome_email.txt:12
|
|
123
|
-
#, python-format
|
|
124
|
-
msgid "The %(site_name)s team"
|
|
125
|
-
msgstr "L'équipe de %(site_name)s"
|
|
126
|
-
|
|
127
179
|
#: templates/registration/password_reset_subject.txt:2
|
|
128
180
|
#, python-format
|
|
129
181
|
msgid "Password reset on %(site_name)s"
|
|
@@ -139,43 +191,52 @@ msgstr "Bienvenue sur %(site_name)s."
|
|
|
139
191
|
msgid "Welcome on %(site_name)s"
|
|
140
192
|
msgstr "Bienvenue sur %(site_name)s"
|
|
141
193
|
|
|
142
|
-
#: views/authentication_views.py:
|
|
194
|
+
#: views/authentication_views.py:82
|
|
195
|
+
#, python-brace-format
|
|
196
|
+
msgid ""
|
|
197
|
+
"Your connection is temporarily disabled after several unsuccessful attempts, "
|
|
198
|
+
"please retry in {seconds} seconds."
|
|
199
|
+
msgstr ""
|
|
200
|
+
"Votre connexion est temporairement désactivée après plusieurs tentatives "
|
|
201
|
+
"infructueuses, veuillez réessayer dans {seconds} secondes."
|
|
202
|
+
|
|
203
|
+
#: views/authentication_views.py:244 views/authentication_views.py:407
|
|
143
204
|
msgid "password updated successfully"
|
|
144
205
|
msgstr "le mot de passe a été mis à jour avec succès"
|
|
145
206
|
|
|
146
|
-
#: views/authentication_views.py:
|
|
207
|
+
#: views/authentication_views.py:249
|
|
147
208
|
msgid "Incorrect password"
|
|
148
209
|
msgstr "Mot de passe incorrect"
|
|
149
210
|
|
|
150
|
-
#: views/authentication_views.py:
|
|
211
|
+
#: views/authentication_views.py:252 views/authentication_views.py:416
|
|
151
212
|
msgid "Empty password is not allowed"
|
|
152
213
|
msgstr "Un mot de passe vide n’est pas autorisé"
|
|
153
214
|
|
|
154
|
-
#: views/authentication_views.py:
|
|
215
|
+
#: views/authentication_views.py:341
|
|
155
216
|
msgid "User and token are valid"
|
|
156
217
|
msgstr "L'utilisateur et le token sont valides"
|
|
157
218
|
|
|
158
|
-
#: views/authentication_views.py:
|
|
219
|
+
#: views/authentication_views.py:343
|
|
159
220
|
msgid "User or token is invalid"
|
|
160
221
|
msgstr "L'utilisateur ou le token est invalide"
|
|
161
222
|
|
|
162
|
-
#: views/authentication_views.py:
|
|
223
|
+
#: views/authentication_views.py:449
|
|
163
224
|
msgid "OTP is already activated"
|
|
164
225
|
msgstr "L'OTP est déjà activé"
|
|
165
226
|
|
|
166
|
-
#: views/authentication_views.py:
|
|
227
|
+
#: views/authentication_views.py:487
|
|
167
228
|
msgid "OTP is activated"
|
|
168
229
|
msgstr "L'OTP est activé"
|
|
169
230
|
|
|
170
|
-
#: views/authentication_views.py:
|
|
231
|
+
#: views/authentication_views.py:488 views/authentication_views.py:522
|
|
171
232
|
msgid "Invalid OTP code"
|
|
172
233
|
msgstr "Code OTP invalide"
|
|
173
234
|
|
|
174
|
-
#: views/authentication_views.py:
|
|
235
|
+
#: views/authentication_views.py:521
|
|
175
236
|
msgid "OTP is disabled"
|
|
176
237
|
msgstr "L'OTP est désactivé"
|
|
177
238
|
|
|
178
|
-
#: views/authentication_views.py:
|
|
239
|
+
#: views/authentication_views.py:777
|
|
179
240
|
msgid ""
|
|
180
241
|
"If the email address you entered is correct, you will receive an email from "
|
|
181
242
|
"us with instructions to reset your password."
|
|
@@ -184,6 +245,10 @@ msgstr ""
|
|
|
184
245
|
"un courrier électronique de notre part contenant des instructions pour "
|
|
185
246
|
"réinitialiser votre mot de passe."
|
|
186
247
|
|
|
248
|
+
#: views/authentication_views.py:845
|
|
249
|
+
msgid "A new authentication code has been sent by email."
|
|
250
|
+
msgstr "Un nouveau code d'authentification a été envoyé par e-mail."
|
|
251
|
+
|
|
187
252
|
#: views/filters_views.py:79
|
|
188
253
|
#, python-brace-format
|
|
189
254
|
msgid "Invalid value for {filter} filter"
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
from
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
2
3
|
from django.db import models
|
|
4
|
+
from django.utils import timezone
|
|
3
5
|
from django.utils.translation import gettext_lazy as _
|
|
4
6
|
|
|
7
|
+
from pfx.pfxcore.settings import PFXSettings
|
|
8
|
+
|
|
9
|
+
settings = PFXSettings()
|
|
10
|
+
|
|
5
11
|
|
|
6
12
|
class OtpUserMixin(models.Model):
|
|
7
13
|
otp_secret_token = models.CharField(
|
|
@@ -9,6 +15,8 @@ class OtpUserMixin(models.Model):
|
|
|
9
15
|
blank=True, unique=True)
|
|
10
16
|
otp_secret_token_tmp = models.CharField(
|
|
11
17
|
_("Temporary OTP secret token"), max_length=32, null=True, blank=True)
|
|
18
|
+
hotp_count = models.IntegerField(_("HOTP count"), default=0)
|
|
19
|
+
hotp_expiry = models.DateTimeField(_("HOTP expiry"), default=timezone.now)
|
|
12
20
|
|
|
13
21
|
class Meta:
|
|
14
22
|
abstract = True
|
|
@@ -41,8 +49,25 @@ class OtpUserMixin(models.Model):
|
|
|
41
49
|
def is_otp_valid(self, otp_code, tmp=False):
|
|
42
50
|
import pyotp
|
|
43
51
|
totp = pyotp.parse_uri(self.get_otp_setup_uri(tmp=tmp))
|
|
44
|
-
|
|
52
|
+
valid = totp.verify(otp_code)
|
|
53
|
+
if not valid and timezone.now() <= self.hotp_expiry:
|
|
54
|
+
hotp = pyotp.hotp.HOTP(
|
|
55
|
+
tmp and self.otp_secret_token_tmp or
|
|
56
|
+
self.otp_secret_token)
|
|
57
|
+
return hotp.verify(otp_code, self.hotp_count)
|
|
58
|
+
return valid
|
|
45
59
|
|
|
46
60
|
def get_user_jwt_signature_key(self):
|
|
47
61
|
return super().get_user_jwt_signature_key() + (
|
|
48
62
|
self.otp_secret_token or "")
|
|
63
|
+
|
|
64
|
+
def get_hotp_code(self):
|
|
65
|
+
import pyotp
|
|
66
|
+
if not self.otp_secret_token:
|
|
67
|
+
raise Exception("OTP disabled")
|
|
68
|
+
self.hotp_count += 1
|
|
69
|
+
self.hotp_expiry = timezone.now() + timedelta(
|
|
70
|
+
minutes=settings.PFX_HOTP_CODE_VALIDITY)
|
|
71
|
+
self.save(update_fields=[
|
|
72
|
+
'hotp_count', 'hotp_expiry'])
|
|
73
|
+
return pyotp.hotp.HOTP(self.otp_secret_token).at(self.hotp_count)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{% load i18n %}{% autoescape off %}
|
|
2
|
+
{% blocktrans %}You're receiving this email because you requested an authentication code at {{ site_name }}.{% endblocktrans %}
|
|
3
|
+
|
|
4
|
+
{% trans "Authentication code:" %} {{ otp_code }}
|
|
5
|
+
|
|
6
|
+
{% blocktrans %}This code is valid for {{ otp_validity }} minutes.{% endblocktrans %}
|
|
7
|
+
|
|
8
|
+
{% trans "Thanks for using our site!" %}
|
|
9
|
+
|
|
10
|
+
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
|
11
|
+
|
|
12
|
+
{% endautoescape %}
|
|
@@ -417,7 +417,7 @@ class AuthenticationView(
|
|
|
417
417
|
raise UnauthorizedError()
|
|
418
418
|
|
|
419
419
|
@method_decorator(never_cache)
|
|
420
|
-
@rest_api("/otp/activate", public=False, method="put")
|
|
420
|
+
@rest_api("/otp/activate", public=False, method="put", priority_doc=52)
|
|
421
421
|
def otp_activate(self, *args, **kwargs):
|
|
422
422
|
"""Entrypoint for :code:`PUT /otp/activate` route.
|
|
423
423
|
|
|
@@ -453,7 +453,7 @@ class AuthenticationView(
|
|
|
453
453
|
|
|
454
454
|
@method_decorator(never_cache)
|
|
455
455
|
@rest_api(
|
|
456
|
-
"/otp/confirm", public=False, method="put",
|
|
456
|
+
"/otp/confirm", public=False, method="put", priority_doc=53,
|
|
457
457
|
response_schema='message_schema')
|
|
458
458
|
def otp_confirm(self, *args, **kwargs):
|
|
459
459
|
"""Entrypoint for :code:`PUT /otp/confirm` route.
|
|
@@ -488,7 +488,7 @@ class AuthenticationView(
|
|
|
488
488
|
return JsonResponse(dict(message=_("Invalid OTP code")), status=422)
|
|
489
489
|
|
|
490
490
|
@method_decorator(never_cache)
|
|
491
|
-
@rest_api("/otp/disable", public=False, method="put")
|
|
491
|
+
@rest_api("/otp/disable", public=False, method="put", priority_doc=54)
|
|
492
492
|
def otp_disable(self, *args, **kwargs):
|
|
493
493
|
"""Entrypoint for :code:`PUT /otp/disable` route.
|
|
494
494
|
|
|
@@ -523,7 +523,7 @@ class AuthenticationView(
|
|
|
523
523
|
|
|
524
524
|
@method_decorator(never_cache)
|
|
525
525
|
@rest_api(
|
|
526
|
-
"/otp/login", public=True, method="post",
|
|
526
|
+
"/otp/login", public=True, method="post", priority_doc=50,
|
|
527
527
|
response_schema='login_schema')
|
|
528
528
|
def otp_login(self, *args, **kwargs):
|
|
529
529
|
"""Entrypoint for :code:`PUT /otp/login` route.
|
|
@@ -557,7 +557,6 @@ class AuthenticationView(
|
|
|
557
557
|
description: If the token is missing, invalid or expired.
|
|
558
558
|
403:
|
|
559
559
|
description: If the OTP is disabled for this user.
|
|
560
|
-
|
|
561
560
|
"""
|
|
562
561
|
data = self.deserialize_body()
|
|
563
562
|
token = data.get('token')
|
|
@@ -779,3 +778,107 @@ class ForgottenPasswordView(SendMessageTokenMixin, BodyMixin, BaseRestView):
|
|
|
779
778
|
'you will receive an email from us with '
|
|
780
779
|
'instructions to reset your password.')
|
|
781
780
|
})
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
@rest_view("/auth/otp")
|
|
784
|
+
class OtpEmailView(BodyMixin, JWTTokenDecodeMixin, BaseRestView):
|
|
785
|
+
"""View for forgotten password service."""
|
|
786
|
+
#: The email template.
|
|
787
|
+
email_template_name = 'registration/otp_code_email.txt'
|
|
788
|
+
#: The email subject template.
|
|
789
|
+
subject_template_name = 'registration/otp_code_subject.txt'
|
|
790
|
+
#: The HTML email template.
|
|
791
|
+
html_email_template_name = None
|
|
792
|
+
#: Extra email context.
|
|
793
|
+
extra_email_context = None
|
|
794
|
+
#: The from value of the email.
|
|
795
|
+
from_email = None
|
|
796
|
+
#: The email field of user model.
|
|
797
|
+
email_field = 'email'
|
|
798
|
+
#: The language field of user model
|
|
799
|
+
language_field = 'language'
|
|
800
|
+
#: The ApiDoc tags.
|
|
801
|
+
tags = [AUTHENTICATION_TAG]
|
|
802
|
+
|
|
803
|
+
@method_decorator(never_cache)
|
|
804
|
+
@rest_api(
|
|
805
|
+
"/email", public=True, method="post", priority_doc=51,
|
|
806
|
+
response_schema='message_schema')
|
|
807
|
+
def sent_email(self, *args, **kwargs):
|
|
808
|
+
"""Entrypoint for :code:`POST /email` route.
|
|
809
|
+
|
|
810
|
+
Request a new OTP code by email.
|
|
811
|
+
|
|
812
|
+
:returns: The JSON response
|
|
813
|
+
:rtype: :class:`JsonResponse`
|
|
814
|
+
---
|
|
815
|
+
post:
|
|
816
|
+
summary: Send OTP email
|
|
817
|
+
description: Request a new OTP code by email.
|
|
818
|
+
requestBody:
|
|
819
|
+
content:
|
|
820
|
+
application/json:
|
|
821
|
+
schema:
|
|
822
|
+
properties:
|
|
823
|
+
token:
|
|
824
|
+
type: string
|
|
825
|
+
description: a valid JWT user token. This
|
|
826
|
+
is required only if the user is not
|
|
827
|
+
already connected (with a Bearer token
|
|
828
|
+
or a Cookie).
|
|
829
|
+
responses:
|
|
830
|
+
401:
|
|
831
|
+
description: If the token is missing, invalid or expired.
|
|
832
|
+
403:
|
|
833
|
+
description: If the OTP is disabled for this user.
|
|
834
|
+
"""
|
|
835
|
+
if self.request.user.is_anonymous:
|
|
836
|
+
data = self.deserialize_body()
|
|
837
|
+
try:
|
|
838
|
+
user, __, __ = self.decode_jwt(
|
|
839
|
+
data.get('token'), otp_login=True)
|
|
840
|
+
except Exception as e:
|
|
841
|
+
logger.exception(e)
|
|
842
|
+
raise UnauthorizedError()
|
|
843
|
+
else:
|
|
844
|
+
user = self.request.user
|
|
845
|
+
if not isinstance(user, OtpUserMixin):
|
|
846
|
+
logger.error("User must inherit OtpUserMixin to activate OTP")
|
|
847
|
+
raise NotFoundError()
|
|
848
|
+
if not user.otp_secret_token:
|
|
849
|
+
raise ForbiddenError()
|
|
850
|
+
self.send_otp_message(user)
|
|
851
|
+
return JsonResponse({
|
|
852
|
+
'message': _('A new authentication code has been sent by email.')})
|
|
853
|
+
|
|
854
|
+
def send_otp_message(self, user):
|
|
855
|
+
"""Send an email to a user with an OTP code.
|
|
856
|
+
|
|
857
|
+
:param user: The user
|
|
858
|
+
"""
|
|
859
|
+
from django.utils import translation
|
|
860
|
+
lang = str(getattr(user, self.language_field, settings.LANGUAGE_CODE))
|
|
861
|
+
|
|
862
|
+
otp_code = user.get_hotp_code()
|
|
863
|
+
data = {
|
|
864
|
+
'target_user': user,
|
|
865
|
+
'otp_code': otp_code,
|
|
866
|
+
'otp_validity': settings.PFX_HOTP_CODE_VALIDITY,
|
|
867
|
+
'site_name': settings.PFX_SITE_NAME,
|
|
868
|
+
'user': user,
|
|
869
|
+
**(self.extra_email_context or {})
|
|
870
|
+
}
|
|
871
|
+
with translation.override(lang):
|
|
872
|
+
subject = loader.render_to_string(self.subject_template_name, data)
|
|
873
|
+
# Email subject *must not* contain newlines
|
|
874
|
+
subject = ''.join(subject.splitlines())
|
|
875
|
+
body = loader.render_to_string(self.email_template_name, data)
|
|
876
|
+
email_message = EmailMultiAlternatives(
|
|
877
|
+
subject, body, self.from_email,
|
|
878
|
+
[getattr(user, self.email_field)])
|
|
879
|
+
if self.html_email_template_name is not None:
|
|
880
|
+
html_email = loader.render_to_string(
|
|
881
|
+
self.html_email_template_name, data)
|
|
882
|
+
email_message.attach_alternative(
|
|
883
|
+
html_email, 'text/html')
|
|
884
|
+
email_message.send()
|
|
@@ -944,6 +944,34 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
|
|
|
944
944
|
self.assertIsNone(self.user1.otp_secret_token)
|
|
945
945
|
self.assertIsNone(self.user1.otp_secret_token_tmp)
|
|
946
946
|
|
|
947
|
+
@override_settings(
|
|
948
|
+
PFX_HOTP_CODE_VALIDITY=15,
|
|
949
|
+
PFX_TOKEN_OTP_VALIDITY={'minutes': 30},)
|
|
950
|
+
def test_otp_disable_by_email(self):
|
|
951
|
+
self.enable_otp(self.user1)
|
|
952
|
+
|
|
953
|
+
self.otp_login(self.user1, 'RIGHT PASSWORD')
|
|
954
|
+
|
|
955
|
+
with freeze_time("2023-05-01 08:00:00"):
|
|
956
|
+
response = self.client.post('/api/auth/otp/email', dict())
|
|
957
|
+
self.assertRC(response, 200)
|
|
958
|
+
|
|
959
|
+
self.assertEqual(
|
|
960
|
+
mail.outbox[0].subject,
|
|
961
|
+
f'New authentication code for {settings.PFX_SITE_NAME}')
|
|
962
|
+
code_match = re.search(
|
|
963
|
+
r'Authentication code: (\d{6})', mail.outbox[0].body)
|
|
964
|
+
self.assertIsNotNone(code_match)
|
|
965
|
+
otp_code = int(code_match.group(1))
|
|
966
|
+
|
|
967
|
+
response = self.client.put('/api/auth/otp/disable', dict(
|
|
968
|
+
otp_code=otp_code))
|
|
969
|
+
self.assertRC(response, 200)
|
|
970
|
+
self.assertJE(response, 'message', "OTP is disabled")
|
|
971
|
+
self.user1.refresh_from_db()
|
|
972
|
+
self.assertIsNone(self.user1.otp_secret_token)
|
|
973
|
+
self.assertIsNone(self.user1.otp_secret_token_tmp)
|
|
974
|
+
|
|
947
975
|
def test_otp_disable_bad_code(self):
|
|
948
976
|
self.enable_otp(self.user1)
|
|
949
977
|
|
|
@@ -1195,3 +1223,91 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
|
|
|
1195
1223
|
'/api/private/authors',
|
|
1196
1224
|
HTTP_AUTHORIZATION='Bearer ' + login_token)
|
|
1197
1225
|
self.assertRC(response, 200)
|
|
1226
|
+
|
|
1227
|
+
@override_settings(PFX_HOTP_CODE_VALIDITY=15)
|
|
1228
|
+
def test_send_otp_email(self):
|
|
1229
|
+
self.enable_otp(self.user1)
|
|
1230
|
+
|
|
1231
|
+
with freeze_time("2023-05-01 08:00:00"):
|
|
1232
|
+
response = self.client.post('/api/auth/login', dict(
|
|
1233
|
+
username='jrr.tolkien',
|
|
1234
|
+
password='RIGHT PASSWORD'))
|
|
1235
|
+
self.assertRC(response, 200)
|
|
1236
|
+
self.assertJE(response, 'need_otp', True)
|
|
1237
|
+
self.assertJEExists(response, 'token')
|
|
1238
|
+
otp_token = self.get_val(response, 'token')
|
|
1239
|
+
|
|
1240
|
+
response = self.client.post('/api/auth/otp/email', dict(
|
|
1241
|
+
token=otp_token))
|
|
1242
|
+
self.assertRC(response, 200)
|
|
1243
|
+
|
|
1244
|
+
self.assertEqual(
|
|
1245
|
+
mail.outbox[0].subject,
|
|
1246
|
+
f'New authentication code for {settings.PFX_SITE_NAME}')
|
|
1247
|
+
code_match = re.search(
|
|
1248
|
+
r'Authentication code: (\d{6})', mail.outbox[0].body)
|
|
1249
|
+
self.assertIsNotNone(code_match)
|
|
1250
|
+
otp_code = int(code_match.group(1))
|
|
1251
|
+
|
|
1252
|
+
response = self.client.post('/api/auth/otp/login', dict(
|
|
1253
|
+
token=otp_token,
|
|
1254
|
+
otp_code=otp_code))
|
|
1255
|
+
self.assertRC(response, 200)
|
|
1256
|
+
self.assertJEExists(response, 'token')
|
|
1257
|
+
login_token = self.get_val(response, 'token')
|
|
1258
|
+
|
|
1259
|
+
# Login token is accepted
|
|
1260
|
+
response = self.client.get(
|
|
1261
|
+
'/api/private/authors',
|
|
1262
|
+
HTTP_AUTHORIZATION='Bearer ' + login_token)
|
|
1263
|
+
self.assertRC(response, 200)
|
|
1264
|
+
|
|
1265
|
+
@override_settings(
|
|
1266
|
+
PFX_HOTP_CODE_VALIDITY=15,
|
|
1267
|
+
PFX_TOKEN_OTP_VALIDITY={'minutes': 30},)
|
|
1268
|
+
def test_send_otp_email_expiry(self):
|
|
1269
|
+
self.enable_otp(self.user1)
|
|
1270
|
+
|
|
1271
|
+
with freeze_time("2023-05-01 08:00:00"):
|
|
1272
|
+
response = self.client.post('/api/auth/login', dict(
|
|
1273
|
+
username='jrr.tolkien',
|
|
1274
|
+
password='RIGHT PASSWORD'))
|
|
1275
|
+
self.assertRC(response, 200)
|
|
1276
|
+
self.assertJE(response, 'need_otp', True)
|
|
1277
|
+
self.assertJEExists(response, 'token')
|
|
1278
|
+
otp_token = self.get_val(response, 'token')
|
|
1279
|
+
|
|
1280
|
+
response = self.client.post('/api/auth/otp/email', dict(
|
|
1281
|
+
token=otp_token))
|
|
1282
|
+
self.assertRC(response, 200)
|
|
1283
|
+
|
|
1284
|
+
self.assertEqual(
|
|
1285
|
+
mail.outbox[0].subject,
|
|
1286
|
+
f'New authentication code for {settings.PFX_SITE_NAME}')
|
|
1287
|
+
code_match = re.search(
|
|
1288
|
+
r'Authentication code: (\d{6})', mail.outbox[0].body)
|
|
1289
|
+
self.assertIsNotNone(code_match)
|
|
1290
|
+
otp_code = int(code_match.group(1))
|
|
1291
|
+
|
|
1292
|
+
with freeze_time("2023-05-01 08:15:01"):
|
|
1293
|
+
response = self.client.post('/api/auth/otp/login', dict(
|
|
1294
|
+
token=otp_token,
|
|
1295
|
+
otp_code=otp_code))
|
|
1296
|
+
self.assertRC(response, 422)
|
|
1297
|
+
|
|
1298
|
+
with freeze_time("2023-05-01 08:15:00"):
|
|
1299
|
+
response = self.client.post('/api/auth/otp/login', dict(
|
|
1300
|
+
token=otp_token,
|
|
1301
|
+
otp_code=otp_code))
|
|
1302
|
+
self.assertRC(response, 200)
|
|
1303
|
+
self.assertJEExists(response, 'token')
|
|
1304
|
+
|
|
1305
|
+
@override_settings(
|
|
1306
|
+
PFX_HOTP_CODE_VALIDITY=15,
|
|
1307
|
+
PFX_TOKEN_OTP_VALIDITY={'minutes': 30},)
|
|
1308
|
+
def test_send_otp_email_without_token(self):
|
|
1309
|
+
self.enable_otp(self.user1)
|
|
1310
|
+
|
|
1311
|
+
with freeze_time("2023-05-01 08:00:00"):
|
|
1312
|
+
response = self.client.post('/api/auth/otp/email', dict())
|
|
1313
|
+
self.assertRC(response, 401)
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/models/user_filtered_queryset_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/templates/registration/welcome_email.txt
RENAMED
|
File without changes
|
{django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/templates/registration/welcome_subject.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/media_redirect.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/subset_page_size.py
RENAMED
|
File without changes
|
{django-pfx-1.4.dev22 → django-pfx-1.4.dev24}/pfx/pfxcore/views/parameters/subset_page_subset.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|