Update for custom CC-Django

This commit is contained in:
Alejandro Franco 2022-06-21 23:13:35 -05:00
parent 223dd6dc91
commit 577c92248a
10 changed files with 176 additions and 29 deletions

View File

@ -302,13 +302,20 @@ CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
# django-allauth # django-allauth
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True) ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
# https://django-allauth.readthedocs.io/en/latest/advanced.html#custom-user-models
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_USERNAME_REQUIRED = False
# https://django-allauth.readthedocs.io/en/latest/configuration.html # https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_AUTHENTICATION_METHOD = "username" ACCOUNT_AUTHENTICATION_METHOD = "email"
# https://django-allauth.readthedocs.io/en/latest/configuration.html # https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_EMAIL_REQUIRED = True
# https://django-allauth.readthedocs.io/en/latest/configuration.html # https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_VERIFICATION = "mandatory" ACCOUNT_EMAIL_VERIFICATION = "mandatory"
# https://django-allauth.readthedocs.io/en/latest/configuration.html # https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_LOGOUT_ON_GET = True
# https://django-allauth.readthedocs.io/en/latest/configuration.html
SOCIALACCOUNT_LOGIN_ON_GET = True
# https://django-allauth.readthedocs.io/en/latest/configuration.html?highlight=SOCIALACCOUNT_LOGIN_ON_GET
ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter" ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter"
# https://django-allauth.readthedocs.io/en/latest/forms.html # https://django-allauth.readthedocs.io/en/latest/forms.html
ACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSignupForm"} ACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSignupForm"}

View File

@ -1,9 +1,14 @@
from typing import Any from typing import Any
from allauth.account.adapter import DefaultAccountAdapter from allauth.account.adapter import DefaultAccountAdapter
from allauth.account.models import EmailAddress
from allauth.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.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import redirect
from django.urls import reverse
class AccountAdapter(DefaultAccountAdapter): class AccountAdapter(DefaultAccountAdapter):
@ -14,3 +19,47 @@ class AccountAdapter(DefaultAccountAdapter):
class SocialAccountAdapter(DefaultSocialAccountAdapter): class SocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
def pre_social_login(self, request, sociallogin):
# social account already exists, so this is just a login
if sociallogin.is_existing:
return
# some social logins don't have an email address
if not sociallogin.email_addresses:
return
# find the first verified email that we get from this sociallogin
verified_email = None
for email in sociallogin.email_addresses:
if email.verified:
verified_email = email
break
# no verified emails found, nothing more to do
if not verified_email:
return
if request.user.is_authenticated and request.user.email != verified_email.email:
messages.error(
request,
"""
No es posible enlazar tu cuenta de {},
ya que no coincide con tu correo en esta plataforma.
""".format(
list(request._socialapp_cache.keys())[0].capitalize()
),
)
raise ImmediateHttpResponse(redirect(reverse("socialaccount_connections")))
# check if given email address already exists as a verified email on
# an existing user's account
try:
existing_email = EmailAddress.objects.get(
email__iexact=verified_email.email, verified=True
)
except EmailAddress.DoesNotExist:
return
# if it does, connect this new social login to the existing user
sociallogin.connect(request, existing_email.user)

View File

@ -10,12 +10,12 @@ User = get_user_model()
@admin.register(User) @admin.register(User)
class UserAdmin(auth_admin.UserAdmin): class UserAdmin(auth_admin.UserAdmin):
form = UserAdminChangeForm form = UserAdminChangeForm
add_form = UserAdminCreationForm add_form = UserAdminCreationForm
readonly_fields = ("uuid",)
fieldsets = ( fieldsets = (
(None, {"fields": ("username", "password")}), (None, {"fields": ("email", "uuid", "password")}),
(_("Personal info"), {"fields": ("name", "email")}), (_("Personal info"), {"fields": ("first_name", "last_name")}),
( (
_("Permissions"), _("Permissions"),
{ {
@ -30,5 +30,28 @@ class UserAdmin(auth_admin.UserAdmin):
), ),
(_("Important dates"), {"fields": ("last_login", "date_joined")}), (_("Important dates"), {"fields": ("last_login", "date_joined")}),
) )
list_display = ["username", "name", "is_superuser"] add_fieldsets = (
search_fields = ["name"] (
None,
{
"classes": ("wide",),
"fields": (
"email",
"first_name",
"last_name",
"password1",
"password2",
),
},
),
)
list_display = [
"email",
"first_name",
"last_name",
"is_active",
"is_staff",
"is_superuser",
]
search_fields = ["email", "first_name", "last_name"]
ordering = ("email", "first_name", "last_name")

View File

@ -7,8 +7,8 @@ User = get_user_model()
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ["username", "name", "url"] fields = ["username", "first_name", "last_name", "url"]
extra_kwargs = { extra_kwargs = {
"url": {"view_name": "api:user-detail", "lookup_field": "username"} "url": {"view_name": "api:user-detail", "lookup_field": "uuid"}
} }

View File

@ -13,7 +13,7 @@ User = get_user_model()
class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet): class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
serializer_class = UserSerializer serializer_class = UserSerializer
queryset = User.objects.all() queryset = User.objects.all()
lookup_field = "username" lookup_field = "uuid"
def get_queryset(self, *args, **kwargs): def get_queryset(self, *args, **kwargs):
assert isinstance(self.request.user.id, int) assert isinstance(self.request.user.id, int)

View File

@ -10,6 +10,7 @@ User = get_user_model()
class UserAdminChangeForm(admin_forms.UserChangeForm): class UserAdminChangeForm(admin_forms.UserChangeForm):
class Meta(admin_forms.UserChangeForm.Meta): class Meta(admin_forms.UserChangeForm.Meta):
model = User model = User
fields = ("email", "first_name", "last_name")
class UserAdminCreationForm(admin_forms.UserCreationForm): class UserAdminCreationForm(admin_forms.UserCreationForm):
@ -20,9 +21,10 @@ class UserAdminCreationForm(admin_forms.UserCreationForm):
class Meta(admin_forms.UserCreationForm.Meta): class Meta(admin_forms.UserCreationForm.Meta):
model = User model = User
fields = ("email", "first_name", "last_name")
error_messages = { error_messages = {
"username": {"unique": _("This username has already been taken.")} "email": {"unique": _("This email has already been taken.")}
} }
@ -32,6 +34,21 @@ class UserSignupForm(SignupForm):
Default fields will be added automatically. Default fields will be added automatically.
Check UserSocialSignupForm for accounts created from social. Check UserSocialSignupForm for accounts created from social.
""" """
first_name = forms.CharField(
max_length=150,
label=_("first name").capitalize(),
widget=forms.TextInput(attrs={"placeholder": _("first name").capitalize()}),
)
last_name = forms.CharField(
max_length=150,
label=_("last name").capitalize(),
widget=forms.TextInput(attrs={"placeholder": _("last name").capitalize()}),
)
def custom_signup(self, request, user):
user.first_name = self.cleaned_data["first_name"]
user.last_name = self.cleaned_data["last_name"]
return super().custom_signup(request, user)
class UserSocialSignupForm(SocialSignupForm): class UserSocialSignupForm(SocialSignupForm):

View File

@ -4,17 +4,59 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class User(AbstractUser): class UserManager(BaseUserManager):
""" """Define a model manager for User model with no username field."""
Default custom user model for {{cookiecutter.project_name}}.
If adding fields that need to be filled at user signup,
check forms.SignupForm and forms.SocialSignupForms accordingly.
"""
#: First and last name do not cover name patterns around the globe use_in_migrations = True
name = CharField(_("Name of User"), blank=True, max_length=255)
first_name = None # type: ignore def _create_user(self, email, password, **extra_fields):
last_name = None # type: ignore """Create and save a User with the given email and password."""
if not email:
raise ValueError("The given email 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_user(self, email, password=None, **extra_fields):
"""Create and save a regular User with the given email and password."""
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
"""Create and save a SuperUser with the given email and password."""
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
if extra_fields.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if extra_fields.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")
return self._create_user(email, password, **extra_fields)
class User(AbstractUser):
"""Default custom user model for {{cookiecutter.project_name}}."""
email = models.EmailField(_("email address"), unique=True)
username = None
uuid = models.UUIDField(
unique=True, db_index=True, default=uuid_lib.uuid4, editable=False
)
first_name = models.CharField(_("first name"), max_length=150)
last_name = models.CharField(_("last name"), max_length=150)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = [
"first_name",
"last_name",
]
objects = UserManager()
def get_absolute_url(self): def get_absolute_url(self):
"""Get url for user's detail view. """Get url for user's detail view.
@ -23,4 +65,13 @@ class User(AbstractUser):
str: URL for user detail. str: URL for user detail.
""" """
return reverse("users:detail", kwargs={"username": self.username}) return reverse("users:detail", kwargs={"uuid": self.uuid})
def __str__(self):
return f"{self.first_name} {self.last_name}"
def save(self, *args, **kwargs):
self.first_name = self.first_name.strip()
self.last_name = self.last_name.strip()
self.email = self.email.lower()
super().save(*args, **kwargs)

View File

@ -27,7 +27,7 @@ class TestUserViewSet:
response = view.me(request) response = view.me(request)
assert response.data == { assert response.data == {
"username": user.username, "email": user.uuid,
"name": user.name, "name": user.first_name,
"url": f"http://testserver/api/users/{user.username}/", "url": f"http://testserver/api/users/{user.uuid}/",
} }

View File

@ -10,5 +10,5 @@ app_name = "users"
urlpatterns = [ urlpatterns = [
path("~redirect/", view=user_redirect_view, name="redirect"), path("~redirect/", view=user_redirect_view, name="redirect"),
path("~update/", view=user_update_view, name="update"), path("~update/", view=user_update_view, name="update"),
path("<str:username>/", view=user_detail_view, name="detail"), path("<uuid:uuid>/", view=user_detail_view, name="detail"),
] ]

View File

@ -11,8 +11,8 @@ User = get_user_model()
class UserDetailView(LoginRequiredMixin, DetailView): class UserDetailView(LoginRequiredMixin, DetailView):
model = User model = User
slug_field = "username" slug_field = "uuid"
slug_url_kwarg = "username" slug_url_kwarg = "uuid"
user_detail_view = UserDetailView.as_view() user_detail_view = UserDetailView.as_view()
@ -21,7 +21,7 @@ user_detail_view = UserDetailView.as_view()
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = User model = User
fields = ["name"] fields = ["first_name", "last_name"]
success_message = _("Information successfully updated") success_message = _("Information successfully updated")
def get_success_url(self): def get_success_url(self):
@ -42,7 +42,7 @@ class UserRedirectView(LoginRequiredMixin, RedirectView):
permanent = False permanent = False
def get_redirect_url(self): def get_redirect_url(self):
return reverse("users:detail", kwargs={"username": self.request.user.username}) return reverse("users:detail", kwargs={"uuid": self.request.user.uuid})
user_redirect_view = UserRedirectView.as_view() user_redirect_view = UserRedirectView.as_view()