Compare commits

..

No commits in common. "master" and "0.7.0" have entirely different histories.

55 changed files with 393 additions and 2413 deletions

76
.gitignore vendored
View File

@ -1,35 +1,26 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
@ -39,66 +30,31 @@ pip-delete-this-directory.txt
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
coverage_html
# Translations
*.mo
*.pot
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
*.pot
# Sphinx documentation
docs/_build/
# PyBuilder
target/
.DS_Store
db.sqlite3
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# IntelliJ IDE files
.idea

View File

@ -1,15 +1,12 @@
language: python
python:
- "2.7"
- "3.5"
- "3.6"
env:
- DJANGO=1.11.* DRF=3.7.*
- DJANGO=1.11.* DRF=3.8.*
- DJANGO=2.0.* DRF=3.7.*
- DJANGO=2.0.* DRF=3.8.*
- DJANGO=1.7.11
- DJANGO=1.8.9
- DJANGO=1.9.2
install:
- pip install -q Django==$DJANGO djangorestframework==$DRF
- pip install -q Django==$DJANGO --use-mirrors
- pip install coveralls
- pip install -r rest_auth/tests/requirements.pip
script:
@ -18,9 +15,3 @@ after_success:
- coveralls
before_script:
- flake8 . --config=flake8
matrix:
exclude:
- python: "2.7"
env: DJANGO=2.0.* DRF=3.7.*
- python: "2.7"
env: DJANGO=2.0.* DRF=3.8.*

View File

@ -2,4 +2,3 @@ include AUTHORS
include LICENSE
include MANIFEST.in
include README.md
graft rest_auth

View File

@ -1,17 +1,11 @@
===========
Deprecated
===========
Please use https://github.com/iMerica/dj-rest-auth as this project is no longer maintained. Thanks!
Welcome to django-rest-auth
===========================
.. image:: https://travis-ci.org/Tivix/django-rest-auth.svg
.. image:: https://travis-ci.org/Tivix/django-rest-auth.png
:target: https://travis-ci.org/Tivix/django-rest-auth
.. image:: https://coveralls.io/repos/Tivix/django-rest-auth/badge.svg
.. image:: https://coveralls.io/repos/Tivix/django-rest-auth/badge.png
:target: https://coveralls.io/r/Tivix/django-rest-auth?branch=master
@ -34,4 +28,4 @@ https://github.com/Tivix/django-rest-auth
Stack Overflow
-----------
http://stackoverflow.com/questions/tagged/django-rest-auth
http://stackoverflow.com/questions/tagged/django-rest-auth

View File

@ -10,9 +10,9 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
@ -22,8 +22,19 @@ SECRET_KEY = 'ma3c@7uu!%e0=tynp+i6+q%$)9v@$t(eulqurym_b=48z82&5n'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
"django.core.context_processors.request",
# Disabling due to alluth>=0.21.0 changes
# "allauth.account.context_processors.account",
# "allauth.socialaccount.context_processors.socialaccount",
)
# Application definition
INSTALLED_APPS = (
@ -44,25 +55,23 @@ INSTALLED_APPS = (
'rest_auth.registration',
'allauth.socialaccount',
'allauth.socialaccount.providers.facebook',
'rest_framework_swagger',
)
MIDDLEWARE = (
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
# For backwards compatibility for Django 1.8
MIDDLEWARE_CLASSES = MIDDLEWARE
ROOT_URLCONF = 'demo.urls'
WSGI_APPLICATION = 'demo.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
@ -86,30 +95,15 @@ USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATIC_URL = '/static/'
# TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]
TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
REST_SESSION_LOGIN = True
REST_SESSION_LOGIN = False
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SITE_ID = 1
ACCOUNT_EMAIL_REQUIRED = False
@ -122,8 +116,3 @@ REST_FRAMEWORK = {
'rest_framework.authentication.TokenAuthentication',
)
}
SWAGGER_SETTINGS = {
'LOGIN_URL': 'login',
'LOGOUT_URL': 'logout',
}

View File

@ -2,8 +2,6 @@ from django.conf.urls import include, url
from django.contrib import admin
from django.views.generic import TemplateView, RedirectView
from rest_framework_swagger.views import get_swagger_view
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name="home.html"), name='home'),
url(r'^signup/$', TemplateView.as_view(template_name="signup.html"),
@ -38,7 +36,6 @@ urlpatterns = [
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
url(r'^account/', include('allauth.urls')),
url(r'^admin/', admin.site.urls),
url(r'^admin/', include(admin.site.urls)),
url(r'^accounts/profile/$', RedirectView.as_view(url='/', permanent=True), name='profile-redirect'),
url(r'^docs/$', get_swagger_view(title='API Docs'), name='api_docs')
]

View File

@ -1,6 +1,4 @@
django>=1.9.0
django-rest-auth==0.9.5
djangorestframework>=3.7.0
django-allauth>=0.24.1
django>=1.7.0
django-rest-auth==0.6.0
django-allauth==0.24.1
six==1.9.0
django-rest-swagger==2.0.7

View File

@ -7,7 +7,7 @@
<meta name="description" content="Django-rest-auth demo">
<meta name="author" content="Tivix, Inc.">
<title>django-rest-auth demo</title>
<title>Starter Template for Bootstrap</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
@ -60,7 +60,6 @@
<li class="active"><a href="/">Demo</a></li>
<li><a target="_blank" href="http://django-rest-auth.readthedocs.org/en/latest/">Documentation</a></li>
<li><a target="_blank" href="https://github.com/Tivix/django-rest-auth">Source code</a></li>
<li><a target="_blank" href="{% url 'api_docs' %}">API Docs</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>

View File

@ -11,7 +11,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Logout</button>
<button type="submit" class="btn btn-default">Login</button>
</div>
</div>

View File

@ -1,4 +0,0 @@
--editable .
responses>=0.5.0
djangorestframework-jwt
django-allauth

View File

@ -6,15 +6,14 @@ Basic
- /rest-auth/login/ (POST)
- username
- email
- password
- username (string)
- email (string)
- password (string)
Returns Token key
- /rest-auth/logout/ (POST)
.. note:: ``ACCOUNT_LOGOUT_ON_GET = True`` to allow logout using GET - this is the exact same configuration from allauth. NOT recommended, see: http://django-allauth.readthedocs.io/en/latest/views.html#logout
- token
- /rest-auth/password/reset/ (POST)
@ -35,16 +34,18 @@ Basic
- new_password2
- old_password
.. note:: ``OLD_PASSWORD_FIELD_ENABLED = True`` to use old_password.
.. note:: ``LOGOUT_ON_PASSWORD_CHANGE = False`` to keep the user logged in after password change
- /rest-auth/user/ (GET, PUT, PATCH)
- /rest-auth/user/ (GET)
- /rest-auth/user/ (PUT/PATCH)
- username
- first_name
- last_name
Returns pk, username, email, first_name, last_name
- email
Registration
@ -72,8 +73,6 @@ Basing on example from installation section :doc:`Installation </installation>`
- access_token
- code
.. note:: ``access_token`` OR ``code`` can be used as standalone arguments, see https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/registration/views.py
- /rest-auth/twitter/ (POST)
- access_token

View File

@ -1,59 +1,6 @@
Changelog
=========
0.9.5
-----
- fixed package distribution issue
0.9.4
-----
- Compatibility fixes (#437, #506)
- JWT auth cookie fix (#345)
- config & packaging fixes
- updated docs
- added new translations (Czech, Chinese, Turkish, Korean)
0.9.3
-----
- added social connect views
- added check for pre-existing accounts in social login
- prevent double-validation in LoginSerializer
- unit tests and demo project changes for Django 2.0
0.9.2
-----
- added permission classes configuration for registration
- added more info to JWT docs
- added Polish translations
0.9.1
-----
- fixed import error when extending rest_auth serializers
- added sensitive fields decorator
- added Spanish translations
0.9.0
-----
- allowed using custom UserDetailsSerializer with JWTSerializer
- fixed error with logout on GET
- updated api endpoints and configuration docs
- bugfixes
- minor text fixes
0.8.2
-----
- fixed allauth import error
- added swagger docs to demo project
0.8.1
-----
- added support for django-allauth hmac email confirmation pattern
0.8.0
-----
- added support for django-rest-framework-jwt
- bugfixes
0.7.0
-----
- Wrapped API returned strings in ugettext_lazy

View File

@ -44,16 +44,16 @@ master_doc = 'index'
# General information about the project.
project = u'django-rest-auth'
copyright = u'2018, Tivix Inc.'
copyright = u'2014, Tivix Inc.'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.9.5'
version = '0.3.0'
# The full version, including alpha/beta/rc tags.
release = '0.9.5'
release = '0.3.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -10,8 +10,6 @@ Configuration
- TOKEN_SERIALIZER - response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.TokenSerializer``
- JWT_SERIALIZER - (Using REST_USE_JWT=True) response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.JWTSerializer``
- USER_DETAILS_SERIALIZER - serializer class in ``rest_auth.views.UserDetailsView``, default value ``rest_auth.serializers.UserDetailsSerializer``
- PASSWORD_RESET_SERIALIZER - serializer class in ``rest_auth.views.PasswordResetView``, default value ``rest_auth.serializers.PasswordResetSerializer``
@ -31,14 +29,12 @@ Configuration
...
}
- **REST_AUTH_REGISTER_SERIALIZERS**
- **REST_AUTH_REGISTRATION_SERIALIZERS**
You can define your custom serializers for registration endpoint.
Possible key values:
- REGISTER_SERIALIZER - serializer class in ``rest_auth.registration.views.RegisterView``, default value ``rest_auth.registration.serializers.RegisterSerializer``
.. note:: The custom REGISTER_SERIALIZER must define a ``def save(self, request)`` method that returns a user model instance
- REGISTER_SERIALIZER - serializer class in ``rest_auth.register.views.RegisterView``, default value ``rest_auth.register.serializers.RegisterSerializer``
- **REST_AUTH_TOKEN_MODEL** - model class for tokens, default value ``rest_framework.authtoken.models``
@ -46,8 +42,6 @@ Configuration
- **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True)
- **REST_USE_JWT** - Enable JWT Authentication instead of Token/Session based. This is built on top of django-rest-framework-jwt http://getblimp.github.io/django-rest-framework-jwt/, which must also be installed. (default: False)
- **OLD_PASSWORD_FIELD_ENABLED** - set it to True if you want to have old password verification on password change enpoint (default: False)
- **LOGOUT_ON_PASSWORD_CHANGE** - set to False if you want to keep the current user logged in after a password change

View File

@ -7,14 +7,14 @@ FAQ
.. code-block:: python
url(r'^account-confirm-email/(?P<key>[-:\w]+)/$', TemplateView.as_view(),
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
name='account_confirm_email'),
This url is used by django-allauth. Empty TemplateView is defined just to allow reverse() call inside app - when email with verification link is being sent.
You should override this view/url to handle it in your API client somehow and then, send post to /verify-email/ endpoint with proper key.
If you don't want to use API on that step, then just use ConfirmEmailView view from:
django-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py
djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190
2. I get an error: Reverse for 'password_reset_confirm' not found.

View File

@ -26,51 +26,40 @@ Installation
.. code-block:: python
urlpatterns = [
urlpatterns = patterns('',
...,
url(r'^rest-auth/', include('rest_auth.urls'))
]
)
4. Migrate your database
.. code-block:: python
python manage.py migrate
You're good to go now!
Registration (optional)
-----------------------
1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[with_social]``.
1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[extras]`` or ``pip install django-rest-auth[with_social]``.
2. Add ``django.contrib.sites``, ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py:
3. Add ``SITE_ID = 1`` to your django settings.py
2. Add ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py:
.. code-block:: python
INSTALLED_APPS = (
...,
'django.contrib.sites',
'allauth',
'allauth.account',
'rest_auth.registration',
)
SITE_ID = 1
3. Add rest_auth.registration urls:
.. code-block:: python
urlpatterns = [
urlpatterns = patterns('',
...,
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^rest-auth/registration/', include('rest_auth.registration.urls'))
]
)
Social Authentication (optional)
@ -90,7 +79,6 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati
'rest_framework.authtoken',
'rest_auth'
...,
'django.contrib.sites',
'allauth',
'allauth.account',
'rest_auth.registration',
@ -120,10 +108,10 @@ Facebook
.. code-block:: python
urlpatterns += [
urlpatterns += pattern('',
...,
url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login')
]
)
Twitter
@ -131,15 +119,15 @@ Twitter
If you are using Twitter for your social authentication, it is a bit different since Twitter uses OAuth 1.0.
3. Create new view as a subclass of ``rest_auth.registration.views.SocialLoginView`` with ``TwitterOAuthAdapter`` adapter and ``TwitterLoginSerializer`` as an attribute:
3. Create new view as a subclass of ``rest_auth.views.LoginView`` with ``TwitterOAuthAdapter`` adapter and ``TwitterLoginSerializer`` as an attribute:
.. code-block:: python
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
from rest_auth.registration.views import SocialLoginView
from rest_auth.views import LoginView
from rest_auth.social_serializers import TwitterLoginSerializer
class TwitterLogin(SocialLoginView):
class TwitterLogin(LoginView):
serializer_class = TwitterLoginSerializer
adapter_class = TwitterOAuthAdapter
@ -147,114 +135,8 @@ If you are using Twitter for your social authentication, it is a bit different s
.. code-block:: python
urlpatterns += [
urlpatterns += pattern('',
...,
url(r'^rest-auth/twitter/$', TwitterLogin.as_view(), name='twitter_login')
]
.. note:: Starting from v0.21.0, django-allauth has dropped support for context processors. Check out http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0 for more details.
GitHub
######
If you are using GitHub for your social authentication, it uses code and not AccessToken directly.
3. Create new view as a subclass of ``rest_auth.views.SocialLoginView`` with ``GitHubOAuth2Adapter`` adapter, an ``OAuth2Client`` and a callback_url as attributes:
.. code-block:: python
from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from rest_auth.registration.views import SocialLoginView
class GithubLogin(SocialLoginView):
adapter_class = GitHubOAuth2Adapter
callback_url = CALLBACK_URL_YOU_SET_ON_GITHUB
client_class = OAuth2Client
4. Create url for GitHubLogin view:
.. code-block:: python
urlpatterns += [
...,
url(r'^rest-auth/github/$', GitHubLogin.as_view(), name='github_login')
]
Additional Social Connect Views
###############################
If you want to allow connecting existing accounts in addition to login, you can use connect views:
.. code-block:: python
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from rest_auth.registration.views import SocialConnectView
from rest_auth.social_serializers import TwitterConnectSerializer
class FacebookConnect(SocialConnectView):
adapter_class = FacebookOAuth2Adapter
class TwitterConnect(SocialConnectView):
serializer_class = TwitterConnectSerializer
adapter_class = TwitterOAuthAdapter
class GithubConnect(SocialConnectView):
adapter_class = GitHubOAuth2Adapter
callback_url = CALLBACK_URL_YOU_SET_ON_GITHUB
client_class = OAuth2Client
In urls.py:
.. code-block:: python
urlpatterns += [
...,
url(r'^rest-auth/facebook/connect/$', FacebookConnect.as_view(), name='fb_connect')
url(r'^rest-auth/twitter/connect/$', TwitterConnect.as_view(), name='twitter_connect')
url(r'^rest-auth/github/connect/$', GithubConnect.as_view(), name='github_connect')
]
You can also use the following views to check all social accounts attached to the current authenticated user and disconnect selected social accounts:
.. code-block:: python
from rest_auth.registration.views import (
SocialAccountListView, SocialAccountDisconnectView
)
urlpatterns += [
...,
url(
r'^socialaccounts/$',
SocialAccountListView.as_view(),
name='social_account_list'
),
url(
r'^socialaccounts/(?P<pk>\d+)/disconnect/$',
SocialAccountDisconnectView.as_view(),
name='social_account_disconnect'
)
]
JSON Web Token (JWT) Support (optional)
---------------------------------------
By default ``django-rest-auth`` uses Django's Token-based authentication. If you want to use JWT authentication, follow these steps:
1. Install `djangorestframework-jwt <http://getblimp.github.io/django-rest-framework-jwt/>`_
- ``djangorestframework-jwt`` is currently the only supported JWT library.
2. The ``JWT_PAYLOAD_HANDLER`` and ``JWT_ENCODE_HANDLER`` settings are imported from the ``django-rest-framework-jwt`` settings object.
- Refer to `the library's documentation <http://getblimp.github.io/django-rest-framework-jwt/#additional-settings>`_ for information on using different encoders.
3. Add the following configuration value to your settings file to enable JWT authentication.
.. code-block:: python
REST_USE_JWT = True
.. note:: Starting from v0.21.0, django-allauth has dropped support for context processors. Check out http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0 for more details.

4
flake8 Normal file
View File

@ -0,0 +1,4 @@
[flake8]
max-line-length = 160
exclude = docs/*,demo/*
ignore = F403

View File

@ -2,7 +2,6 @@ from django.conf import settings
from rest_auth.serializers import (
TokenSerializer as DefaultTokenSerializer,
JWTSerializer as DefaultJWTSerializer,
UserDetailsSerializer as DefaultUserDetailsSerializer,
LoginSerializer as DefaultLoginSerializer,
PasswordResetSerializer as DefaultPasswordResetSerializer,
@ -18,9 +17,6 @@ serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
TokenSerializer = import_callable(
serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer))
JWTSerializer = import_callable(
serializers.get('JWT_SERIALIZER', DefaultJWTSerializer))
UserDetailsSerializer = import_callable(
serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer)
)

View File

@ -1,102 +0,0 @@
# Czech translations of Tivix/django-rest-auth
#
# This file is distributed under the same license as the Tivix/django-rest-auth package.
#
msgid ""
msgstr ""
"Project-Id-Version: Tivix/django-rest-auth\n"
"Report-Msgid-Bugs-To: https://github.com/Tivix/django-rest-auth/issues\n"
"POT-Creation-Date: 2018-06-27 23:05+0200\n"
"PO-Revision-Date: 2018-06-27 23:22+0200\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"Last-Translator: Václav Dohnal <vaclav.dohnal@gmail.com>\n"
"Language-Team: N/A\n"
"X-Generator: Poedit 2.0.8\n"
#: .\registration\serializers.py:67
msgid "View is not defined, pass it as a context variable"
msgstr "View není definováno, předejte jej jako proměnnou kontextu"
#: .\registration\serializers.py:72
msgid "Define adapter_class in view"
msgstr "Definujte adapter_class ve view"
#: .\registration\serializers.py:91
msgid "Define callback_url in view"
msgstr "Definujte callback_url ve view"
#: .\registration\serializers.py:95
msgid "Define client_class in view"
msgstr "Definujte client_class ve view"
#: .\registration\serializers.py:116
msgid "Incorrect input. access_token or code is required."
msgstr "Nesprávný vstup. access_token je povinný."
#: .\registration\serializers.py:125
msgid "Incorrect value"
msgstr "Nesprávná hodnota"
#: .\registration\serializers.py:139
msgid "User is already registered with this e-mail address."
msgstr "Uživatel s touto adresou je již registrován."
#: .\registration\serializers.py:185
msgid "A user is already registered with this e-mail address."
msgstr "Uživatel s touto adresou je již registrován."
#: .\registration\serializers.py:193
msgid "The two password fields didn't match."
msgstr "Zadaná hesla se neshodují."
#: .\registration\views.py:51
msgid "Verification e-mail sent."
msgstr "Ověřovací e-mail odeslán."
#: .\registration\views.py:98
msgid "ok"
msgstr "ok"
#: .\serializers.py:30
msgid "Must include \"email\" and \"password\"."
msgstr "Musí obsahovat \"e-mail\" a \"heslo\"."
#: .\serializers.py:41
msgid "Must include \"username\" and \"password\"."
msgstr "Musí obsahovat \"uživatelské jméno\" a \"heslo\"."
#: .\serializers.py:54
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "Musí obsahovat \"uživatelské jméno\" nebo \"e-mail\" a \"heslo\"."
#: .\serializers.py:95
msgid "User account is disabled."
msgstr "Uživatelský účet je zakázán."
#: .\serializers.py:98
msgid "Unable to log in with provided credentials."
msgstr "Pomocí zadaných údajů se nelze přihlásit."
#: .\serializers.py:107
msgid "E-mail is not verified."
msgstr "E-mail není ověřený."
#: .\views.py:127
msgid "Successfully logged out."
msgstr "Byli jste úspěšně odhlášeni."
#: .\views.py:175
msgid "Password reset e-mail has been sent."
msgstr "E-mail pro resetování hesla byl odeslán."
#: .\views.py:201
msgid "Password has been reset with the new password."
msgstr "Vaše heslo bylo resetováno."
#: .\views.py:223
msgid "New password has been saved."
msgstr "Nové heslo bylo uloženo."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-05 21:56-0800\n"
"POT-Creation-Date: 2016-02-02 14:11+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,19 +18,19 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: registration/serializers.py:53
#: registration/serializers.py:54
msgid "View is not defined, pass it as a context variable"
msgstr "\"View\" ist nicht definiert, übergib es als Contextvariable"
#: registration/serializers.py:58
#: registration/serializers.py:59
msgid "Define adapter_class in view"
msgstr "Definier \"adapter_class\" in view"
#: registration/serializers.py:77
#: registration/serializers.py:78
msgid "Define callback_url in view"
msgstr "Definier \"callback_url\" in view"
#: registration/serializers.py:81
#: registration/serializers.py:82
msgid "Define client_class in view"
msgstr "Definier \"client_class\" in view"
@ -50,49 +50,50 @@ msgstr "Ein User mit dieser E-Mail Adresse ist schon registriert."
msgid "The two password fields didn't match."
msgstr "Die beiden Passwörter sind nicht identisch."
#: registration/views.py:91
#: registration/views.py:64
msgid "ok"
msgstr "Ok"
#: serializers.py:30
#: serializers.py:29
msgid "Must include \"email\" and \"password\"."
msgstr "Muss \"email\" und \"password\" enthalten."
#: serializers.py:41
#: serializers.py:40
msgid "Must include \"username\" and \"password\"."
msgstr "Muss \"username\" und \"password\" enthalten."
#: serializers.py:54
#: serializers.py:53
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "Muss entweder \"username\" oder \"email\" und password \"password\""
#: serializers.py:95
#: serializers.py:94
msgid "User account is disabled."
msgstr "Der Useraccount ist deaktiviert."
#: serializers.py:98
#: serializers.py:97
msgid "Unable to log in with provided credentials."
msgstr "Kann nicht mit den angegeben Zugangsdaten anmelden."
#: serializers.py:107
#: serializers.py:106
msgid "E-mail is not verified."
msgstr "E-Mail Adresse ist nicht verifiziert."
#: views.py:126
#: serializers.py:152
msgid "Error"
msgstr "Fehler"
#: views.py:71
msgid "Successfully logged out."
msgstr "Erfolgreich ausgeloggt."
#: views.py:174
#: views.py:111
msgid "Password reset e-mail has been sent."
msgstr "Die E-Mail zum Zurücksetzen des Passwortes wurde verschickt."
#: views.py:200
#: views.py:132
msgid "Password has been reset with the new password."
msgstr "Das Passwort wurde mit dem neuen Passwort ersetzt."
#: views.py:222
#: views.py:150
msgid "New password has been saved."
msgstr "Das neue Passwort wurde gespeichert."
#~ msgid "Error"
#~ msgstr "Fehler"

View File

@ -1,99 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-05 21:56-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Carlos de las Heras <cahersan@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: registration/serializers.py:53
msgid "View is not defined, pass it as a context variable"
msgstr "\"View\" no está definida, pásala como una variable de contexto"
#: registration/serializers.py:58
msgid "Define adapter_class in view"
msgstr "Defina \"adapter_class\" en view"
#: registration/serializers.py:77
msgid "Define callback_url in view"
msgstr "Defina \"callback_url\" en view"
#: registration/serializers.py:81
msgid "Define client_class in view"
msgstr "Defina \"client_class\" en view"
#: registration/serializers.py:102
msgid "Incorrect input. access_token or code is required."
msgstr "Entrada incorrecta. Se requiere \"access_token\" o \"code\"."
#: registration/serializers.py:111
msgid "Incorrect value"
msgstr "Valor incorrecto"
#: registration/serializers.py:140
msgid "A user is already registered with this e-mail address."
msgstr "Ya existe un usuario registrado con esa dirección de correo electrónico."
#: registration/serializers.py:148
msgid "The two password fields didn't match."
msgstr "Las contraseñas no coinciden"
#: registration/views.py:44
msgid "Verification e-mail sent."
msgstr "Se ha enviado un correo de verificación."
#: registration/views.py:91
msgid "ok"
msgstr "Ok"
#: serializers.py:30
msgid "Must include \"email\" and \"password\"."
msgstr "Debe incluir \"correo electrónico\" y \"contraseña\"."
#: serializers.py:41
msgid "Must include \"username\" and \"password\"."
msgstr "Debe incluir \"nombre de usuario\" y \"contraseña\"."
#: serializers.py:54
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "Debe incluir \"nombre de usuario\" o \"correo electrónico\" y \"contraseña\"."
#: serializers.py:95
msgid "User account is disabled."
msgstr "Cuenta de usuario deshabilitada"
#: serializers.py:98
msgid "Unable to log in with provided credentials."
msgstr "No puede iniciar sesión con las credenciales proporcionadas."
#: serializers.py:107
msgid "E-mail is not verified."
msgstr "El correo electrónico no ha sido verificado."
#: views.py:126
msgid "Successfully logged out."
msgstr "Sesión cerrada con éxito."
#: views.py:174
msgid "Password reset e-mail has been sent."
msgstr "Se ha enviado un correo electrónico para restablecer la contraseña."
#: views.py:200
msgid "Password has been reset with the new password."
msgstr "La contraseña ha sido restablecida con la nueva contraseña."
#: views.py:222
msgid "New password has been saved."
msgstr "La nueva contraseña ha sido guardada."

View File

@ -1,98 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-22 11:37-0800\n"
"PO-Revision-Date: 2017-02-14 13:27+0100\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Last-Translator: \n"
"Language-Team: \n"
"X-Generator: Poedit 1.8.11\n"
#: registration/serializers.py:53
msgid "View is not defined, pass it as a context variable"
msgstr "La “View” nest pas définie, passez la en variable contextuelle"
#: registration/serializers.py:58
msgid "Define adapter_class in view"
msgstr "Définissez “adapter_class” dans la vue"
#: registration/serializers.py:77
msgid "Define callback_url in view"
msgstr "Définissez “callback_url” dans la vue"
#: registration/serializers.py:81
msgid "Define client_class in view"
msgstr "Définissez “client_class” dans la vue"
#: registration/serializers.py:102
msgid "Incorrect input. access_token or code is required."
msgstr "Paramètres incorrects. Il faut “access_token” ou “code”."
#: registration/serializers.py:111
msgid "Incorrect value"
msgstr "Paramètre incorrect"
#: registration/serializers.py:140
msgid "A user is already registered with this e-mail address."
msgstr "Un utilisateur existe déjà avec cette adresse email."
#: registration/serializers.py:148
msgid "The two password fields didn't match."
msgstr "Les deux mots de passes ne sont pas les mêmes."
#: registration/views.py:82
msgid "ok"
msgstr "Ok"
#: serializers.py:30
msgid "Must include \"email\" and \"password\"."
msgstr "Doit inclure “email” et “password”."
#: serializers.py:41
msgid "Must include \"username\" and \"password\"."
msgstr "Doit inclure “username” et “password”."
#: serializers.py:54
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "Doit inclure un “username” ou “email”, et un “password”."
#: serializers.py:95
msgid "User account is disabled."
msgstr "Le compte utilisateur est désactivé."
#: serializers.py:98
msgid "Unable to log in with provided credentials."
msgstr "Connexion impossible avec les informations fournies."
#: serializers.py:107
msgid "E-mail is not verified."
msgstr "Ladresse email na pas été vérifiée."
#: views.py:114
msgid "Successfully logged out."
msgstr "Déconnexion effectuée avec succès."
#: views.py:162
msgid "Password reset e-mail has been sent."
msgstr "Lemail de réinitialisation du mot de passe a été envoyé."
#: views.py:184
msgid "Password has been reset with the new password."
msgstr "Le mot de passe a été réinitialisé."
#: views.py:202
msgid "New password has been saved."
msgstr "Le nouveau mot de passe est sauvé."
#~ msgid "Error"
#~ msgstr "Fehler"

View File

@ -1,99 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-05 21:56-0800\n"
"PO-Revision-Date: 2018-03-20 17:52+0900\n"
"Last-Translator: Jeonsgoo Park <toracle@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: registration/serializers.py:53
msgid "View is not defined, pass it as a context variable"
msgstr "View가 정의되지 않았습니다. 컨텍스트 변수에 포함해주세요"
#: registration/serializers.py:58
msgid "Define adapter_class in view"
msgstr "view에 adapter_class를 정의하세요"
#: registration/serializers.py:77
msgid "Define callback_url in view"
msgstr "view에 callback_url을 정의하세요"
#: registration/serializers.py:81
msgid "Define client_class in view"
msgstr "view에 client_class를 정의하세요"
#: registration/serializers.py:102
msgid "Incorrect input. access_token or code is required."
msgstr "올바르지 않은 입력입니다. access_token이나 code가 필요합니다."
#: registration/serializers.py:111
msgid "Incorrect value"
msgstr "올바르지 않은 값"
#: registration/serializers.py:140
msgid "A user is already registered with this e-mail address."
msgstr "이미 이 이메일 주소로 등록된 사용자가 있습니다."
#: registration/serializers.py:148
msgid "The two password fields didn't match."
msgstr "두 개의 패스워드 필드가 서로 맞지 않습니다."
#: registration/views.py:44
msgid "Verification e-mail sent."
msgstr "확인 이메일을 발송했습니다."
#: registration/views.py:91
msgid "ok"
msgstr "ok"
#: serializers.py:30
msgid "Must include \"email\" and \"password\"."
msgstr "\"email\"과 \"password\"를 반드시 포함해야 합니다."
#: serializers.py:41
msgid "Must include \"username\" and \"password\"."
msgstr "\"username\"과 \"password\"를 반드시 포함해야 합니다."
#: serializers.py:54
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "\"username\"이나 \"email\", 그리고 \"password\"를 반드시 포함해야 합니다."
#: serializers.py:95
msgid "User account is disabled."
msgstr "사용자 계정이 비활성화 되어있습니다."
#: serializers.py:98
msgid "Unable to log in with provided credentials."
msgstr "주어진 자격 증명으로 로그인이 불가능합니다."
#: serializers.py:107
msgid "E-mail is not verified."
msgstr "이메일 주소가 확인되지 않았습니다."
#: views.py:126
msgid "Successfully logged out."
msgstr "로그아웃되었습니다."
#: views.py:174
msgid "Password reset e-mail has been sent."
msgstr "패스워드 초기화 이메일이 발송되었습니다."
#: views.py:200
msgid "Password has been reset with the new password."
msgstr "새로운 패스워드로 패스워드가 초기화 되었습니다."
#: views.py:222
msgid "New password has been saved."
msgstr "새로운 패스워드가 저장되었습니다."

View File

@ -1,99 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-05 21:56-0800\n"
"PO-Revision-Date: 2017-03-11 12:10+0100\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Language-Team: \n"
"X-Generator: Poedit 1.8.11\n"
"Last-Translator: \n"
"Language: pl\n"
#: registration/serializers.py:53
msgid "View is not defined, pass it as a context variable"
msgstr "Widok nie został zdefiniowany, przekaż go przez zmienną \"context\""
#: registration/serializers.py:58
msgid "Define adapter_class in view"
msgstr "Zdefiniuj \"adapter_class\" w widoku"
#: registration/serializers.py:77
msgid "Define callback_url in view"
msgstr "Zdefiniuj \"callback_url\" w widoku"
#: registration/serializers.py:81
msgid "Define client_class in view"
msgstr "Zdefiniuj \"client_class\" w widoku"
#: registration/serializers.py:102
msgid "Incorrect input. access_token or code is required."
msgstr "Podano błędne dane. \"access_token\" lub \"code\" są wymagne."
#: registration/serializers.py:111
msgid "Incorrect value"
msgstr "Niepoprawna wartość."
#: registration/serializers.py:140
msgid "A user is already registered with this e-mail address."
msgstr "Istnieje już użytkownik z takim adresem email."
#: registration/serializers.py:148
msgid "The two password fields didn't match."
msgstr "Hasła nie są identyczne."
#: registration/views.py:44
msgid "Verification e-mail sent."
msgstr "Email weryfikacyjny został wysłany."
#: registration/views.py:91
msgid "ok"
msgstr "ok"
#: serializers.py:30
msgid "Must include \"email\" and \"password\"."
msgstr "Musisz podać email i hasło."
#: serializers.py:41
msgid "Must include \"username\" and \"password\"."
msgstr "Musisz podać nazwę użytkownika i hasło."
#: serializers.py:54
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "Musisz podać nazwę użytkownika (lub email) i hasło."
#: serializers.py:95
msgid "User account is disabled."
msgstr "Konto użytkownika zostało wyłączone."
#: serializers.py:98
msgid "Unable to log in with provided credentials."
msgstr "Podane dane do logowania są niepoprawne."
#: serializers.py:107
msgid "E-mail is not verified."
msgstr "Email nie został zweryfikowany."
#: views.py:126
msgid "Successfully logged out."
msgstr "Wylogowano."
#: views.py:174
msgid "Password reset e-mail has been sent."
msgstr "Email z linkiem do resetu hasła został wysłany."
#: views.py:200
msgid "Password has been reset with the new password."
msgstr "Hasło zostało zresetowane."
#: views.py:222
msgid "New password has been saved."
msgstr "Nowe hasło zostało zapisane."

View File

@ -1,99 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Bruno Barreto Freitas <brunobarretofreitas@outlook.com>, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 09:48-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Bruno Barreto Freitas <brunobarretofreitas@outlook.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: registration/serializers.py:53
msgid "View is not defined, pass it as a context variable"
msgstr "\"View\" não está definida, passe-a como uma variável de contexto"
#: registration/serializers.py:58
msgid "Define adapter_class in view"
msgstr "Defina \"adapter_class\" na view"
#: registration/serializers.py:77
msgid "Define callback_url in view"
msgstr "Defina \"callback_url\" na view"
#: registration/serializers.py:81
msgid "Define client_class in view"
msgstr "Defina \"client_class\" na view"
#: registration/serializers.py:102
msgid "Incorrect input. access_token or code is required."
msgstr "Entrada incorreta. \"access_token\" ou \"code\" são obrigatórios."
#: registration/serializers.py:111
msgid "Incorrect value"
msgstr "Valor incorreto"
#: registration/serializers.py:140
msgid "A user is already registered with this e-mail address."
msgstr "Já existe um usuário cadastrado com este endereço de e-mail."
#: registration/serializers.py:148
msgid "The two password fields didn't match."
msgstr "Os dois campos de senha não correspondem."
#: registration/views.py:44
msgid "Verification e-mail sent."
msgstr "E-mail de verificação enviado."
#: registration/views.py:91
msgid "ok"
msgstr "Ok"
#: serializers.py:30
msgid "Must include \"email\" and \"password\"."
msgstr "Deve-se incluir \"email\" e \"password\"."
#: serializers.py:41
msgid "Must include \"username\" and \"password\"."
msgstr "Deve-se incluir \"username\" e \"password\"."
#: serializers.py:54
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "Deve-se incluir \"username\" ou \"email\" e \"password\"."
#: serializers.py:95
msgid "User account is disabled."
msgstr "Conta de usuário está desativada"
#: serializers.py:98
msgid "Unable to log in with provided credentials."
msgstr "Não foi possível realizar o login com as credenciais fornecidas."
#: serializers.py:107
msgid "E-mail is not verified."
msgstr "E-mail não foi verificado."
#: views.py:126
msgid "Successfully logged out."
msgstr "Logout realizado com sucesso."
#: views.py:174
msgid "Password reset e-mail has been sent."
msgstr "E-mail de redefinição de senha foi enviado."
#: views.py:200
msgid "Password has been reset with the new password."
msgstr "Senha foi redefinida com a nova senha."
#: views.py:222
msgid "New password has been saved."
msgstr "Nova senha foi salva com sucesso."

View File

@ -1,101 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-05 21:56-0800\n"
"PO-Revision-Date: 2016-08-01 07:48+0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
"X-Generator: Poedit 1.8.5\n"
#: registration/serializers.py:53
msgid "View is not defined, pass it as a context variable"
msgstr "View неизвестен, передайте его как переменную контекста"
#: registration/serializers.py:58
msgid "Define adapter_class in view"
msgstr "Установите adapter_class в view"
#: registration/serializers.py:77
msgid "Define callback_url in view"
msgstr "Установите callback_url в view"
#: registration/serializers.py:81
msgid "Define client_class in view"
msgstr "Установите client_class в view"
#: registration/serializers.py:102
msgid "Incorrect input. access_token or code is required."
msgstr "Некорректный ввод. Необходим access_token или code."
#: registration/serializers.py:111
msgid "Incorrect value"
msgstr "Некорректное значение"
#: registration/serializers.py:140
msgid "A user is already registered with this e-mail address."
msgstr "Пользователь с таким e-mail адресом уже зарегистрирован."
#: registration/serializers.py:148
msgid "The two password fields didn't match."
msgstr "Пароли не совпадают."
#: registration/views.py:44
msgid "Verification e-mail sent."
msgstr "Письмо с подтверждением выслано."
#: registration/views.py:91
msgid "ok"
msgstr "ок"
#: serializers.py:30
msgid "Must include \"email\" and \"password\"."
msgstr "Должно включать \"email\" и \"password\"."
#: serializers.py:41
msgid "Must include \"username\" and \"password\"."
msgstr "Должно включать \"username\" и \"password\"."
#: serializers.py:54
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "Должно включать либо \"username\" либо \"email\" и \"password\"."
#: serializers.py:95
msgid "User account is disabled."
msgstr "Пользовательский аккаунт отключён."
#: serializers.py:98
msgid "Unable to log in with provided credentials."
msgstr "Невозможно войти в систему с указанными учётными данными."
#: serializers.py:107
msgid "E-mail is not verified."
msgstr "E-mail не подтверждён."
#: views.py:126
msgid "Successfully logged out."
msgstr "Успешно вышли."
#: views.py:174
msgid "Password reset e-mail has been sent."
msgstr "Письмо с инструкциями по восстановлению пароля выслано."
#: views.py:200
msgid "Password has been reset with the new password."
msgstr "Пароль изменён на новый."
#: views.py:222
msgid "New password has been saved."
msgstr "Новый пароль сохранён."

View File

@ -1,95 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-05 21:56-0800\n"
"PO-Revision-Date: 2018-10-13 19:37+0300\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Last-Translator: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.2\n"
#: registration/serializers.py:67
msgid "View is not defined, pass it as a context variable"
msgstr "“View” tanımlanmadı, “context” değişkeni olarak tanımla"
#: registration/serializers.py:72
msgid "Define adapter_class in view"
msgstr "“view” içerisinde “adapter_class” tanımla"
#: registration/serializers.py:91
msgid "Define callback_url in view"
msgstr "“view” içerisinde “callback_url” tanımla"
#: registration/serializers.py:95
msgid "Define client_class in view"
msgstr "“view” içerisinde “client_class” tanımla"
#: registration/serializers.py:116
msgid "Incorrect input. access_token or code is required."
msgstr "Geçersiz girdi. “access_token” veya “code” gerekli."
#: registration/serializers.py:125
msgid "Incorrect value"
msgstr "Geçersiz değer"
#: registration/serializers.py:185
msgid "A user is already registered with this e-mail address."
msgstr "Bu e-posta adresi ile bir kullanıcı zaten kayıt olmuştu."
#: registration/serializers.py:193
msgid "The two password fields didn't match."
msgstr "İki şifre alanı eşleşmiyor."
#: registration/views.py:98
msgid "ok"
msgstr "tamam"
#: serializers.py:33
msgid "Must include \"email\" and \"password\"."
msgstr "\"email\" ve \"password\" içermelidir."
#: serializers.py:44
msgid "Must include \"username\" and \"password\"."
msgstr "“username\" und \"password\" içermelidir."
#: serializers.py:57
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "Ya ”username\" yada \"email\" ve \"password\" içermelidir."
#: serializers.py:98
msgid "User account is disabled."
msgstr "Kullanıcı hesap pasiftir."
#: serializers.py:101
msgid "Unable to log in with provided credentials."
msgstr "Sağlanan kimlik bilgileri ile giriş yapılamıyor."
#: serializers.py:110
msgid "E-mail is not verified."
msgstr "E-posta adresi doğrulanmadı."
#: views.py:127
msgid "Successfully logged out."
msgstr "Başarılı bir şekilde çıkış yapıldı."
#: views.py:175
msgid "Password reset e-mail has been sent."
msgstr "Şifre sıfırlama e-postası gönderildi."
#: views.py:201
msgid "Password has been reset with the new password."
msgstr "Yeni şifre ile şifre sıfırlandı."
#: views.py:223
msgid "New password has been saved."
msgstr "Yeni şifre kaydedildi."

View File

@ -1,104 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-28 11:41+0800\n"
"PO-Revision-Date: 2018-10-28 11:45+0806\n"
"Last-Translator: b' <admin@xx.com>'\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Translated-Using: django-rosetta 0.9.0\n"
#: registration/serializers.py:67
msgid "View is not defined, pass it as a context variable"
msgstr "View未定义请通过context变量传入"
#: registration/serializers.py:72
msgid "Define adapter_class in view"
msgstr "请在View中定义adapter_class"
#: registration/serializers.py:91
msgid "Define callback_url in view"
msgstr "请在view中定义callback_url"
#: registration/serializers.py:95
msgid "Define client_class in view"
msgstr "请在view中定义client_class"
#: registration/serializers.py:116
msgid "Incorrect input. access_token or code is required."
msgstr "输入错误。access_token或code是必填项。"
#: registration/serializers.py:125
msgid "Incorrect value"
msgstr "错误的值"
#: registration/serializers.py:139
msgid "User is already registered with this e-mail address."
msgstr "该邮箱地址已被注册。"
#: registration/serializers.py:185
msgid "A user is already registered with this e-mail address."
msgstr "该邮箱地址已被注册。"
#: registration/serializers.py:193
msgid "The two password fields didn't match."
msgstr "两次输入的密码不相同"
#: registration/views.py:51
msgid "Verification e-mail sent."
msgstr "验证邮件已发送。"
#: registration/views.py:98
msgid "ok"
msgstr "好的"
#: serializers.py:33
msgid "Must include \"email\" and \"password\"."
msgstr "比如包含\"email\"和\"password\"。"
#: serializers.py:44
msgid "Must include \"username\" and \"password\"."
msgstr "比如包含\"username\"和\"password\"。"
#: serializers.py:57
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr "比如包含\"username\"\"email\"\"password\"其中一个。"
#: serializers.py:98
msgid "User account is disabled."
msgstr "用户账号已被禁用。"
#: serializers.py:101
msgid "Unable to log in with provided credentials."
msgstr "无法使用提供的信息登录。"
#: serializers.py:110
msgid "E-mail is not verified."
msgstr "邮箱未验证。"
#: views.py:127
msgid "Successfully logged out."
msgstr "已成功登出。"
#: views.py:175
msgid "Password reset e-mail has been sent."
msgstr "密码重置邮件已发送。"
#: views.py:201
msgid "Password has been reset with the new password."
msgstr "密码重置成功。"
#: views.py:223
msgid "New password has been saved."
msgstr "新密码已设置成功。"

View File

@ -1,103 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-28 11:41+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: registration/serializers.py:67
msgid "View is not defined, pass it as a context variable"
msgstr ""
#: registration/serializers.py:72
msgid "Define adapter_class in view"
msgstr ""
#: registration/serializers.py:91
msgid "Define callback_url in view"
msgstr ""
#: registration/serializers.py:95
msgid "Define client_class in view"
msgstr ""
#: registration/serializers.py:116
msgid "Incorrect input. access_token or code is required."
msgstr ""
#: registration/serializers.py:125
msgid "Incorrect value"
msgstr ""
#: registration/serializers.py:139
msgid "User is already registered with this e-mail address."
msgstr ""
#: registration/serializers.py:185
msgid "A user is already registered with this e-mail address."
msgstr ""
#: registration/serializers.py:193
msgid "The two password fields didn't match."
msgstr ""
#: registration/views.py:51
msgid "Verification e-mail sent."
msgstr ""
#: registration/views.py:98
msgid "ok"
msgstr ""
#: serializers.py:33
msgid "Must include \"email\" and \"password\"."
msgstr ""
#: serializers.py:44
msgid "Must include \"username\" and \"password\"."
msgstr ""
#: serializers.py:57
msgid "Must include either \"username\" or \"email\" and \"password\"."
msgstr ""
#: serializers.py:98
msgid "User account is disabled."
msgstr ""
#: serializers.py:101
msgid "Unable to log in with provided credentials."
msgstr ""
#: serializers.py:110
msgid "E-mail is not verified."
msgstr ""
#: views.py:127
msgid "Successfully logged out."
msgstr ""
#: views.py:175
msgid "Password reset e-mail has been sent."
msgstr ""
#: views.py:201
msgid "Password has been reset with the new password."
msgstr ""
#: views.py:223
msgid "New password has been saved."
msgstr ""

View File

@ -1,6 +1,5 @@
from django.conf import settings
from rest_framework.permissions import AllowAny
from rest_auth.registration.serializers import (
RegisterSerializer as DefaultRegisterSerializer)
from ..utils import import_callable
@ -10,10 +9,3 @@ serializers = getattr(settings, 'REST_AUTH_REGISTER_SERIALIZERS', {})
RegisterSerializer = import_callable(
serializers.get('REGISTER_SERIALIZER', DefaultRegisterSerializer))
def register_permission_classes():
permission_classes = [AllowAny, ]
for klass in getattr(settings, 'REST_AUTH_REGISTER_PERMISSION_CLASSES', tuple()):
permission_classes.append(import_callable(klass))
return tuple(permission_classes)

View File

@ -1,6 +1,6 @@
from django.http import HttpRequest
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model
try:
from allauth.account import app_settings as allauth_settings
@ -8,29 +8,19 @@ try:
get_username_max_length)
from allauth.account.adapter import get_adapter
from allauth.account.utils import setup_user_email
from allauth.socialaccount.helpers import complete_social_login
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.providers.base import AuthProcess
except ImportError:
raise ImportError("allauth needs to be added to INSTALLED_APPS.")
raise ImportError('allauth needs to be added to INSTALLED_APPS.')
from rest_framework import serializers
from requests.exceptions import HTTPError
# Import is needed only if we are using social login, in which
# case the allauth.socialaccount will be declared
class SocialAccountSerializer(serializers.ModelSerializer):
"""
serialize allauth SocialAccounts for use with a REST API
"""
class Meta:
model = SocialAccount
fields = (
'id',
'provider',
'uid',
'last_login',
'date_joined',
)
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
try:
from allauth.socialaccount.helpers import complete_social_login
except ImportError:
pass
class SocialLoginSerializer(serializers.Serializer):
@ -45,13 +35,12 @@ class SocialLoginSerializer(serializers.Serializer):
def get_social_login(self, adapter, app, token, response):
"""
:param adapter: allauth.socialaccount Adapter subclass.
Usually OAuthAdapter or Auth2Adapter
:param adapter: allauth.socialaccount Adapter subclass. Usually OAuthAdapter or Auth2Adapter
:param app: `allauth.socialaccount.SocialApp` instance
:param token: `allauth.socialaccount.SocialToken` instance
:param response: Provider's response for OAuth1. Not used in the
:returns: A populated instance of the
`allauth.socialaccount.SocialLoginView` instance
:return: :return: A populated instance of the `allauth.socialaccount.SocialLoginView` instance
"""
request = self._get_request()
social_login = adapter.complete_login(request, app, token, response=response)
@ -64,35 +53,35 @@ class SocialLoginSerializer(serializers.Serializer):
if not view:
raise serializers.ValidationError(
_("View is not defined, pass it as a context variable")
_('View is not defined, pass it as a context variable')
)
adapter_class = getattr(view, 'adapter_class', None)
if not adapter_class:
raise serializers.ValidationError(_("Define adapter_class in view"))
raise serializers.ValidationError(_('Define adapter_class in view'))
adapter = adapter_class(request)
adapter = adapter_class()
app = adapter.get_provider().get_app(request)
# More info on code vs access_token
# http://stackoverflow.com/questions/8666316/facebook-oauth-2-0-code-and-token
# Case 1: We received the access_token
if attrs.get('access_token'):
if('access_token' in attrs):
access_token = attrs.get('access_token')
# Case 2: We received the authorization code
elif attrs.get('code'):
elif('code' in attrs):
self.callback_url = getattr(view, 'callback_url', None)
self.client_class = getattr(view, 'client_class', None)
if not self.callback_url:
raise serializers.ValidationError(
_("Define callback_url in view")
_('Define callback_url in view')
)
if not self.client_class:
raise serializers.ValidationError(
_("Define client_class in view")
_('Define client_class in view')
)
code = attrs.get('code')
@ -112,57 +101,25 @@ class SocialLoginSerializer(serializers.Serializer):
access_token = token['access_token']
else:
raise serializers.ValidationError(
_("Incorrect input. access_token or code is required."))
raise serializers.ValidationError(_('Incorrect input. access_token or code is required.'))
social_token = adapter.parse_token({'access_token': access_token})
social_token.app = app
token = adapter.parse_token({'access_token': access_token})
token.app = app
try:
login = self.get_social_login(adapter, app, social_token, access_token)
login = self.get_social_login(adapter, app, token, access_token)
complete_social_login(request, login)
except HTTPError:
raise serializers.ValidationError(_("Incorrect value"))
raise serializers.ValidationError(_('Incorrect value'))
if not login.is_existing:
# We have an account already signed up in a different flow
# with the same email address: raise an exception.
# This needs to be handled in the frontend. We can not just
# link up the accounts due to security constraints
if allauth_settings.UNIQUE_EMAIL:
# Do we have an account already with this email address?
account_exists = get_user_model().objects.filter(
email=login.user.email,
).exists()
if account_exists:
raise serializers.ValidationError(
_("User is already registered with this e-mail address.")
)
login.lookup()
login.save(request, connect=True)
attrs['user'] = login.account.user
return attrs
class SocialConnectMixin(object):
def get_social_login(self, *args, **kwargs):
"""
Set the social login process state to connect rather than login
Refer to the implementation of get_social_login in base class and to the
allauth.socialaccount.helpers module complete_social_login function.
"""
social_login = super(SocialConnectMixin, self).get_social_login(*args, **kwargs)
social_login.state['process'] = AuthProcess.CONNECT
return social_login
class SocialConnectSerializer(SocialConnectMixin, SocialLoginSerializer):
pass
class RegisterSerializer(serializers.Serializer):
username = serializers.CharField(
max_length=get_username_max_length(),
@ -170,8 +127,8 @@ class RegisterSerializer(serializers.Serializer):
required=allauth_settings.USERNAME_REQUIRED
)
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
password1 = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True)
password1 = serializers.CharField(required=True, write_only=True)
password2 = serializers.CharField(required=True, write_only=True)
def validate_username(self, username):
username = get_adapter().clean_username(username)

View File

@ -17,7 +17,7 @@ urlpatterns = [
# with proper key.
# If you don't want to use API on that step, then just use ConfirmEmailView
# view from:
# django-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py
url(r'^account-confirm-email/(?P<key>[-:\w]+)/$', TemplateView.as_view(),
# djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
name='account_confirm_email'),
]

View File

@ -1,63 +1,36 @@
from django.conf import settings
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_post_parameters
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import (AllowAny,
IsAuthenticated)
from rest_framework.generics import CreateAPIView, ListAPIView, GenericAPIView
from rest_framework.exceptions import NotFound
from rest_framework.permissions import AllowAny
from rest_framework.generics import CreateAPIView
from rest_framework import status
from rest_framework.exceptions import MethodNotAllowed
from allauth.account.adapter import get_adapter
from allauth.account.views import ConfirmEmailView
from allauth.account.utils import complete_signup
from allauth.account import app_settings as allauth_settings
from allauth.socialaccount import signals
from allauth.socialaccount.adapter import get_adapter as get_social_adapter
from allauth.socialaccount.models import SocialAccount
from rest_auth.app_settings import (TokenSerializer,
JWTSerializer,
create_token)
from rest_auth.models import TokenModel
from rest_auth.registration.serializers import (VerifyEmailSerializer,
SocialLoginSerializer,
SocialAccountSerializer,
SocialConnectSerializer)
from rest_auth.utils import jwt_encode
from rest_auth.registration.serializers import (SocialLoginSerializer,
VerifyEmailSerializer)
from rest_auth.views import LoginView
from .app_settings import RegisterSerializer, register_permission_classes
sensitive_post_parameters_m = method_decorator(
sensitive_post_parameters('password1', 'password2')
)
from rest_auth.models import TokenModel
from .app_settings import RegisterSerializer
class RegisterView(CreateAPIView):
serializer_class = RegisterSerializer
permission_classes = register_permission_classes()
permission_classes = (AllowAny, )
token_model = TokenModel
@sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(RegisterView, self).dispatch(*args, **kwargs)
def get_response_data(self, user):
if allauth_settings.EMAIL_VERIFICATION == \
allauth_settings.EmailVerificationMethod.MANDATORY:
return {"detail": _("Verification e-mail sent.")}
return {}
if getattr(settings, 'REST_USE_JWT', False):
data = {
'user': user,
'token': self.token
}
return JWTSerializer(data).data
else:
return TokenSerializer(user.auth_token).data
return TokenSerializer(user.auth_token).data
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
@ -65,17 +38,11 @@ class RegisterView(CreateAPIView):
user = self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(self.get_response_data(user),
status=status.HTTP_201_CREATED,
headers=headers)
return Response(self.get_response_data(user), status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
user = serializer.save(self.request)
if getattr(settings, 'REST_USE_JWT', False):
self.token = jwt_encode(user)
else:
create_token(self.token_model, user, serializer)
create_token(self.token_model, user, serializer)
complete_signup(self.request._request, user,
allauth_settings.EMAIL_VERIFICATION,
None)
@ -83,19 +50,20 @@ class RegisterView(CreateAPIView):
class VerifyEmailView(APIView, ConfirmEmailView):
permission_classes = (AllowAny,)
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
def get_serializer(self, *args, **kwargs):
return VerifyEmailSerializer(*args, **kwargs)
def get(self, *args, **kwargs):
raise MethodNotAllowed('GET')
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer = VerifyEmailSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.kwargs['key'] = serializer.validated_data['key']
confirmation = self.get_object()
confirmation.confirm(self.request)
return Response({'detail': _('ok')}, status=status.HTTP_200_OK)
return Response({'message': _('ok')}, status=status.HTTP_200_OK)
class SocialLoginView(LoginView):
@ -117,70 +85,9 @@ class SocialLoginView(LoginView):
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
client_class = OAuth2Client
callback_url = 'localhost:8000'
client_class = OAuth2Client
callback_url = 'localhost:8000'
-------------
"""
serializer_class = SocialLoginSerializer
def process_login(self):
get_adapter(self.request).login(self.request, self.user)
class SocialConnectView(LoginView):
"""
class used for social account linking
example usage for facebook with access_token
-------------
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
class FacebookConnect(SocialConnectView):
adapter_class = FacebookOAuth2Adapter
-------------
"""
serializer_class = SocialConnectSerializer
permission_classes = (IsAuthenticated,)
def process_login(self):
get_adapter(self.request).login(self.request, self.user)
class SocialAccountListView(ListAPIView):
"""
List SocialAccounts for the currently logged in user
"""
serializer_class = SocialAccountSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return SocialAccount.objects.filter(user=self.request.user)
class SocialAccountDisconnectView(GenericAPIView):
"""
Disconnect SocialAccount from remote service for
the currently logged in user
"""
serializer_class = SocialConnectSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return SocialAccount.objects.filter(user=self.request.user)
def post(self, request, *args, **kwargs):
accounts = self.get_queryset()
account = accounts.filter(pk=kwargs['pk']).first()
if not account:
raise NotFound
get_social_adapter(self.request).validate_disconnect(account, accounts)
account.delete()
signals.social_account_removed.send(
sender=SocialAccount,
request=self.request,
socialaccount=account
)
return Response(self.get_serializer(account).data)

View File

@ -6,12 +6,11 @@ from django.utils.http import urlsafe_base64_decode as uid_decoder
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text
from .models import TokenModel
from rest_framework import serializers, exceptions
from rest_framework.exceptions import ValidationError
from .models import TokenModel
from .utils import import_callable
# Get the UserModel
UserModel = get_user_model()
@ -21,14 +20,11 @@ class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(required=False, allow_blank=True)
password = serializers.CharField(style={'input_type': 'password'})
def authenticate(self, **kwargs):
return authenticate(self.context['request'], **kwargs)
def _validate_email(self, email, password):
user = None
if email and password:
user = self.authenticate(email=email, password=password)
user = authenticate(email=email, password=password)
else:
msg = _('Must include "email" and "password".')
raise exceptions.ValidationError(msg)
@ -39,7 +35,7 @@ class LoginSerializer(serializers.Serializer):
user = None
if username and password:
user = self.authenticate(username=username, password=password)
user = authenticate(username=username, password=password)
else:
msg = _('Must include "username" and "password".')
raise exceptions.ValidationError(msg)
@ -50,9 +46,9 @@ class LoginSerializer(serializers.Serializer):
user = None
if email and password:
user = self.authenticate(email=email, password=password)
user = authenticate(email=email, password=password)
elif username and password:
user = self.authenticate(username=username, password=password)
user = authenticate(username=username, password=password)
else:
msg = _('Must include either "username" or "email" and "password".')
raise exceptions.ValidationError(msg)
@ -74,7 +70,7 @@ class LoginSerializer(serializers.Serializer):
user = self._validate_email(email, password)
# Authentication through username
elif app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
user = self._validate_username(username, password)
# Authentication through either username or email
@ -124,52 +120,36 @@ class TokenSerializer(serializers.ModelSerializer):
class UserDetailsSerializer(serializers.ModelSerializer):
"""
User model w/o password
"""
class Meta:
model = UserModel
fields = ('pk', 'username', 'email', 'first_name', 'last_name')
fields = ('username', 'email', 'first_name', 'last_name')
read_only_fields = ('email', )
class JWTSerializer(serializers.Serializer):
"""
Serializer for JWT authentication.
"""
token = serializers.CharField()
user = serializers.SerializerMethodField()
def get_user(self, obj):
"""
Required to allow using custom USER_DETAILS_SERIALIZER in
JWTSerializer. Defining it here to avoid circular imports
"""
rest_auth_serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
JWTUserDetailsSerializer = import_callable(
rest_auth_serializers.get('USER_DETAILS_SERIALIZER', UserDetailsSerializer)
)
user_data = JWTUserDetailsSerializer(obj['user'], context=self.context).data
return user_data
class PasswordResetSerializer(serializers.Serializer):
"""
Serializer for requesting a password reset e-mail.
"""
email = serializers.EmailField()
password_reset_form_class = PasswordResetForm
def get_email_options(self):
"""Override this method to change default e-mail options"""
""" Override this method to change default e-mail options
"""
return {}
def validate_email(self, value):
# Create PasswordResetForm with the serializer
self.reset_form = self.password_reset_form_class(data=self.initial_data)
if not self.reset_form.is_valid():
raise serializers.ValidationError(self.reset_form.errors)
raise serializers.ValidationError(_('Error'))
return value
@ -190,10 +170,12 @@ class PasswordResetConfirmSerializer(serializers.Serializer):
"""
Serializer for requesting a password reset e-mail.
"""
new_password1 = serializers.CharField(max_length=128)
new_password2 = serializers.CharField(max_length=128)
uid = serializers.CharField()
token = serializers.CharField()
uid = serializers.CharField(required=True)
token = serializers.CharField(required=True)
set_password_form_class = SetPasswordForm
@ -223,10 +205,11 @@ class PasswordResetConfirmSerializer(serializers.Serializer):
return attrs
def save(self):
return self.set_password_form.save()
self.set_password_form.save()
class PasswordChangeSerializer(serializers.Serializer):
old_password = serializers.CharField(max_length=128)
new_password1 = serializers.CharField(max_length=128)
new_password2 = serializers.CharField(max_length=128)
@ -256,8 +239,7 @@ class PasswordChangeSerializer(serializers.Serializer):
)
if all(invalid_password_conditions):
err_msg = _("Your old password was entered incorrectly. Please enter it again.")
raise serializers.ValidationError(err_msg)
raise serializers.ValidationError('Invalid password')
return value
def validate(self, attrs):

View File

@ -1,19 +1,19 @@
from django.conf import settings
from django.http import HttpRequest
from rest_framework import serializers
from requests.exceptions import HTTPError
# Import is needed only if we are using social login, in which
# case the allauth.socialaccount will be declared
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
try:
from allauth.socialaccount.helpers import complete_social_login
from allauth.socialaccount.models import SocialToken
from allauth.socialaccount.providers.oauth.client import OAuthError
except ImportError:
pass
from rest_auth.registration.serializers import SocialConnectMixin
from allauth.socialaccount.models import SocialToken
class TwitterLoginSerializer(serializers.Serializer):
access_token = serializers.CharField()
token_secret = serializers.CharField()
access_token = serializers.CharField(required=True)
token_secret = serializers.CharField(required=True)
def _get_request(self):
request = self.context.get('request')
@ -23,17 +23,15 @@ class TwitterLoginSerializer(serializers.Serializer):
def get_social_login(self, adapter, app, token, response):
"""
:param adapter: allauth.socialaccount Adapter subclass.
Usually OAuthAdapter or Auth2Adapter
:param adapter: allauth.socialaccount Adapter subclass. Usually OAuthAdapter or Auth2Adapter
:param app: `allauth.socialaccount.SocialApp` instance
:param token: `allauth.socialaccount.SocialToken` instance
:param response: Provider's response for OAuth1. Not used in the
:returns: A populated instance of the
`allauth.socialaccount.SocialLoginView` instance
:return: :return: A populated instance of the `allauth.socialaccount.SocialLoginView` instance
"""
request = self._get_request()
social_login = adapter.complete_login(request, app, token,
response=response)
social_login = adapter.complete_login(request, app, token, response=response)
social_login.token = token
return social_login
@ -43,18 +41,21 @@ class TwitterLoginSerializer(serializers.Serializer):
if not view:
raise serializers.ValidationError(
"View is not defined, pass it as a context variable"
'View is not defined, pass it as a context variable'
)
adapter_class = getattr(view, 'adapter_class', None)
if not adapter_class:
raise serializers.ValidationError("Define adapter_class in view")
raise serializers.ValidationError('Define adapter_class in view')
adapter = adapter_class(request)
adapter = adapter_class()
app = adapter.get_provider().get_app(request)
access_token = attrs.get('access_token')
token_secret = attrs.get('token_secret')
if('access_token' in attrs) and ('token_secret' in attrs):
access_token = attrs.get('access_token')
token_secret = attrs.get('token_secret')
else:
raise serializers.ValidationError('Incorrect input. access_token and token_secret are required.')
request.session['oauth_api.twitter.com_access_token'] = {
'oauth_token': access_token,
@ -66,8 +67,8 @@ class TwitterLoginSerializer(serializers.Serializer):
try:
login = self.get_social_login(adapter, app, token, access_token)
complete_social_login(request, login)
except OAuthError as e:
raise serializers.ValidationError(str(e))
except HTTPError:
raise serializers.ValidationError('Incorrect value')
if not login.is_existing:
login.lookup()
@ -75,7 +76,3 @@ class TwitterLoginSerializer(serializers.Serializer):
attrs['user'] = login.account.user
return attrs
class TwitterConnectSerializer(SocialConnectMixin, TwitterLoginSerializer):
pass

View File

@ -1,16 +1,79 @@
# Moved in Django 1.8 from django to tests/auth_tests/urls.py
from django.conf.urls import url
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth import views
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.urls import urlpatterns
from django.contrib.messages.api import info
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from django.template import RequestContext, Template
from django.views.decorators.cache import never_cache
class CustomRequestAuthenticationForm(AuthenticationForm):
def __init__(self, request, *args, **kwargs):
assert isinstance(request, HttpRequest)
super(CustomRequestAuthenticationForm, self).__init__(request, *args, **kwargs)
@never_cache
def remote_user_auth_view(request):
"""
Dummy view for remote user tests
"""
t = Template("Username is {{ user }}.")
c = RequestContext(request, {})
return HttpResponse(t.render(c))
def auth_processor_no_attr_access(request):
render(request, 'context_processors/auth_attrs_no_access.html')
# *After* rendering, we check whether the session was accessed
return render(request,
'context_processors/auth_attrs_test_access.html',
{'session_accessed': request.session.accessed})
def auth_processor_attr_access(request):
render(request, 'context_processors/auth_attrs_access.html')
return render(request,
'context_processors/auth_attrs_test_access.html',
{'session_accessed': request.session.accessed})
def auth_processor_user(request):
return render(request, 'context_processors/auth_attrs_user.html')
def auth_processor_perms(request):
return render(request, 'context_processors/auth_attrs_perms.html')
def auth_processor_perm_in_perms(request):
return render(request, 'context_processors/auth_attrs_perm_in_perms.html')
def auth_processor_messages(request):
info(request, "Message 1")
return render(request, 'context_processors/auth_attrs_messages.html')
def userpage(request):
pass
def custom_request_auth_login(request):
return views.login(request, authentication_form=CustomRequestAuthenticationForm)
# special urls for auth test cases
urlpatterns += [
url(r'^logout/custom_query/$', views.logout, dict(redirect_field_name='follow')),
url(r'^logout/next_page/$', views.logout, dict(next_page='/somewhere/')),
url(r'^logout/next_page/named/$', views.logout, dict(next_page='password_reset')),
url(r'^remote_user/$', remote_user_auth_view),
url(r'^password_reset_from_email/$', views.password_reset, dict(from_email='staffmember@example.com')),
url(r'^password_reset/custom_redirect/$', views.password_reset, dict(post_reset_redirect='/custom/')),
url(r'^password_reset/custom_redirect/named/$', views.password_reset, dict(post_reset_redirect='password_reset')),
@ -27,4 +90,16 @@ urlpatterns += [
url(r'^admin_password_reset/$', views.password_reset, dict(is_admin_site=True)),
url(r'^login_required/$', login_required(views.password_reset)),
url(r'^login_required_login_url/$', login_required(views.password_reset, login_url='/somewhere/')),
url(r'^auth_processor_no_attr_access/$', auth_processor_no_attr_access),
url(r'^auth_processor_attr_access/$', auth_processor_attr_access),
url(r'^auth_processor_user/$', auth_processor_user),
url(r'^auth_processor_perms/$', auth_processor_perms),
url(r'^auth_processor_perm_in_perms/$', auth_processor_perm_in_perms),
url(r'^auth_processor_messages/$', auth_processor_messages),
url(r'^custom_request_auth_login/$', custom_request_auth_login),
url(r'^userpage/(.+)/$', userpage, name="userpage"),
# This line is only required to render the password reset with is_admin=True
url(r'^admin/', include(admin.site.urls)),
]

View File

@ -1,5 +1,3 @@
django-allauth>=0.25.0
django-allauth>=0.19.1
responses>=0.3.0
flake8==2.4.0
djangorestframework-jwt>=1.7.2
djangorestframework>=3.6.4

View File

@ -25,7 +25,7 @@ DATABASES = {
}
}
MIDDLEWARE = [
MIDDLEWARE_CLASSES = [
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@ -33,9 +33,6 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware'
]
# Adding for backwards compatibility for Django 1.8 tests
MIDDLEWARE_CLASSES = MIDDLEWARE
TEMPLATE_CONTEXT_PROCESSORS = [
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
@ -48,27 +45,6 @@ TEMPLATE_CONTEXT_PROCESSORS = [
"allauth.socialaccount.context_processors.socialaccount",
]
# avoid deprecation warnings during tests
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
# insert your TEMPLATE_DIRS here
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': TEMPLATE_CONTEXT_PROCESSORS,
},
},
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
)
}
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
@ -83,24 +59,14 @@ INSTALLED_APPS = [
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.facebook',
'allauth.socialaccount.providers.twitter',
'rest_framework',
'rest_framework.authtoken',
'rest_auth',
'rest_auth.registration',
'rest_framework_jwt'
'rest_auth.registration'
]
SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd"
ACCOUNT_ACTIVATION_DAYS = 1
SITE_ID = 1
AUTHENTICATION_BACKENDS = (
# Needed to login by username in Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
# `allauth` specific authentication methods, such as login by e-mail
'allauth.account.auth_backends.AuthenticationBackend',
)

View File

@ -1,33 +1,24 @@
from django.test import TestCase, override_settings
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.core import mail
from django.conf import settings
from django.test.utils import override_settings
from django.utils.encoding import force_text
from allauth.account import app_settings as account_app_settings
from rest_framework import status
from rest_framework.test import APIRequestFactory
from rest_auth.registration.views import RegisterView
from rest_auth.registration.app_settings import register_permission_classes
from .mixins import TestsMixin, CustomPermissionClass
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
from .test_base import BaseAPITestCase
@override_settings(ROOT_URLCONF="tests.urls")
class APIBasicTests(TestsMixin, TestCase):
class APITestCase1(TestCase, BaseAPITestCase):
"""
Case #1:
- user profile: defined
- custom registration: backend defined
"""
# urls = 'tests.urls'
urls = 'tests.urls'
USERNAME = 'person'
PASS = 'person'
@ -66,36 +57,7 @@ class APIBasicTests(TestsMixin, TestCase):
result['token'] = default_token_generator.make_token(user)
return result
@override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.EMAIL)
def test_login_failed_email_validation(self):
payload = {
"email": '',
"password": self.PASS
}
resp = self.post(self.login_url, data=payload, status_code=400)
self.assertEqual(resp.json['non_field_errors'][0], u'Must include "email" and "password".')
@override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.USERNAME)
def test_login_failed_username_validation(self):
payload = {
"username": '',
"password": self.PASS
}
resp = self.post(self.login_url, data=payload, status_code=400)
self.assertEqual(resp.json['non_field_errors'][0], u'Must include "username" and "password".')
@override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.USERNAME_EMAIL)
def test_login_failed_username_email_validation(self):
payload = {
"password": self.PASS
}
resp = self.post(self.login_url, data=payload, status_code=400)
self.assertEqual(resp.json['non_field_errors'][0], u'Must include either "username" or "email" and "password".')
def test_allauth_login_with_username(self):
def test_login(self):
payload = {
"username": self.USERNAME,
"password": self.PASS
@ -129,34 +91,6 @@ class APIBasicTests(TestsMixin, TestCase):
# test empty payload
self.post(self.login_url, data={}, status_code=400)
@override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.EMAIL)
def test_allauth_login_with_email(self):
payload = {
"email": self.EMAIL,
"password": self.PASS
}
# there is no users in db so it should throw error (400)
self.post(self.login_url, data=payload, status_code=400)
self.post(self.password_change_url, status_code=403)
# create user
get_user_model().objects.create_user(self.EMAIL, email=self.EMAIL, password=self.PASS)
self.post(self.login_url, data=payload, status_code=200)
@override_settings(REST_USE_JWT=True)
def test_login_jwt(self):
payload = {
"username": self.USERNAME,
"password": self.PASS
}
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
self.post(self.login_url, data=payload, status_code=200)
self.assertEqual('token' in self.response.json.keys(), True)
self.token = self.response.json['token']
def test_login_by_email(self):
# starting test without allauth app
settings.INSTALLED_APPS.remove('allauth')
@ -202,9 +136,6 @@ class APIBasicTests(TestsMixin, TestCase):
# test empty payload
self.post(self.login_url, data={}, status_code=400)
# bring back allauth
settings.INSTALLED_APPS.append('allauth')
def test_password_change(self):
login_payload = {
"username": self.USERNAME,
@ -376,21 +307,6 @@ class APIBasicTests(TestsMixin, TestCase):
self.assertEqual(user.last_name, self.response.json['last_name'])
self.assertEqual(user.email, self.response.json['email'])
@override_settings(REST_USE_JWT=True)
def test_user_details_using_jwt(self):
user = get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS)
payload = {
"username": self.USERNAME,
"password": self.PASS
}
self.post(self.login_url, data=payload, status_code=200)
self.token = self.response.json['token']
self.get(self.user_url, status_code=200)
self.patch(self.user_url, data=self.BASIC_USER_DATA, status_code=200)
user = get_user_model().objects.get(pk=user.pk)
self.assertEqual(user.email, self.response.json['email'])
def test_registration(self):
user_count = get_user_model().objects.all().count()
@ -407,33 +323,6 @@ class APIBasicTests(TestsMixin, TestCase):
self._login()
self._logout()
@override_settings(REST_AUTH_REGISTER_PERMISSION_CLASSES=(CustomPermissionClass,))
def test_registration_with_custom_permission_class(self):
class CustomRegisterView(RegisterView):
permission_classes = register_permission_classes()
authentication_classes = ()
factory = APIRequestFactory()
request = factory.post('/customer/details', self.REGISTRATION_DATA, format='json')
response = CustomRegisterView.as_view()(request)
self.assertEqual(response.data['detail'], CustomPermissionClass.message)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
@override_settings(REST_USE_JWT=True)
def test_registration_with_jwt(self):
user_count = get_user_model().objects.all().count()
self.post(self.register_url, data={}, status_code=400)
result = self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201)
self.assertIn('token', result.data)
self.assertEqual(get_user_model().objects.all().count(), user_count + 1)
self._login()
self._logout()
def test_registration_with_invalid_password(self):
data = self.REGISTRATION_DATA.copy()
data['password2'] = 'foobar'
@ -442,8 +331,7 @@ class APIBasicTests(TestsMixin, TestCase):
@override_settings(
ACCOUNT_EMAIL_VERIFICATION='mandatory',
ACCOUNT_EMAIL_REQUIRED=True,
ACCOUNT_EMAIL_CONFIRMATION_HMAC=False
ACCOUNT_EMAIL_REQUIRED=True
)
def test_registration_with_email_verification(self):
user_count = get_user_model().objects.all().count()
@ -482,7 +370,7 @@ class APIBasicTests(TestsMixin, TestCase):
email_confirmation = new_user.emailaddress_set.get(email=self.EMAIL)\
.emailconfirmation_set.order_by('-created')[0]
self.post(
self.verify_email_url,
self.veirfy_email_url,
data={"key": email_confirmation.key},
status_code=status.HTTP_200_OK
)
@ -490,29 +378,3 @@ class APIBasicTests(TestsMixin, TestCase):
# try to login again
self._login()
self._logout()
@override_settings(ACCOUNT_LOGOUT_ON_GET=True)
def test_logout_on_get(self):
payload = {
"username": self.USERNAME,
"password": self.PASS
}
# create user
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
self.post(self.login_url, data=payload, status_code=200)
self.get(self.logout_url, status=status.HTTP_200_OK)
@override_settings(ACCOUNT_LOGOUT_ON_GET=False)
def test_logout_on_post_only(self):
payload = {
"username": self.USERNAME,
"password": self.PASS
}
# create user
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
self.post(self.login_url, data=payload, status_code=status.HTTP_200_OK)
self.get(self.logout_url, status_code=status.HTTP_405_METHOD_NOT_ALLOWED)

View File

@ -1,23 +1,11 @@
import json
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.client import Client, MULTIPART_CONTENT
from django.utils.encoding import force_text
from rest_framework import status
from rest_framework import permissions
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
class CustomPermissionClass(permissions.BasePermission):
message = 'You shall not pass!'
def has_permission(self, request, view):
return False
class APIClient(Client):
@ -29,7 +17,8 @@ class APIClient(Client):
return self.generic('OPTIONS', path, data, content_type, **extra)
class TestsMixin(object):
class BaseAPITestCase(object):
"""
base for API tests:
* easy request calls, f.e.: self.post(url, data), self.get(url)
@ -48,22 +37,17 @@ class TestsMixin(object):
# check_headers = kwargs.pop('check_headers', True)
if hasattr(self, 'token'):
if getattr(settings, 'REST_USE_JWT', False):
kwargs['HTTP_AUTHORIZATION'] = 'JWT %s' % self.token
else:
kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token
kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token
self.response = request_func(*args, **kwargs)
is_json = bool(
[x for x in self.response._headers['content-type'] if 'json' in x])
self.response.json = {}
if is_json and self.response.content:
self.response.json = json.loads(force_text(self.response.content))
else:
self.response.json = {}
if status_code:
self.assertEqual(self.response.status_code, status_code)
return self.response
def post(self, *args, **kwargs):
@ -75,6 +59,29 @@ class TestsMixin(object):
def patch(self, *args, **kwargs):
return self.send_request('patch', *args, **kwargs)
# def put(self, *args, **kwargs):
# return self.send_request('put', *args, **kwargs)
# def delete(self, *args, **kwargs):
# return self.send_request('delete', *args, **kwargs)
# def options(self, *args, **kwargs):
# return self.send_request('options', *args, **kwargs)
# def post_file(self, *args, **kwargs):
# kwargs['content_type'] = MULTIPART_CONTENT
# return self.send_request('post', *args, **kwargs)
# def get_file(self, *args, **kwargs):
# content_type = None
# if 'content_type' in kwargs:
# content_type = kwargs.pop('content_type')
# response = self.send_request('get', *args, **kwargs)
# if content_type:
# self.assertEqual(
# bool(filter(lambda x: content_type in x, response._headers['content-type'])), True)
# return response
def init(self):
settings.DEBUG = True
self.client = APIClient()
@ -85,14 +92,8 @@ class TestsMixin(object):
self.register_url = reverse('rest_register')
self.password_reset_url = reverse('rest_password_reset')
self.user_url = reverse('rest_user_details')
self.verify_email_url = reverse('rest_verify_email')
self.veirfy_email_url = reverse('rest_verify_email')
self.fb_login_url = reverse('fb_login')
self.tw_login_url = reverse('tw_login')
self.tw_login_no_view_url = reverse('tw_login_no_view')
self.tw_login_no_adapter_url = reverse('tw_login_no_adapter')
self.fb_connect_url = reverse('fb_connect')
self.tw_connect_url = reverse('tw_connect')
self.social_account_list_url = reverse('social_account_list')
def _login(self):
payload = {

View File

@ -1,26 +1,20 @@
import json
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.test.utils import override_settings
from django.contrib.sites.models import Site
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
from allauth.socialaccount.models import SocialApp
from allauth.socialaccount.providers.facebook.provider import GRAPH_API_URL
import responses
from rest_framework import status
from .mixins import TestsMixin
from .test_base import BaseAPITestCase
@override_settings(ROOT_URLCONF="tests.urls")
class TestSocialAuth(TestsMixin, TestCase):
class TestSocialAuth(TestCase, BaseAPITestCase):
urls = 'tests.urls'
USERNAME = 'person'
PASS = 'person'
@ -41,19 +35,9 @@ class TestSocialAuth(TestsMixin, TestCase):
client_id='123123123',
secret='321321321',
)
twitter_social_app = SocialApp.objects.create(
provider='twitter',
name='Twitter',
client_id='11223344',
secret='55667788',
)
site = Site.objects.get_current()
social_app.sites.add(site)
twitter_social_app.sites.add(site)
self.graph_api_url = GRAPH_API_URL + '/me'
self.twitter_url = 'http://twitter.com/foobarme'
@responses.activate
def test_failed_social_auth(self):
@ -74,221 +58,6 @@ class TestSocialAuth(TestsMixin, TestCase):
@responses.activate
def test_social_auth(self):
# fake response for facebook call
resp_body = {
"id": "123123123123",
"first_name": "John",
"gender": "male",
"last_name": "Smith",
"link": "https://www.facebook.com/john.smith",
"locale": "en_US",
"name": "John Smith",
"timezone": 2,
"updated_time": "2014-08-13T10:14:38+0000",
"username": "john.smith",
"verified": True
}
responses.add(
responses.GET,
self.graph_api_url,
body=json.dumps(resp_body),
status=200,
content_type='application/json'
)
users_count = get_user_model().objects.all().count()
payload = {
'access_token': 'abc123'
}
self.post(self.fb_login_url, data=payload, status_code=200)
self.assertIn('key', self.response.json.keys())
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
# make sure that second request will not create a new user
self.post(self.fb_login_url, data=payload, status_code=200)
self.assertIn('key', self.response.json.keys())
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
def _twitter_social_auth(self):
# fake response for twitter call
resp_body = {
"id": "123123123123",
}
responses.add(
responses.GET,
'https://api.twitter.com/1.1/account/verify_credentials.json',
body=json.dumps(resp_body),
status=200,
content_type='application/json'
)
users_count = get_user_model().objects.all().count()
payload = {
'access_token': 'abc123',
'token_secret': '1111222233334444'
}
self.post(self.tw_login_url, data=payload)
self.assertIn('key', self.response.json.keys())
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
# make sure that second request will not create a new user
self.post(self.tw_login_url, data=payload, status_code=200)
self.assertIn('key', self.response.json.keys())
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
@responses.activate
@override_settings(SOCIALACCOUNT_AUTO_SIGNUP=True)
def test_twitter_social_auth(self):
self._twitter_social_auth()
@responses.activate
@override_settings(SOCIALACCOUNT_AUTO_SIGNUP=False)
def test_twitter_social_auth_without_auto_singup(self):
self._twitter_social_auth()
@responses.activate
def test_twitter_social_auth_request_error(self):
# fake response for twitter call
resp_body = {
"id": "123123123123",
}
responses.add(
responses.GET,
'https://api.twitter.com/1.1/account/verify_credentials.json',
body=json.dumps(resp_body),
status=400,
content_type='application/json'
)
users_count = get_user_model().objects.all().count()
payload = {
'access_token': 'abc123',
'token_secret': '1111222233334444'
}
self.post(self.tw_login_url, data=payload, status_code=400)
self.assertNotIn('key', self.response.json.keys())
self.assertEqual(get_user_model().objects.all().count(), users_count)
@responses.activate
def test_twitter_social_auth_no_view_in_context(self):
# fake response for twitter call
resp_body = {
"id": "123123123123",
}
responses.add(
responses.GET,
'https://api.twitter.com/1.1/account/verify_credentials.json',
body=json.dumps(resp_body),
status=400,
content_type='application/json'
)
users_count = get_user_model().objects.all().count()
payload = {
'access_token': 'abc123',
'token_secret': '1111222233334444'
}
self.post(self.tw_login_no_view_url, data=payload, status_code=400)
self.assertEqual(get_user_model().objects.all().count(), users_count)
@responses.activate
def test_twitter_social_auth_no_adapter(self):
# fake response for twitter call
resp_body = {
"id": "123123123123",
}
responses.add(
responses.GET,
'https://api.twitter.com/1.1/account/verify_credentials.json',
body=json.dumps(resp_body),
status=400,
content_type='application/json'
)
users_count = get_user_model().objects.all().count()
payload = {
'access_token': 'abc123',
'token_secret': '1111222233334444'
}
self.post(self.tw_login_no_adapter_url, data=payload, status_code=400)
self.assertEqual(get_user_model().objects.all().count(), users_count)
@responses.activate
@override_settings(
ACCOUNT_EMAIL_VERIFICATION='mandatory',
ACCOUNT_EMAIL_REQUIRED=True,
REST_SESSION_LOGIN=False,
ACCOUNT_EMAIL_CONFIRMATION_HMAC=False
)
def test_email_clash_with_existing_account(self):
resp_body = {
"id": "123123123123",
"first_name": "John",
"gender": "male",
"last_name": "Smith",
"link": "https://www.facebook.com/john.smith",
"locale": "en_US",
"name": "John Smith",
"timezone": 2,
"updated_time": "2014-08-13T10:14:38+0000",
"username": "john.smith",
"verified": True,
"email": self.EMAIL
}
responses.add(
responses.GET,
self.graph_api_url,
body=json.dumps(resp_body),
status=200,
content_type='application/json'
)
# test empty payload
self.post(self.register_url, data={}, status_code=400)
# register user and send email confirmation
self.post(
self.register_url,
data=self.REGISTRATION_DATA,
status_code=201
)
new_user = get_user_model().objects.latest('id')
self.assertEqual(new_user.username, self.REGISTRATION_DATA['username'])
# verify email
email_confirmation = new_user.emailaddress_set.get(email=self.EMAIL)\
.emailconfirmation_set.order_by('-created')[0]
self.post(
self.verify_email_url,
data={"key": email_confirmation.key},
status_code=status.HTTP_200_OK
)
self._login()
self._logout()
# fb log in with already existing email
payload = {
'access_token': 'abc123'
}
self.post(self.fb_login_url, data=payload, status_code=400)
@responses.activate
@override_settings(
REST_USE_JWT=True
)
def test_jwt(self):
resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true}' # noqa
responses.add(
responses.GET,
@ -304,144 +73,55 @@ class TestSocialAuth(TestsMixin, TestCase):
}
self.post(self.fb_login_url, data=payload, status_code=200)
self.assertIn('token', self.response.json.keys())
self.assertIn('user', self.response.json.keys())
self.assertIn('key', self.response.json.keys())
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
@override_settings(ROOT_URLCONF="tests.urls")
class TestSocialConnectAuth(TestsMixin, TestCase):
USERNAME = 'person'
PASS = 'person'
EMAIL = "person1@world.com"
REGISTRATION_DATA = {
"username": USERNAME,
"password1": PASS,
"password2": PASS,
"email": EMAIL
}
def setUp(self):
self.init()
facebook_social_app = SocialApp.objects.create(
provider='facebook',
name='Facebook',
client_id='123123123',
secret='321321321',
)
twitter_social_app = SocialApp.objects.create(
provider='twitter',
name='Twitter',
client_id='11223344',
secret='55667788',
)
site = Site.objects.get_current()
facebook_social_app.sites.add(site)
twitter_social_app.sites.add(site)
self.graph_api_url = GRAPH_API_URL + '/me'
self.twitter_url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
# make sure that second request will not create a new user
self.post(self.fb_login_url, data=payload, status_code=200)
self.assertIn('key', self.response.json.keys())
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
@responses.activate
def test_social_connect_no_auth(self):
@override_settings(
ACCOUNT_EMAIL_VERIFICATION='mandatory',
ACCOUNT_EMAIL_REQUIRED=True,
REST_SESSION_LOGIN=False
)
def test_edge_case(self):
resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true,"email":"%s"}' # noqa
responses.add(
responses.GET,
self.graph_api_url,
body='',
body=resp_body % self.EMAIL,
status=200,
content_type='application/json'
)
payload = {
'access_token': 'abc123'
}
self.post(self.fb_connect_url, data=payload, status_code=403)
self.post(self.tw_connect_url, data=payload, status_code=403)
@responses.activate
def test_social_connect(self):
# register user
# test empty payload
self.post(self.register_url, data={}, status_code=400)
self.post(
self.register_url,
data=self.REGISTRATION_DATA,
status_code=201
)
new_user = get_user_model().objects.latest('id')
self.assertEqual(new_user.username, self.REGISTRATION_DATA['username'])
# Test Facebook
resp_body = {
"id": "123123123123",
"first_name": "John",
"gender": "male",
"last_name": "Smith",
"link": "https://www.facebook.com/john.smith",
"locale": "en_US",
"name": "John Smith",
"timezone": 2,
"updated_time": "2014-08-13T10:14:38+0000",
"username": "john.smith",
"verified": True
}
responses.add(
responses.GET,
self.graph_api_url,
body=json.dumps(resp_body),
status=200,
content_type='application/json'
# verify email
email_confirmation = new_user.emailaddress_set.get(email=self.EMAIL)\
.emailconfirmation_set.order_by('-created')[0]
self.post(
self.veirfy_email_url,
data={"key": email_confirmation.key},
status_code=status.HTTP_200_OK
)
self._login()
self._logout()
payload = {
'access_token': 'abc123'
}
self.post(self.fb_connect_url, data=payload, status_code=200)
self.post(self.fb_login_url, data=payload, status_code=200)
self.assertIn('key', self.response.json.keys())
# Test Twitter
resp_body = {
"id": "123123123123",
}
responses.add(
responses.GET,
self.twitter_url,
body=json.dumps(resp_body),
status=200,
content_type='application/json'
)
payload = {
'access_token': 'abc123',
'token_secret': '1111222233334444'
}
self.post(self.tw_connect_url, data=payload)
self.assertIn('key', self.response.json.keys())
# Check current social accounts
self.get(self.social_account_list_url)
self.assertEqual(len(self.response.json), 2)
self.assertEqual(self.response.json[0]['provider'], 'facebook')
self.assertEqual(self.response.json[1]['provider'], 'twitter')
facebook_social_account_id = self.response.json[0]['id']
# Try disconnecting accounts
self.incorrect_disconnect_url = reverse(
'social_account_disconnect', args=[999999999]
)
self.post(self.incorrect_disconnect_url, status_code=404)
self.disconnect_url = reverse(
'social_account_disconnect', args=[facebook_social_account_id]
)
self.post(self.disconnect_url, status_code=200)
# Check social accounts after disconnecting
self.get(self.social_account_list_url)
self.assertEqual(len(self.response.json), 1)
self.assertEqual(self.response.json[0]['provider'], 'twitter')

View File

@ -3,70 +3,21 @@ from django.views.generic import TemplateView
from . import django_urls
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
from rest_framework.decorators import api_view
from rest_auth.urls import urlpatterns
from rest_auth.registration.views import (
SocialLoginView, SocialConnectView, SocialAccountListView,
SocialAccountDisconnectView
)
from rest_auth.social_serializers import (
TwitterLoginSerializer, TwitterConnectSerializer
)
from rest_auth.registration.views import SocialLoginView
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
class TwitterLogin(SocialLoginView):
adapter_class = TwitterOAuthAdapter
serializer_class = TwitterLoginSerializer
class FacebookConnect(SocialConnectView):
adapter_class = FacebookOAuth2Adapter
class TwitterConnect(SocialConnectView):
adapter_class = TwitterOAuthAdapter
serializer_class = TwitterConnectSerializer
class TwitterLoginSerializerFoo(TwitterLoginSerializer):
pass
@api_view(['POST'])
def twitter_login_view(request):
serializer = TwitterLoginSerializerFoo(
data={'access_token': '11223344', 'token_secret': '55667788'},
context={'request': request}
)
serializer.is_valid(raise_exception=True)
class TwitterLoginNoAdapter(SocialLoginView):
serializer_class = TwitterLoginSerializer
urlpatterns += [
url(r'^rest-registration/', include('rest_auth.registration.urls')),
url(r'^test-admin/', include(django_urls)),
url(r'^account-email-verification-sent/$', TemplateView.as_view(),
name='account_email_verification_sent'),
url(r'^account-confirm-email/(?P<key>[-:\w]+)/$', TemplateView.as_view(),
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
name='account_confirm_email'),
url(r'^social-login/facebook/$', FacebookLogin.as_view(), name='fb_login'),
url(r'^social-login/twitter/$', TwitterLogin.as_view(), name='tw_login'),
url(r'^social-login/twitter-no-view/$', twitter_login_view, name='tw_login_no_view'),
url(r'^social-login/twitter-no-adapter/$', TwitterLoginNoAdapter.as_view(), name='tw_login_no_adapter'),
url(r'^social-login/facebook/connect/$', FacebookConnect.as_view(), name='fb_connect'),
url(r'^social-login/twitter/connect/$', TwitterConnect.as_view(), name='tw_connect'),
url(r'^socialaccounts/$', SocialAccountListView.as_view(), name='social_account_list'),
url(r'^socialaccounts/(?P<pk>\d+)/disconnect/$', SocialAccountDisconnectView.as_view(),
name='social_account_disconnect'),
url(r'^accounts/', include('allauth.socialaccount.urls'))
]

View File

@ -14,16 +14,3 @@ def import_callable(path_or_callable):
def default_create_token(token_model, user, serializer):
token, _ = token_model.objects.get_or_create(user=user)
return token
def jwt_encode(user):
try:
from rest_framework_jwt.settings import api_settings
except ImportError:
raise ImportError("djangorestframework_jwt needs to be installed")
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
return jwt_encode_handler(payload)

View File

@ -1,36 +1,25 @@
from django.contrib.auth import (
login as django_login,
logout as django_logout
)
from django.contrib.auth import login, logout
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_post_parameters
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.generics import RetrieveUpdateAPIView
from .app_settings import (
TokenSerializer, UserDetailsSerializer, LoginSerializer,
PasswordResetSerializer, PasswordResetConfirmSerializer,
PasswordChangeSerializer, JWTSerializer, create_token
PasswordChangeSerializer, create_token
)
from .models import TokenModel
from .utils import jwt_encode
sensitive_post_parameters_m = method_decorator(
sensitive_post_parameters(
'password', 'old_password', 'new_password1', 'new_password2'
)
)
class LoginView(GenericAPIView):
"""
Check the credentials and return the REST Token
if the credentials are valid and authenticated.
@ -43,70 +32,28 @@ class LoginView(GenericAPIView):
permission_classes = (AllowAny,)
serializer_class = LoginSerializer
token_model = TokenModel
@sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(LoginView, self).dispatch(*args, **kwargs)
def process_login(self):
django_login(self.request, self.user)
def get_response_serializer(self):
if getattr(settings, 'REST_USE_JWT', False):
response_serializer = JWTSerializer
else:
response_serializer = TokenSerializer
return response_serializer
response_serializer = TokenSerializer
def login(self):
self.user = self.serializer.validated_data['user']
if getattr(settings, 'REST_USE_JWT', False):
self.token = jwt_encode(self.user)
else:
self.token = create_token(self.token_model, self.user,
self.serializer)
self.token = create_token(self.token_model, self.user, self.serializer)
if getattr(settings, 'REST_SESSION_LOGIN', True):
self.process_login()
login(self.request, self.user)
def get_response(self):
serializer_class = self.get_response_serializer()
if getattr(settings, 'REST_USE_JWT', False):
data = {
'user': self.user,
'token': self.token
}
serializer = serializer_class(instance=data,
context={'request': self.request})
else:
serializer = serializer_class(instance=self.token,
context={'request': self.request})
response = Response(serializer.data, status=status.HTTP_200_OK)
if getattr(settings, 'REST_USE_JWT', False):
from rest_framework_jwt.settings import api_settings as jwt_settings
if jwt_settings.JWT_AUTH_COOKIE:
from datetime import datetime
expiration = (datetime.utcnow() + jwt_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(jwt_settings.JWT_AUTH_COOKIE,
self.token,
expires=expiration,
httponly=True)
return response
return Response(
self.response_serializer(self.token).data, status=status.HTTP_200_OK
)
def post(self, request, *args, **kwargs):
self.request = request
self.serializer = self.get_serializer(data=self.request.data,
context={'request': request})
self.serializer = self.get_serializer(data=self.request.data)
self.serializer.is_valid(raise_exception=True)
self.login()
return self.get_response()
class LogoutView(APIView):
"""
Calls Django logout method and delete the Token object
assigned to the current User object.
@ -115,44 +62,27 @@ class LogoutView(APIView):
"""
permission_classes = (AllowAny,)
def get(self, request, *args, **kwargs):
if getattr(settings, 'ACCOUNT_LOGOUT_ON_GET', False):
response = self.logout(request)
else:
response = self.http_method_not_allowed(request, *args, **kwargs)
return self.finalize_response(request, response, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.logout(request)
def logout(self, request):
def post(self, request):
try:
request.user.auth_token.delete()
except (AttributeError, ObjectDoesNotExist):
pass
if getattr(settings, 'REST_SESSION_LOGIN', True):
django_logout(request)
response = Response({"detail": _("Successfully logged out.")},
status=status.HTTP_200_OK)
if getattr(settings, 'REST_USE_JWT', False):
from rest_framework_jwt.settings import api_settings as jwt_settings
if jwt_settings.JWT_AUTH_COOKIE:
response.delete_cookie(jwt_settings.JWT_AUTH_COOKIE)
return response
logout(request)
return Response({"success": _("Successfully logged out.")},
status=status.HTTP_200_OK)
class UserDetailsView(RetrieveUpdateAPIView):
"""
Reads and updates UserModel fields
Accepts GET, PUT, PATCH methods.
Returns User's details in JSON format.
Default accepted fields: username, first_name, last_name
Default display fields: pk, username, email, first_name, last_name
Read-only fields: pk, email
Returns UserModel fields.
Accepts the following GET parameters: token
Accepts the following POST parameters:
Required: token
Optional: email, first_name, last_name and UserProfile fields
Returns the updated UserProfile and/or User object.
"""
serializer_class = UserDetailsSerializer
permission_classes = (IsAuthenticated,)
@ -160,22 +90,16 @@ class UserDetailsView(RetrieveUpdateAPIView):
def get_object(self):
return self.request.user
def get_queryset(self):
"""
Adding this method since it is sometimes called when using
django-rest-swagger
https://github.com/Tivix/django-rest-auth/issues/275
"""
return get_user_model().objects.none()
class PasswordResetView(GenericAPIView):
"""
Calls Django Auth PasswordResetForm save method.
Accepts the following POST parameters: email
Returns the success/fail message.
"""
serializer_class = PasswordResetSerializer
permission_classes = (AllowAny,)
@ -187,34 +111,28 @@ class PasswordResetView(GenericAPIView):
serializer.save()
# Return the success message with OK HTTP status
return Response(
{"detail": _("Password reset e-mail has been sent.")},
{"success": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
class PasswordResetConfirmView(GenericAPIView):
"""
Password reset e-mail link is confirmed, therefore
this resets the user's password.
Password reset e-mail link is confirmed, therefore this resets the user's password.
Accepts the following POST parameters: token, uid,
new_password1, new_password2
Accepts the following POST parameters: new_password1, new_password2
Accepts the following Django URL arguments: token, uid
Returns the success/fail message.
"""
serializer_class = PasswordResetConfirmSerializer
permission_classes = (AllowAny,)
@sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
{"detail": _("Password has been reset with the new password.")}
)
return Response({"success": _("Password has been reset with the new password.")})
class PasswordChangeView(GenericAPIView):
@ -224,15 +142,12 @@ class PasswordChangeView(GenericAPIView):
Accepts the following POST parameters: new_password1, new_password2
Returns the success/fail message.
"""
serializer_class = PasswordChangeSerializer
permission_classes = (IsAuthenticated,)
@sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(PasswordChangeView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail": _("New password has been saved.")})
return Response({"success": _("New password has been saved.")})

View File

@ -1,39 +0,0 @@
[bdist_wheel]
universal = 1
[metadata]
license_file = LICENSE
[flake8]
max-line-length = 120
exclude = docs/*,demo/*
ignore = F403
[coverage:run]
omit=*site-packages*,*distutils*,*migrations*
[coverage:report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
ignore_errors = True
[coverage:html]
directory = coverage_html

View File

@ -1,9 +1,15 @@
#!/usr/bin/env python
import os
from setuptools import setup, find_packages
try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
import os
here = os.path.dirname(os.path.abspath(__file__))
f = open(os.path.join(here, 'README.rst'))
long_description = f.read().strip()
@ -12,7 +18,7 @@ f.close()
setup(
name='django-rest-auth',
version='0.9.5',
version='0.6.0',
author='Sumit Chachra',
author_email='chachra@tivix.com',
url='http://github.com/Tivix/django-rest-auth',
@ -22,20 +28,20 @@ setup(
keywords='django rest auth registration rest-framework django-registration api',
zip_safe=False,
install_requires=[
'Django>=1.8.0',
'djangorestframework>=3.1.3',
'Django>=1.7.0',
'djangorestframework>=3.1.0',
'six>=1.9.0',
],
extras_require={
'with_social': ['django-allauth>=0.25.0'],
'with_social': ['django-allauth>=0.24.1'],
},
tests_require=[
'responses>=0.5.0',
'django-allauth>=0.25.0',
'djangorestframework-jwt>=1.9.0',
'django-allauth>=0.24.1',
],
test_suite='runtests.runtests',
include_package_data=True,
# cmdclass={},
classifiers=[
'Framework :: Django',
'Intended Audience :: Developers',