mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2025-09-20 19:02:37 +03:00
Uograde CC 240913
This commit is contained in:
parent
50946eedbd
commit
7a07ca17a7
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -74,8 +74,8 @@ jobs:
|
|||
args: "frontend_pipeline=Gulp"
|
||||
- name: Webpack
|
||||
args: "frontend_pipeline=Webpack use_heroku=y"
|
||||
- name: Email
|
||||
args: "ci_tool=Github project_name='Something superduper long - the great amazing project' project_slug=my_awesome_project"
|
||||
- name: Email Username
|
||||
args: "username_type=email ci_tool=Github project_name='Something superduper long - the great amazing project' project_slug=my_awesome_project"
|
||||
|
||||
name: "Bare metal ${{ matrix.script.name }}"
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -116,6 +116,10 @@ Answer the prompts with your own desired [options](http://cookiecutter-django.re
|
|||
4 - Apache Software License 2.0
|
||||
5 - Not open source
|
||||
Choose from 1, 2, 3, 4, 5 [1]: 1
|
||||
Select username_type:
|
||||
1 - username
|
||||
2 - email
|
||||
Choose from 1, 2 [1]: 1
|
||||
timezone [UTC]: America/Los_Angeles
|
||||
windows [n]: n
|
||||
Select an editor to use. The choices are:
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"Apache Software License 2.0",
|
||||
"Not open source"
|
||||
],
|
||||
"username_type": ["username", "email"],
|
||||
"timezone": "UTC",
|
||||
"windows": "n",
|
||||
"editor": ["None", "PyCharm", "VS Code"],
|
||||
|
@ -38,7 +39,6 @@
|
|||
"use_sentry": "n",
|
||||
"use_whitenoise": "n",
|
||||
"use_heroku": "n",
|
||||
"username_type": ["email"],
|
||||
"ci_tool": ["None", "Travis", "Gitlab", "Github", "Drone"],
|
||||
"keep_local_envs_in_vcs": "y",
|
||||
"debug": "n"
|
||||
|
|
|
@ -24,6 +24,13 @@ author_name:
|
|||
email:
|
||||
The email address you want to identify yourself in the project.
|
||||
|
||||
username_type:
|
||||
The type of username you want to use in the project. This can be either
|
||||
``username`` or ``email``. If you choose ``username``, the ``email`` field
|
||||
will be included. If you choose ``email``, the ``username`` field will be
|
||||
excluded. It is best practice to always include an email field, so there is
|
||||
no option for having just the ``username`` field.
|
||||
|
||||
domain_name:
|
||||
The domain name you plan to use for your project once it goes live.
|
||||
Note that it can be safely changed later on whenever you need to.
|
||||
|
|
|
@ -449,8 +449,8 @@ def main():
|
|||
if "{{ cookiecutter.open_source_license}}" != "GPLv3":
|
||||
remove_gplv3_files()
|
||||
|
||||
# if "{{ cookiecutter.username_type }}" == "username":
|
||||
# remove_custom_user_manager_files()
|
||||
if "{{ cookiecutter.username_type }}" == "username":
|
||||
remove_custom_user_manager_files()
|
||||
|
||||
if "{{ cookiecutter.editor }}" != "PyCharm":
|
||||
remove_pycharm_files()
|
||||
|
|
|
@ -43,6 +43,7 @@ def context():
|
|||
|
||||
|
||||
SUPPORTED_COMBINATIONS = [
|
||||
{"username_type": "username"},
|
||||
{"username_type": "email"},
|
||||
{"open_source_license": "MIT"},
|
||||
{"open_source_license": "BSD"},
|
||||
|
|
|
@ -7,8 +7,7 @@ set -o nounset
|
|||
|
||||
{% if cookiecutter.use_celery == 'y' %}
|
||||
# N.B. If only .env files supported variable expansion...
|
||||
export CELERY_BROKER_URL="${REDIS_URL}"
|
||||
# Updated in .envs
|
||||
export CELERY_BROKER_URL="${REDIS_URL}"
|
||||
{% endif %}
|
||||
|
||||
if [ -z "${POSTGRES_USER}" ]; then
|
||||
|
@ -16,7 +15,6 @@ if [ -z "${POSTGRES_USER}" ]; then
|
|||
export POSTGRES_USER="${base_postgres_image_default_user}"
|
||||
fi
|
||||
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
|
||||
# Updated in .envs
|
||||
|
||||
python << END
|
||||
import sys
|
||||
|
|
|
@ -325,13 +325,15 @@ CELERY_TASK_SEND_SENT_EVENT = True
|
|||
# ------------------------------------------------------------------------------
|
||||
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
ACCOUNT_AUTHENTICATION_METHOD = "email"
|
||||
ACCOUNT_AUTHENTICATION_METHOD = "{{cookiecutter.username_type}}"
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
ACCOUNT_USERNAME_REQUIRED = False
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
||||
{%- endif %}
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
|
|
|
@ -41,12 +41,12 @@ django_settings_module = "config.settings.test"
|
|||
|
||||
# ==== djLint ====
|
||||
[tool.djlint]
|
||||
blank_line_after_tag = "load,extends,endblock"
|
||||
blank_line_after_tag = "load,extends"
|
||||
close_void_tags = true
|
||||
format_css = false
|
||||
format_js = false
|
||||
format_css = true
|
||||
format_js = true
|
||||
# TODO: remove T002 when fixed https://github.com/djlint/djLint/issues/687
|
||||
ignore = "H006,H030,H031,T002,H020,H023,H033,D018"
|
||||
ignore = "H006,H030,H031,T002"
|
||||
include = "H017,H035"
|
||||
indent = 2
|
||||
max_line_length = 119
|
||||
|
@ -65,10 +65,6 @@ extend-exclude = [
|
|||
"*/migrations/*.py",
|
||||
"staticfiles/*",
|
||||
]
|
||||
# Same as Django: https://github.com/cookiecutter/cookiecutter-django/issues/4792.
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
target-version = "py312"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
|
|
|
@ -52,5 +52,5 @@ django-webpack-loader==3.1.1 # https://github.com/django-webpack/django-webpack
|
|||
|
||||
# Project
|
||||
# ------------------------------------------------------------------------------
|
||||
fontawesomefree==6.5.2 # https://github.com/FortAwesome/Font-Awesome
|
||||
django-import-export==4.0.8 # https://github.com/django-import-export/django-import-export
|
||||
fontawesomefree==6.6.0 # https://github.com/FortAwesome/Font-Awesome
|
||||
django-import-export==4.1.1 # https://github.com/django-import-export/django-import-export
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
|
||||
{% block title %}
|
||||
User: {% endraw %}
|
||||
{% if cookiecutter.username_type == "email" %}
|
||||
{% raw %}{{ object.first_name }}{% endraw %}
|
||||
{% else %}
|
||||
{% raw %}{{ object.username }}{% endraw %}
|
||||
{% endif %}
|
||||
{% raw %}
|
||||
{% endblock title %}
|
||||
{% block content %}
|
||||
|
@ -13,11 +17,20 @@
|
|||
<div class="col-sm-12">
|
||||
<h2>
|
||||
{% endraw %}
|
||||
{% if cookiecutter.username_type == "email" %}
|
||||
{% raw %}{{ object.first_name }}{% endraw %}
|
||||
{% else %}
|
||||
{% raw %}{{ object.username }}{% endraw %}
|
||||
{% endif %}
|
||||
</h2>
|
||||
{%- if cookiecutter.username_type == "username" %}
|
||||
{%- raw %}
|
||||
<p>{{ object.first_name }}</p>
|
||||
{%- endraw %}
|
||||
{%- endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% raw %}
|
||||
{%- raw %}
|
||||
{% if object == request.user %}
|
||||
<!-- Action buttons -->
|
||||
<div class="row">
|
||||
|
@ -36,4 +49,4 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
{% endraw %}
|
||||
{%- endraw %}
|
||||
|
|
|
@ -2,18 +2,35 @@
|
|||
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ user }}{% endblock title %}
|
||||
|
||||
{% block title %}
|
||||
{% endraw %}
|
||||
{% if cookiecutter.username_type == "email" %}
|
||||
{% raw %}{{ user.first_name }}{% endraw %}
|
||||
{% else %}
|
||||
{% raw %}{{ user.username }}{% endraw %}
|
||||
{% endif %}
|
||||
{% raw %}
|
||||
{% endblock title %}
|
||||
{% block content %}
|
||||
<h1>{{ user }}</h1>
|
||||
<form class="form-horizontal" method="post" action="{% url 'users:update' %}">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<button type="submit" class="btn btn-primary">Update</button>
|
||||
</div>
|
||||
<h1>
|
||||
{% endraw %}
|
||||
{% if cookiecutter.username_type == "email" %}
|
||||
{% raw %}{{ user.first_name }}{% endraw %}
|
||||
{% else %}
|
||||
{% raw %}{{ user.username }}{% endraw %}
|
||||
{% endif %}
|
||||
{% raw %}
|
||||
</h1>
|
||||
<form class="form-horizontal"
|
||||
method="post"
|
||||
action="{% url 'users:update' %}">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<button type="submit" class="btn btn-primary">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
{%- endraw %}
|
||||
|
|
|
@ -55,10 +55,10 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter):
|
|||
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(
|
||||
(
|
||||
"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()
|
||||
),
|
||||
)
|
||||
|
|
|
@ -21,9 +21,13 @@ class UserAdmin(auth_admin.UserAdmin):
|
|||
add_form = UserAdminCreationForm
|
||||
readonly_fields = ("uuid",)
|
||||
fieldsets = (
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
(None, {"fields": ("email", "password")}),
|
||||
(_("Personal info"), {"fields": ("first_name", "last_name")}),
|
||||
(None, {"fields": ("uuid",)}),
|
||||
{%- else %}
|
||||
(None, {"fields": ("username", "password")}),
|
||||
(_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
|
||||
{%- endif %}
|
||||
(
|
||||
_("Permissions"),
|
||||
{
|
||||
|
@ -39,15 +43,16 @@ class UserAdmin(auth_admin.UserAdmin):
|
|||
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
||||
)
|
||||
list_display = [
|
||||
"email",
|
||||
"{{cookiecutter.username_type}}",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
]
|
||||
search_fields = ["email", "first_name", "last_name"]
|
||||
ordering = ("email", "first_name", "last_name")
|
||||
search_fields = ["{{cookiecutter.username_type}}", "first_name", "last_name"]
|
||||
ordering = ("{{cookiecutter.username_type}}", "first_name", "last_name")
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
add_fieldsets = (
|
||||
(
|
||||
None,
|
||||
|
@ -63,3 +68,21 @@ class UserAdmin(auth_admin.UserAdmin):
|
|||
},
|
||||
),
|
||||
)
|
||||
{%- else %}
|
||||
add_fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"classes": ("wide",),
|
||||
"fields": (
|
||||
"username",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"password1",
|
||||
"password2",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
{%- endif %}
|
||||
|
|
|
@ -6,5 +6,11 @@ from {{ cookiecutter.project_slug }}.users.models import User
|
|||
class UserSerializer(serializers.ModelSerializer[User]):
|
||||
class Meta:
|
||||
model = User
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
fields = ["email", "uuid", "first_name", "last_name", "url"]
|
||||
extra_kwargs = {"url": {"view_name": "api:user-detail", "lookup_field": "uuid"}}
|
||||
{%- else %}
|
||||
fields = ["username", "email", "uuid", "first_name", "last_name", "url"]
|
||||
{%- endif %}
|
||||
extra_kwargs = {
|
||||
"url": {"view_name": "api:user-detail", "lookup_field": "uuid"}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from allauth.account.forms import SignupForm
|
||||
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 get_user_model
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
from django.forms import EmailField
|
||||
{%- endif %}
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
User = get_user_model()
|
||||
|
@ -11,7 +12,9 @@ User = get_user_model()
|
|||
class UserAdminChangeForm(admin_forms.UserChangeForm):
|
||||
class Meta(admin_forms.UserChangeForm.Meta): # type: ignore[name-defined]
|
||||
model = User
|
||||
fields = ("email", "first_name", "last_name")
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
field_classes = {"email": EmailField}
|
||||
{%- endif %}
|
||||
|
||||
|
||||
class UserAdminCreationForm(admin_forms.UserCreationForm):
|
||||
|
@ -22,14 +25,18 @@ class UserAdminCreationForm(admin_forms.UserCreationForm):
|
|||
|
||||
class Meta(admin_forms.UserCreationForm.Meta): # type: ignore[name-defined]
|
||||
model = User
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
fields = ("email", "first_name", "last_name")
|
||||
|
||||
field_classes = {"email": EmailField}
|
||||
error_messages = {
|
||||
"email": {"unique": _("This email has already been taken.")}
|
||||
"email": {"unique": _("This email has already been taken.")},
|
||||
}
|
||||
{%- else %}
|
||||
fields = ("username", "first_name", "last_name")
|
||||
error_messages = {
|
||||
"username": {"unique": _("This username has already been taken.")},
|
||||
}
|
||||
{%- endif %}
|
||||
|
||||
|
||||
class UserSignupForm(SignupForm):
|
||||
|
|
|
@ -44,6 +44,36 @@ class Migration(migrations.Migration):
|
|||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
{%- if cookiecutter.username_type == "username" -%}
|
||||
(
|
||||
"username",
|
||||
models.CharField(
|
||||
error_messages={
|
||||
"unique": "A user with that username already exists."
|
||||
},
|
||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||
max_length=150,
|
||||
unique=True,
|
||||
validators=[
|
||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||
],
|
||||
verbose_name="username",
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, verbose_name="email address",
|
||||
),
|
||||
),
|
||||
{%- else %}
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
unique=True, max_length=254, verbose_name="email address",
|
||||
),
|
||||
),
|
||||
{%- endif %}
|
||||
(
|
||||
"is_staff",
|
||||
models.BooleanField(
|
||||
|
@ -66,12 +96,6 @@ class Migration(migrations.Migration):
|
|||
default=django.utils.timezone.now, verbose_name="date joined",
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
max_length=254, unique=True, verbose_name="email address"
|
||||
),
|
||||
),
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
|
@ -115,7 +139,11 @@ class Migration(migrations.Migration):
|
|||
"abstract": False,
|
||||
},
|
||||
managers=[
|
||||
('objects', {{ cookiecutter.project_slug }}.users.models.UserManager()),
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
("objects", {{cookiecutter.project_slug}}.users.models.UserManager()),
|
||||
{%- else %}
|
||||
("objects", django.contrib.auth.models.UserManager()),
|
||||
{%- endif %}
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import uuid as uuid_lib
|
||||
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
from typing import ClassVar
|
||||
|
||||
from django.contrib.auth.base_user import BaseUserManager
|
||||
{% endif -%}
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
from django.db.models import CharField
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
from django.db.models import EmailField
|
||||
{%- endif %}
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
|
||||
from .managers import UserManager
|
||||
{%- endif %}
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
|
@ -18,22 +23,23 @@ class User(AbstractUser):
|
|||
check forms.SignupForm and forms.SocialSignupForms accordingly.
|
||||
"""
|
||||
|
||||
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)
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
email = EmailField(_("email address"), unique=True)
|
||||
username = None # type: ignore[assignment]
|
||||
|
||||
USERNAME_FIELD = "email"
|
||||
|
||||
REQUIRED_FIELDS = [
|
||||
"first_name",
|
||||
"last_name",
|
||||
]
|
||||
|
||||
objects: ClassVar[UserManager] = UserManager()
|
||||
{%- endif %}
|
||||
|
||||
def get_absolute_url(self) -> str:
|
||||
"""Get URL for user's detail view.
|
||||
|
@ -45,7 +51,11 @@ class User(AbstractUser):
|
|||
return reverse("users:detail", kwargs={"uuid": self.uuid})
|
||||
|
||||
def __str__(self):
|
||||
{ % - if cookiecutter.username_type == "email" %}
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
{ % - else %}
|
||||
return f"{self.username}"
|
||||
{ % - endif %}
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.first_name = self.first_name.strip()
|
||||
|
|
|
@ -32,16 +32,28 @@ class TestUserAdmin:
|
|||
data={
|
||||
"first_name": "New",
|
||||
"last_name": "Admin",
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
"email": "new-admin@example.com",
|
||||
{%- else %}
|
||||
"username": "test",
|
||||
{%- endif %}
|
||||
"password1": "My_R@ndom-P@ssw0rd",
|
||||
"password2": "My_R@ndom-P@ssw0rd",
|
||||
},
|
||||
)
|
||||
assert response.status_code == HTTPStatus.FOUND
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
assert User.objects.filter(email="new-admin@example.com").exists()
|
||||
{%- else %}
|
||||
assert User.objects.filter(username="test").exists()
|
||||
{%- endif %}
|
||||
|
||||
def test_view_user(self, admin_client):
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
user = User.objects.get(email="admin@example.com")
|
||||
{%- else %}
|
||||
user = User.objects.get(username="admin")
|
||||
{%- endif %}
|
||||
url = reverse("admin:users_user_change", kwargs={"object_id": user.pk})
|
||||
response = admin_client.get(url)
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
|
|
|
@ -29,6 +29,9 @@ class TestUserViewSet:
|
|||
response = view.me(request) # type: ignore[call-arg, arg-type, misc]
|
||||
|
||||
assert response.data == {
|
||||
{%- if cookiecutter.username_type == "username" %}
|
||||
"username": user.username,
|
||||
{%- endif %}
|
||||
"email": user.email,
|
||||
"uuid": str(user.uuid),
|
||||
"first_name": user.first_name,
|
||||
|
|
|
@ -23,7 +23,11 @@ class TestUserAdminCreationForm:
|
|||
# hence cannot be created.
|
||||
form = UserAdminCreationForm(
|
||||
{
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
"email": user.email,
|
||||
{%- else %}
|
||||
"username": user.username,
|
||||
{%- endif %}
|
||||
"first_name": user.first_name,
|
||||
"last_name": user.last_name,
|
||||
"password1": user.password,
|
||||
|
@ -33,5 +37,10 @@ class TestUserAdminCreationForm:
|
|||
|
||||
assert not form.is_valid()
|
||||
assert len(form.errors) == 1
|
||||
{%- if cookiecutter.username_type == "email" %}
|
||||
assert "email" in form.errors
|
||||
assert form.errors["email"][0] == _("This email has already been taken.")
|
||||
{%- else %}
|
||||
assert "username" in form.errors
|
||||
assert form.errors["username"][0] == _("This username has already been taken.")
|
||||
{%- endif %}
|
||||
|
|
Loading…
Reference in New Issue
Block a user