Skip to content

Commit

Permalink
lots of
Browse files Browse the repository at this point in the history
  • Loading branch information
hnthh committed Nov 28, 2023
1 parent 3693f35 commit c97c10d
Show file tree
Hide file tree
Showing 30 changed files with 2,572 additions and 79 deletions.
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
venv
testproject
.vscode
.idea
.vscode

**/*~
**/.DS_Store

testproject
venv
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
VENV=cd testproject/django && poetry run python src/manage.py
VENV = cd testproject && poetry run python src/manage.py

test: bootstrap
$(VENV) makemigrations --check
$(VENV) startapp test_app

bootstrap:
rm -Rf testproject
mkdir -p testproject

cd testproject && cookiecutter --no-input ../
cookiecutter --no-input ./

coverage:
$(VENV) -m pip install pytest-cov
Expand Down
2 changes: 1 addition & 1 deletion cookiecutter.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"project_slug": "django",
"project_slug": "testproject",
"email": "",
"project_version": "0.0.0-dev",
"_copy_without_render": [
Expand Down
3 changes: 3 additions & 0 deletions {{cookiecutter.project_slug}}/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ manage = poetry run src/manage.py

fmt:
poetry run autoflake --in-place --remove-all-unused-imports --recursive src tests
poetry run isort src tests
poetry run black src tests

lint:
$(manage) check
poetry run isort --check-only src tests
poetry run black --check src tests
poetry run flake8 src tests
poetry run mypy src tests
poetry run dotenv-linter src/core/.env.ci
Expand Down
2,347 changes: 2,347 additions & 0 deletions {{cookiecutter.project_slug}}/poetry.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions {{cookiecutter.project_slug}}/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[tool.poetry]
authors = ["you <[email protected]>"]
description = "your project description"
name = "your-project-name"
name = "{{cookiecutter.project_slug}}"
readme = "README.md"
version = "0.1.0"
version = "{{cookiecutter.project_version}}"

[tool.poetry.dependencies]
python = "~3.11"
Expand Down Expand Up @@ -48,6 +48,7 @@ flake8-variables-names = "^0.0.6"
flake8-walrus = "^1.2.0"
freezegun = "^1.2.2"
ipython = "^8.18.0"
isort = "^5.12.0"
jedi = "^0.19.1"
mixer = {extras = ["django"], version = "^7.2.2"}
mypy = "^1.7.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from django.contrib import admin

from core.admin import ModelAdmin

# Register your models here.
from core.admin import ModelAdmin

This file was deleted.

This file was deleted.

This file was deleted.

127 changes: 107 additions & 20 deletions {{cookiecutter.project_slug}}/src/apps/users/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,125 @@


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
("auth", "0012_alter_user_first_name_max_length"),
]

operations = [
migrations.CreateModel(
name='User',
name="User",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('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')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"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",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.Group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.Permission",
verbose_name="user permissions",
),
),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
("objects", django.contrib.auth.models.UserManager()),
],
),
]
3 changes: 2 additions & 1 deletion {{cookiecutter.project_slug}}/src/apps/users/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import ClassVar

from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as _UserManager
from typing import ClassVar


class User(AbstractUser): # noqa
Expand Down
2 changes: 1 addition & 1 deletion {{cookiecutter.project_slug}}/src/core/.env.ci
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
DEBUG=Off
SECRET_KEY={{ random_ascii_string(48, punctuation=True) }}
SECRET_KEY=l!@xGb!Jkd]pQsvtU,@y`=%/c}mY;]oYwnsVeU}".VwwClOX
DATABASE_URL=sqlite:///db.sqlite
4 changes: 3 additions & 1 deletion {{cookiecutter.project_slug}}/src/core/api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@

class AppJSONRenderer(CamelCaseJSONRenderer):
charset = "utf-8" # force DRF to add charset header to the content-type
json_underscoreize = {"no_underscore_before_number": True} # https://github.com/vbabiy/djangorestframework-camel-case#underscoreize-options
json_underscoreize = {
"no_underscore_before_number": True
} # https://github.com/vbabiy/djangorestframework-camel-case#underscoreize-options
16 changes: 12 additions & 4 deletions {{cookiecutter.project_slug}}/src/core/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,14 @@ def get_object(self, *args: Any, **kwargs: Any) -> Any:
class DefaultCreateModelMixin(CreateModelMixin):
"""Return detail-serialized created instance"""

def create(self: BaseGenericViewSet, request: Request, *args: Any, **kwargs: Any) -> Response:
def create(
self: BaseGenericViewSet, request: Request, *args: Any, **kwargs: Any
) -> Response:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer) # No getting created instance in original DRF
instance = self.perform_create(
serializer
) # No getting created instance in original DRF
headers = self.get_success_headers(serializer.data)
return self.get_response(instance, status.HTTP_201_CREATED, headers)

Expand All @@ -52,12 +56,16 @@ def perform_create(self: BaseGenericViewSet, serializer: Any) -> Any:
class DefaultUpdateModelMixin(UpdateModelMixin):
"""Return detail-serialized updated instance"""

def update(self: BaseGenericViewSet, request: Request, *args: Any, **kwargs: Any) -> Response:
def update(
self: BaseGenericViewSet, request: Request, *args: Any, **kwargs: Any
) -> Response:
partial = kwargs.pop("partial", False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
instance = self.perform_update(serializer) # No getting updated instance in original DRF
instance = self.perform_update(
serializer
) # No getting updated instance in original DRF

if getattr(instance, "_prefetched_objects_cache", None):
# If 'prefetch_related' has been applied to a queryset, we need to
Expand Down
12 changes: 9 additions & 3 deletions {{cookiecutter.project_slug}}/src/core/conf/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

REST_FRAMEWORK = {
"DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticatedOrReadOnly",),
"DEFAULT_PERMISSION_CLASSES": (
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
),
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
"rest_framework_jwt.authentication.JSONWebTokenAuthentication",
Expand All @@ -32,8 +34,12 @@

# Adding session auth and browsable API at the developer machine
if env("DEBUG", cast=bool, default=False):
REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].append("rest_framework.authentication.SessionAuthentication")
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append("djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer")
REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"].append(
"rest_framework.authentication.SessionAuthentication"
)
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append(
"djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer"
)


# Set up drf_spectacular, https://drf-spectacular.readthedocs.io/en/latest/settings.html
Expand Down
14 changes: 8 additions & 6 deletions {{cookiecutter.project_slug}}/src/core/conf/i18n.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
from pathlib import Path

LANGUAGE_CODE = "en-us"
LOCALE_PATHS = ["locale"]
USE_L10N = True
USE_I18N = True
from core.conf.boilerplate import BASE_DIR

LANGUAGE_CODE = "ru"

LOCALE_PATHS = [Path(BASE_DIR).parent / "locale"]

USE_i18N = True
13 changes: 12 additions & 1 deletion {{cookiecutter.project_slug}}/src/core/conf/storage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
from core.conf.environ import env

DEFAULT_FILE_STORAGE = env("DEFAULT_FILE_STORAGE", cast=str, default="django.core.files.storage.FileSystemStorage")
STORAGES = {
"default": {
"BACKEND": env(
"DEFAULT_FILE_STORAGE",
cast=str,
default="django.core.files.storage.FileSystemStorage",
),
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}

AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", default="")
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY", default="")
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ class Command(BaseCommand):
"""Disable automatic names for django migrations, thanks https://adamj.eu/tech/2020/02/24/how-to-disallow-auto-named-django-migrations/"""

def handle(self, *app_labels, **options):
if options["name"] is None and not any([options["dry_run"], options["check_changes"]]):
raise MakemigrationsError("Migration name is required. Run again with `-n/--name` argument and specify name explicitly.")
if options["name"] is None and not any(
[options["dry_run"], options["check_changes"]]
):
raise MakemigrationsError(
"Migration name is required. Run again with `-n/--name` argument and specify name explicitly."
)

super().handle(*app_labels, **options)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from os import path
from pathlib import Path

from django.conf import settings
from django.core.management.commands.startapp import Command as BaseCommand
Expand All @@ -9,6 +9,17 @@ class Command(BaseCommand):

def handle(self, **options):
if "template" not in options or options["template"] is None:
options["template"] = path.join(settings.BASE_DIR, ".django-app-template")
options["template"] = str(
Path(settings.BASE_DIR).parent / ".django-app-template"
)

super().handle(**options)

testsdir = (
Path(settings.BASE_DIR).parent.parent / "tests" / "apps" / options["name"]
)
testsdir.mkdir(parents=True, exist_ok=True)

(testsdir / "__init__.py").touch()
(testsdir / "factory.py").touch()
(testsdir / "fixtures.py").touch()
Loading

0 comments on commit c97c10d

Please sign in to comment.