mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2025-09-23 20:26:43 +03:00
Update for custom CC-Django
This commit is contained in:
parent
223dd6dc91
commit
577c92248a
|
@ -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"}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}/",
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user