CC Upgrade 20250602

Fix custom allauth integration
This commit is contained in:
Alejandro Franco 2025-06-02 22:29:49 -06:00
parent c02d5461bb
commit 3d3d06fb8b
13 changed files with 27 additions and 31 deletions

View File

@ -337,7 +337,7 @@ CELERY_WORKER_HIJACK_ROOT_LOGGER = False
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True) ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
# https://docs.allauth.org/en/latest/account/configuration.html # https://docs.allauth.org/en/latest/account/configuration.html
ACCOUNT_AUTHENTICATION_METHOD = "{{cookiecutter.username_type}}" ACCOUNT_LOGIN_METHODS = {"{{cookiecutter.username_type}}"}
# https://docs.allauth.org/en/latest/account/configuration.html # https://docs.allauth.org/en/latest/account/configuration.html
{%- if cookiecutter.username_type == "username" %} {%- if cookiecutter.username_type == "username" %}
ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"] ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"]

View File

@ -44,7 +44,8 @@ EMAIL_PORT = 1025
{%- else -%} {%- else -%}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = env( EMAIL_BACKEND = env(
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend", "DJANGO_EMAIL_BACKEND",
default="django.core.mail.backends.console.EmailBackend",
) )
{%- endif %} {%- endif %}

View File

@ -9,7 +9,6 @@ volumes:
production_redis_data: {} production_redis_data: {}
{% endif %} {% endif %}
services: services:
django:{% if cookiecutter.use_celery == 'y' %} &django{% endif %} django:{% if cookiecutter.use_celery == 'y' %} &django{% endif %}
build: build:
@ -75,9 +74,7 @@ services:
- production_redis_data:/data - production_redis_data:/data
{% endif %} {% endif %}
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
celeryworker: celeryworker:
<<: *django <<: *django
image: {{ cookiecutter.project_slug }}_production_celeryworker image: {{ cookiecutter.project_slug }}_production_celeryworker

View File

@ -1,7 +1,7 @@
export COMPOSE_FILE := "docker-compose.local.yml" export COMPOSE_FILE := "docker-compose.local.yml"
## Just does not yet manage signals for subprocesses reliably, which can lead to unexpected behavior. ## Just does not yet manage signals for subprocesses reliably, which can lead to unexpected behavior.
## Exercise caution before expanding its usage in production environments. ## Exercise caution before expanding its usage in production environments.
## For more information, see https://github.com/casey/just/issues/2473 . ## For more information, see https://github.com/casey/just/issues/2473 .

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
"""Django's command-line utility for administrative tasks.""" """Django's command-line utility for administrative tasks."""
import os import os
import sys import sys
from pathlib import Path from pathlib import Path

View File

@ -8,13 +8,12 @@ from allauth.core.exceptions import ImmediateHttpResponse
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.http import HttpRequest
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from allauth.socialaccount.models import SocialLogin from allauth.socialaccount.models import SocialLogin
from {{cookiecutter.project_slug}}.users.models import User from django.http import HttpRequest
class AccountAdapter(DefaultAccountAdapter): class AccountAdapter(DefaultAccountAdapter):
@ -30,8 +29,6 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter):
) -> bool: ) -> bool:
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
def pre_social_login(self, request, sociallogin): def pre_social_login(self, request, sociallogin):
# social account already exists, so this is just a login # social account already exists, so this is just a login
if sociallogin.is_existing: if sociallogin.is_existing:
@ -53,13 +50,13 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter):
return return
if request.user.is_authenticated and request.user.email != verified_email.email: if request.user.is_authenticated and request.user.email != verified_email.email:
# Obtener el nombre del proveedor social de manera más segura
provider_name = sociallogin.account.provider.capitalize()
messages.error( messages.error(
request, request,
( (
"No es posible enlazar tu cuenta de {}, " f"No es posible enlazar tu cuenta de {provider_name}, "
"ya que no coincide con tu correo en esta plataforma." "ya que no coincide con tu correo en esta plataforma."
).format(
list(request._socialapp_cache.keys())[0].capitalize()
), ),
) )
raise ImmediateHttpResponse(redirect(reverse("socialaccount_connections"))) raise ImmediateHttpResponse(redirect(reverse("socialaccount_connections")))
@ -68,7 +65,8 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter):
# an existing user's account # an existing user's account
try: try:
existing_email = EmailAddress.objects.get( existing_email = EmailAddress.objects.get(
email__iexact=verified_email.email, verified=True email__iexact=verified_email.email,
verified=True
) )
except EmailAddress.DoesNotExist: except EmailAddress.DoesNotExist:
return return

View File

@ -12,5 +12,5 @@ class UserSerializer(serializers.ModelSerializer[User]):
fields = ["username", "email", "uuid", "first_name", "last_name", "url"] fields = ["username", "email", "uuid", "first_name", "last_name", "url"]
{%- endif %} {%- endif %}
extra_kwargs = { extra_kwargs = {
"url": {"view_name": "api:user-detail", "lookup_field": "uuid"} "url": {"view_name": "api:user-detail", "lookup_field": "uuid"},
} }

View File

@ -1,4 +1,5 @@
import uuid import uuid
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from rest_framework import status from rest_framework import status
from rest_framework.decorators import action from rest_framework.decorators import action

View File

@ -1,9 +1,8 @@
from allauth.account.forms import SignupForm from allauth.account.forms import SignupForm
from allauth.socialaccount.forms import SignupForm as SocialSignupForm from allauth.socialaccount.forms import SignupForm as SocialSignupForm
from django import forms
from django.contrib.auth import forms as admin_forms from django.contrib.auth import forms as admin_forms
{%- if cookiecutter.username_type == "email" %} from django.contrib.auth import get_user_model
from django.forms import EmailField
{%- endif %}
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
User = get_user_model() User = get_user_model()
@ -13,7 +12,7 @@ class UserAdminChangeForm(admin_forms.UserChangeForm):
class Meta(admin_forms.UserChangeForm.Meta): # type: ignore[name-defined] class Meta(admin_forms.UserChangeForm.Meta): # type: ignore[name-defined]
model = User model = User
{%- if cookiecutter.username_type == "email" %} {%- if cookiecutter.username_type == "email" %}
field_classes = {"email": EmailField} field_classes = {"email": forms.EmailField}
{%- endif %} {%- endif %}
@ -27,7 +26,7 @@ class UserAdminCreationForm(admin_forms.AdminUserCreationForm):
model = User model = User
{%- if cookiecutter.username_type == "email" %} {%- if cookiecutter.username_type == "email" %}
fields = ("email", "first_name", "last_name") fields = ("email", "first_name", "last_name")
field_classes = {"email": EmailField} field_classes = {"email": forms.EmailField}
error_messages = { error_messages = {
"email": {"unique": _("This email has already been taken.")}, "email": {"unique": _("This email has already been taken.")},
} }

View File

@ -4,10 +4,7 @@ from typing import ClassVar
{% endif -%} {% endif -%}
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db.models import CharField from django.db import models
{%- if cookiecutter.username_type == "email" %}
from django.db.models import EmailField
{%- endif %}
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
{%- if cookiecutter.username_type == "email" %} {%- if cookiecutter.username_type == "email" %}
@ -24,12 +21,15 @@ class User(AbstractUser):
""" """
uuid = models.UUIDField( uuid = models.UUIDField(
unique=True, db_index=True, default=uuid_lib.uuid4, editable=False unique=True,
db_index=True,
default=uuid_lib.uuid4,
editable=False
) )
first_name = models.CharField(_("first name"), max_length=150) first_name = models.CharField(_("first name"), max_length=150)
last_name = models.CharField(_("last name"), max_length=150) last_name = models.CharField(_("last name"), max_length=150)
{%- if cookiecutter.username_type == "email" %} {%- if cookiecutter.username_type == "email" %}
email = EmailField(_("email address"), unique=True) email = models.EmailField(_("email address"), unique=True)
username = None # type: ignore[assignment] username = None # type: ignore[assignment]
USERNAME_FIELD = "email" USERNAME_FIELD = "email"

View File

@ -6,6 +6,8 @@ from factory import Faker
from factory import post_generation from factory import post_generation
from factory.django import DjangoModelFactory from factory.django import DjangoModelFactory
from {{ cookiecutter.project_slug }}.users.models import User
class UserFactory(DjangoModelFactory[User]): class UserFactory(DjangoModelFactory[User]):
{%- if cookiecutter.username_type == "username" %} {%- if cookiecutter.username_type == "username" %}

View File

@ -5,10 +5,7 @@ from {{ cookiecutter.project_slug }}.users.models import User
def test_detail(user: User): def test_detail(user: User):
assert ( assert reverse("users:detail", kwargs={"uuid": user.uuid}) == f"/users/{user.uuid}/"
reverse("users:detail", kwargs={"uuid": user.uuid})
== f"/users/{user.uuid}/"
)
assert resolve(f"/users/{user.uuid}/").view_name == "users:detail" assert resolve(f"/users/{user.uuid}/").view_name == "users:detail"

View File

@ -28,7 +28,7 @@ class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
assert self.request.user.is_authenticated # type guard assert self.request.user.is_authenticated # type guard
return self.request.user.get_absolute_url() return self.request.user.get_absolute_url()
def get_object(self, queryset: QuerySet | None=None) -> User: def get_object(self, queryset: QuerySet | None = None) -> User:
assert self.request.user.is_authenticated # type guard assert self.request.user.is_authenticated # type guard
return self.request.user return self.request.user