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
# ------------------------------------------------------------------------------
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
ACCOUNT_AUTHENTICATION_METHOD = "username"
ACCOUNT_AUTHENTICATION_METHOD = "email"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_REQUIRED = True
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
# 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"
# https://django-allauth.readthedocs.io/en/latest/forms.html
ACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSignupForm"}

View File

@ -1,9 +1,14 @@
from typing import Any
from allauth.account.adapter import DefaultAccountAdapter
from allauth.account.models import EmailAddress
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.conf import settings
from django.contrib import messages
from django.http import HttpRequest
from django.shortcuts import redirect
from django.urls import reverse
class AccountAdapter(DefaultAccountAdapter):
@ -14,3 +19,47 @@ class AccountAdapter(DefaultAccountAdapter):
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
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)
class UserAdmin(auth_admin.UserAdmin):
form = UserAdminChangeForm
add_form = UserAdminCreationForm
readonly_fields = ("uuid",)
fieldsets = (
(None, {"fields": ("username", "password")}),
(_("Personal info"), {"fields": ("name", "email")}),
(None, {"fields": ("email", "uuid", "password")}),
(_("Personal info"), {"fields": ("first_name", "last_name")}),
(
_("Permissions"),
{
@ -30,5 +30,28 @@ class UserAdmin(auth_admin.UserAdmin):
),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
)
list_display = ["username", "name", "is_superuser"]
search_fields = ["name"]
add_fieldsets = (
(
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 Meta:
model = User
fields = ["username", "name", "url"]
fields = ["username", "first_name", "last_name", "url"]
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):
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = "username"
lookup_field = "uuid"
def get_queryset(self, *args, **kwargs):
assert isinstance(self.request.user.id, int)

View File

@ -10,6 +10,7 @@ User = get_user_model()
class UserAdminChangeForm(admin_forms.UserChangeForm):
class Meta(admin_forms.UserChangeForm.Meta):
model = User
fields = ("email", "first_name", "last_name")
class UserAdminCreationForm(admin_forms.UserCreationForm):
@ -20,9 +21,10 @@ class UserAdminCreationForm(admin_forms.UserCreationForm):
class Meta(admin_forms.UserCreationForm.Meta):
model = User
fields = ("email", "first_name", "last_name")
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.
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):

View File

@ -4,17 +4,59 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
"""
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.
"""
class UserManager(BaseUserManager):
"""Define a model manager for User model with no username field."""
#: First and last name do not cover name patterns around the globe
name = CharField(_("Name of User"), blank=True, max_length=255)
first_name = None # type: ignore
last_name = None # type: ignore
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""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):
"""Get url for user's detail view.
@ -23,4 +65,13 @@ class User(AbstractUser):
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)
assert response.data == {
"username": user.username,
"name": user.name,
"url": f"http://testserver/api/users/{user.username}/",
"email": user.uuid,
"name": user.first_name,
"url": f"http://testserver/api/users/{user.uuid}/",
}

View File

@ -10,5 +10,5 @@ app_name = "users"
urlpatterns = [
path("~redirect/", view=user_redirect_view, name="redirect"),
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):
model = User
slug_field = "username"
slug_url_kwarg = "username"
slug_field = "uuid"
slug_url_kwarg = "uuid"
user_detail_view = UserDetailView.as_view()
@ -21,7 +21,7 @@ user_detail_view = UserDetailView.as_view()
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = User
fields = ["name"]
fields = ["first_name", "last_name"]
success_message = _("Information successfully updated")
def get_success_url(self):
@ -42,7 +42,7 @@ class UserRedirectView(LoginRequiredMixin, RedirectView):
permanent = False
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()