I wanted to add social media auth endpoints from dj-rest-auth third-party app. I did everything from the dj-rest-auth documentation.
I am getting this strange error, though:
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/views/generic/base.py", line 103, in view
return self.dispatch(request, *args, **kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/utils/decorators.py", line 46, in _wrapper
return bound_method(*args, **kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/views/decorators/debug.py", line 92, in sensitive_post_parameters_wrapper
return view(request, *args, **kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/dj_rest_auth/views.py", line 53, in dispatch
return super().dispatch(*args, **kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/rest_framework/views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/rest_framework/views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
raise exc
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/rest_framework/views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/dj_rest_auth/views.py", line 130, in post
self.serializer.is_valid(raise_exception=True)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/rest_framework/serializers.py", line 227, in is_valid
self._validated_data = self.run_validation(self.initial_data)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/rest_framework/serializers.py", line 429, in run_validation
value = self.validate(value)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/dj_rest_auth/serializers.py", line 121, in validate
user = self.get_auth_user(username, email, password)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/dj_rest_auth/serializers.py", line 96, in get_auth_user
return self.get_auth_user_using_allauth(username, email, password)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/dj_rest_auth/serializers.py", line 62, in get_auth_user_using_allauth
return self._validate_email(email, password)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/dj_rest_auth/serializers.py", line 30, in _validate_email
user = self.authenticate(email=email, password=password)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/dj_rest_auth/serializers.py", line 26, in authenticate
return authenticate(self.context['request'], **kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/views/decorators/debug.py", line 42, in sensitive_variables_wrapper
return func(*func_args, **func_kwargs)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/contrib/auth/__init__.py", line 68, in authenticate
for backend, backend_path in _get_backends(return_tuples=True):
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/contrib/auth/__init__.py", line 27, in _get_backends
backend = load_backend(backend_path)
File "/home/vlad/projects/DjangoProjects/portfolioProjects/resume_website_restapi/env/lib/python3.10/site-packages/django/contrib/auth/__init__.py", line 21, in load_backend
return import_string(path)()
TypeError: BaseAuth.__init__() missing 1 required positional argument: 'strategy'
settings.py
"""
Django settings for resume_website_restapi project.
Generated by 'django-admin startproject' using Django 4.1.3.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
from pathlib import Path
import os
from datetime import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-b#t)ywiymb&@+mv^%j$p&4*y)iq2z-1da*z@beo4s-6-qu9ba%'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
SITE_ID = 2
# Application definition
INSTALLED_APPS = [
'jazzmin',
'django.contrib.admin',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'allauth.socialaccount.providers.github',
'allauth.socialaccount.providers.facebook',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'dj_rest_auth',
'django_filters',
'django_countries',
'posts.apps.PostsConfig',
'users.apps.UsersConfig',
'comments.apps.CommentsConfig',
'category.apps.CategoryConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'resume_website_restapi.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [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',
],
},
},
]
WSGI_APPLICATION = 'resume_website_restapi.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
REST_FRAMEWORK = {
# Permissions
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
# Authentication
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.BasicAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
],
# django-filters
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
],
# Pagination
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 3,
}
REST_USE_JWT = True
# JWT_AUTH_RETURN_EXPIRATION = True
# Cookies
JWT_AUTH_COOKIE = 'blog-auth'
JWT_AUTH_REFRESH_COOKIE = 'blog-refresh-token'
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = 'static/'
MEDIA_URL = 'media/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# User model
AUTH_USER_MODEL = "users.Profile"
AUTHENTICATION_BACKENDS = (
"social_core.backends.google.GoogleOAuth2",
"social_core.backends.facebook.FacebookOAuth2",
"social_core.backends.github.GitHubOAuth2",
"allauth.account.auth_backends.AuthenticationBackend",
"django.contrib.auth.backends.ModelBackend",
)
# JWT Auth
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'JWK_URL': None,
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
from django.urls import reverse_lazy
# All auth
LOGIN_URL = reverse_lazy('users:login')
LOGOUT_REDIRECT_URL = reverse_lazy('users:login')
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
ACCOUNT_USERNAME_REQUIRED = False
# SocialAccount Auth
SOCIALACCOUNT_PROVIDERS = {
"github": {
"APP": {
"client_id": os.environ.get("GitHub_OAUTH_CLIENT_ID"),
"secret": os.environ.get("GitHub_OAUTH_SECRET"),
},
},
"google": {
"APP": {
"client_id": os.environ.get("Google_OAUTH_CLIENT_ID"),
"secret": os.environ.get("Google_OAUTH_SECRET"),
},
},
}
# Email sending credentials
EMAIL_HOST = 'smtp.mailtrap.io'
EMAIL_HOST_USER = 'a923489850d8ef'
EMAIL_HOST_PASSWORD = 'a47074cae95d6b'
EMAIL_PORT = '2525'
#Custom admin panel with django-jazzmin
JAZZMIN_SETTINGS = {
"site_title": "Blog",
"site_header": "your_site_header",
"site_brand": "Blog",
"site_icon": "profiles/default.jpg",
# Add your own branding here
"site_logo": None,
"welcome_sign": "Welcome to the Blog",
# Copyright on the footer
"copyright": "Blog",
"user_avatar": "profiles/default.jpg",
############
# Top Menu #
############
# Links to put along the top menu
"topmenu_links": [
# Url that gets reversed (Permissions can be added)
{"name": "Blog", "url": "posts:posts-list", "permissions": ["auth.view_user"]},
# model admin to link to (Permissions checked against model)
{"model": "users.Profile"},
],
#############
# Side Menu #
#############
# Whether to display the side menu
"show_sidebar": True,
# Whether to aut expand the menu
"navigation_expanded": True,
# Custom icons for side menu apps/models See https://fontawesome.com/icons?d=gallery&m=free&v=5.0.0,5.0.1,5.0.10,5.0.11,5.0.12,5.0.13,5.0.2,5.0.3,5.0.4,5.0.5,5.0.6,5.0.7,5.0.8,5.0.9,5.1.0,5.1.1,5.2.0,5.3.0,5.3.1,5.4.0,5.4.1,5.4.2,5.13.0,5.12.0,5.11.2,5.11.1,5.10.0,5.9.0,5.8.2,5.8.1,5.7.2,5.7.1,5.7.0,5.6.3,5.5.0,5.4.2
# for the full list of 5.13.0 free icon classes
"icons": {
"auth": "fas fa-users-cog",
"auth.user": "fas fa-user",
"users.User": "fas fa-user",
"auth.Group": "fas fa-users",
"admin.LogEntry": "fas fa-file",
},
# # Icons that are used when one is not manually specified
"default_icon_parents": "fas fa-chevron-circle-right",
"default_icon_children": "fas fa-arrow-circle-right",
#################
# Related Modal #
#################
# Use modals instead of popups
"related_modal_active": False,
#############
# UI Tweaks #
#############
# Relative paths to custom CSS/JS scripts (must be present in static files)
# Uncomment this line once you create the bootstrap-dark.css file
"custom_css": "styles/admin.css",
"custom_js": None,
# Whether to show the UI customizer on the sidebar
"show_ui_builder": False,
###############
# Change view #
###############
"changeform_format": "horizontal_tabs",
# override change forms on a per modeladmin basis
"changeform_format_overrides": {
"auth.user": "collapsible",
"auth.group": "vertical_tabs",
},
}
JAZZMIN_UI_TWEAKS = {
"navbar_small_text": False,
"footer_small_text": False,
"body_small_text": False,
"brand_small_text": False,
"brand_colour": "navbar-success",
"accent": "accent-teal",
"navbar": "navbar-dark",
"no_navbar_border": False,
"navbar_fixed": False,
"layout_boxed": False,
"footer_fixed": False,
"sidebar_fixed": False,
"sidebar": "sidebar-dark-info",
"sidebar_nav_small_text": False,
"sidebar_disable_expand": False,
"sidebar_nav_child_indent": False,
"sidebar_nav_compact_style": False,
"sidebar_nav_legacy_style": False,
"sidebar_nav_flat_style": False,
"theme": "cyborg",
"dark_mode_theme": None,
"button_classes": {
"primary": "btn-primary",
"secondary": "btn-secondary",
"info": "btn-info",
"warning": "btn-warning",
"danger": "btn-danger",
"success": "btn-success",
},
}
views.py
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.tokens import default_token_generator
# Auth
from django.contrib.auth import get_user_model, logout
from allauth.account.utils import logout_on_password_change
# SocialAccount auth
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from dj_rest_auth.registration.views import SocialLoginView
# REST FRAMEWORK
from rest_framework.reverse import reverse, reverse_lazy
from rest_framework.response import Response
from rest_framework import permissions
from rest_framework import status
from rest_framework.decorators import (
api_view,
permission_classes,
authentication_classes,
)
from rest_framework import generics
from .serializers import (
UserRegisterSerializer,
UserSerializer,
PasswordResetSerializer,
ChangePasswordSerializer,
)
from . import exceptions as custom_exceptions
Profile = get_user_model()
class UserSignUpApiView(generics.CreateAPIView):
serializer_class = UserRegisterSerializer
permission_classes = (permissions.AllowAny,)
authentication_classes = ()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
print(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
class UsersListApiView(generics.ListAPIView):
serializer_class = UserSerializer
queryset = Profile.objects.all()
@api_view(['GET', 'POST'])
@permission_classes([permissions.AllowAny,])
def activate_account(request, uuid, token):
try:
user = Profile.objects.get(id=uuid)
except (Profile.DoesNotExist, TypeError, ValueError, OverflowError):
user = None
if user is not None and default_token_generator.check_token(user, token):
user.is_active = True
user.save()
else:
raise custom_exceptions.UserOrTokenNotValid
return Response(
{"detail: Activated"},
status=status.HTTP_200_OK,
)
@api_view(['POST'])
@permission_classes([permissions.AllowAny,])
def reset_password(request, *args, **kwargs):
print('kwargs', kwargs)
serializer = PasswordResetSerializer(
data=request.data,
context={'request': request}
)
serializer.is_valid(raise_exception=True)
return Response(
{"detail": "Sent an confirmation email"},
status=status.HTTP_302_FOUND,
)
class PasswordChangeApiView(generics.UpdateAPIView):
serializer_class = ChangePasswordSerializer
permission_classes = (permissions.AllowAny,)
def get_object(self):
try:
profile = Profile.objects.get(id=self.kwargs.get('uuid', ''))
except Profile.DoesNotExist:
profile = None
return profile
def patch(self, request, *args, **kwargs):
user = self.get_object()
serializer = self.serializer_class(
instance=user,
data=request.data,
partial=True,
context={'request': self.request},
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
logout_on_password_change(request, user)
return Response(
{'detail':'Password changed!'},
status=status.HTTP_200_OK,
)
class ProfileDetailUpdateDeleteApiView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = UserSerializer
def get_object(self):
try:
profile = Profile.objects.get(id=self.kwargs.get('pk', ''))
except Profile.DoesNotExist:
profile = None
print(self.request.user, self.request.user.is_authenticated)
print(self.request.user.auth_token)
return profile
def destroy(self, request, *args, **kwargs):
profile = self.get_object()
if profile is not None:
self.perform_destroy(profile)
logout(request)
return Response(
{'detail': 'Profile deleted!'},
status=status.HTTP_200_OK,
)
return Response(
{'detail': 'Profile does not exist!'},
status=status.HTTP_200_OK,
)
# the Logout dj-rest-auth view and this one do not work
@api_view(["POST"])
@permission_classes([permissions.IsAuthenticated, ])
def logout_user(request):
request.user.auth_token.delete()
logout(request)
return Response('User Logged out successfully')
# Social account auth
class GitHubLogin(SocialLoginView):
adapter_class = GitHubOAuth2Adapter
callback_url = reverse_lazy('posts:posts-list')
client_class = OAuth2Client
# if you want to use Authorization Code Grant, use this
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = reverse_lazy('posts:posts-list')
client_class = OAuth2Client
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
urls.py
from django.urls import path
from dj_rest_auth import views as dj_rest_auth_views
# DRF
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
from rest_framework.decorators import authentication_classes
from . import views as custom_views
app_name='users'
urlpatterns = [
# Get a list of all users
path('all-users/', custom_views.UsersListApiView.as_view(),
name='all-users'),
# Custom register and activate account views
path('registration/', custom_views.UserSignUpApiView.as_view(),
name='register'),
path('activate/<uuid>/<token>/', custom_views.activate_account,
name='activate'),
# Custom password reset and change views
path('password/reset/',
custom_views.reset_password,
name='password_reset'),
path('password/change/<uuid>/<token>/',
custom_views.PasswordChangeApiView.as_view(),
name='password_change',
),
# dj-rest-auth views
path('dj-rest-auth/login/',
dj_rest_auth_views.LoginView.as_view(
authentication_classes = (),
), name='login'),
path('dj-rest-auth/logout/',
dj_rest_auth_views.LogoutView.as_view(),
name='logout'),
# token obtain views
path('token/', TokenObtainPairView.as_view(),
name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(),
name='token_refresh'),
# Custom views for profile detail update delete views
path('profile/<uuid:pk>/',
custom_views.ProfileDetailUpdateDeleteApiView.as_view(),
name='profile-detail'),
# SocialAccount auth
path('dj-rest-auth/github/', custom_views.GitHubLogin.as_view(),
name='github_login'),
path('dj-rest-auth/google/', custom_views.GoogleLogin.as_view(),
name='google_login'),
path('dj-rest-auth/facebook/', custom_views.FacebookLogin.as_view(),
name='fb_login'),
]
I started to receive the error after adding the social auth feature from dj-rest-auth third-party app to my django project.