Dj-rest-auth + allauth not sending email

Context: I'm setting DRF + dj-rest-auth + allauth + simple-jwt for user authentication.

Desired behaviour: Register with no username, only email. Authorize login only if email is verified with a link sent to email. Social login to be added.

Problem: It seems that confirmation email is not being sent. When I run the following test I see that it wanted to send some email but it's not found anywhere.

Test code:

    client = APIClient()
    url = reverse("rest_register")  # dj-rest-auth register endpoint
    # Register a user
    data = {
        "email": "user1@example.com",
        "password1": "StrongPass123!",
        "password2": "StrongPass123!",
    }
    response = client.post(url, data, format="json")
    assert response.status_code == 201, response.data

    print(response.data)


    # Manually verify the user
    from allauth.account.models import EmailConfirmation

    user = User.objects.get(email="user1@example.com")
    from django.core import mail    
    print(f'Amount of sent emails: {len(mail.outbox)}')
    print(f'Email Confimation exists: {EmailConfirmation.objects.filter(email_address__email=user.email).exists()}')

This prints:

{'detail': 'Verification e-mail sent.'}
Amount of sent emails: 0
Email Confimation exists: False

My code:

core/urls.py

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('api/auth/', include('authentication.urls')),
    path("admin/", admin.site.urls),

    path("accounts/", include("allauth.urls")),
]

authentication/urls.py

from dj_rest_auth.jwt_auth import get_refresh_view
from dj_rest_auth.registration.views import RegisterView, VerifyEmailView
from dj_rest_auth.views import LoginView, LogoutView, UserDetailsView
from django.urls import path
from rest_framework_simplejwt.views import TokenVerifyView

urlpatterns = [
    path("register/", RegisterView.as_view(), name="rest_register"),
    path("register/verify-email/", VerifyEmailView.as_view(), name="account_email_verification_sent"), # Docs says I must add if email confirmation is mandatory

    path("login/", LoginView.as_view(), name="rest_login"),
    path("logout/", LogoutView.as_view(), name="rest_logout"),

    path("user/", UserDetailsView.as_view(), name="rest_user_details"),

    path("token/verify/", TokenVerifyView.as_view(), name="token_verify"),
    path("token/refresh/", get_refresh_view().as_view(), name="token_refresh"),
]

Custom User model:

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.db import models


class UserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("The Email field must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        return self.create_user(email, password, **extra_fields)

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    is_active = models.BooleanField(default=True) # Some SO question fixed this setting is_active default value to True
    is_staff = models.BooleanField(default=False)

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = UserManager()

Relevant settings:

SITE_ID = 1
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    "django.contrib.sites",
    'django.contrib.staticfiles',    

    # Django REST Framework
    "rest_framework",
    "rest_framework.authtoken",
    # Simple JWT
    "rest_framework_simplejwt",

    # Allauth
    "allauth",
    "allauth.account",
    "allauth.socialaccount",

    # dj-rest-auth
    "dj_rest_auth",
    "dj_rest_auth.registration",

    # CORS
    "corsheaders",

    # Local apps
    "authentication.apps.AuthenticationConfig",
]

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',

    # Allauth
    'allauth.account.middleware.AccountMiddleware',

    # CORS
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
]

ROOT_URLCONF = 'core.urls'

EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" # for testing

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL  = "authentication.User"

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
    "ROTATE_REFRESH_TOKENS": False,
    "BLACKLIST_AFTER_ROTATION": False,
    "UPDATE_LAST_LOGIN": True,
    "SIGNING_KEY": "complexsigningkey",  # generate a key and replace me
    "ALGORITHM": "HS512",
}

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ]
}

# Dj-rest-auth settings
REST_AUTH = {
    "USE_JWT": True,
    "JWT_AUTH_HTTPONLY": False,
    "REGISTER_SERIALIZER": "authentication.serializers.RegisterSerializer"
}

# Allauth settings
ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*']
ACCOUNT_LOGIN_METHODS = {'email'}
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_EMAIL_CONFIRMATION_HMAC = True # This is the default. But just to be explicit.

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
]

Вернуться на верх