mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2025-09-23 12:16:41 +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
|
# 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"}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}/",
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user