init commit

This commit is contained in:
2025-05-06 20:44:33 +09:00
commit 91f0d54563
5567 changed files with 948185 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
from django.conf import settings
def get_config(setting_name):
return {
"NPM_BIN_PATH": getattr(settings, "NPM_BIN_PATH", "npm"),
# 'TAILWIND_DEV_MODE' is deprecated. Leaving it here
# to support legacy browser-sync based configs.
"TAILWIND_DEV_MODE": getattr(settings, "TAILWIND_DEV_MODE", False),
"TAILWIND_CSS_PATH": getattr(settings, "TAILWIND_CSS_PATH", "css/dist/styles.css"),
"TAILWIND_APP_NAME": getattr(settings, "TAILWIND_APP_NAME", None),
}[setting_name]

View File

@@ -0,0 +1,6 @@
{
"app_name": "",
"_copy_without_render": [
"templates/base.html"
]
}

View File

@@ -0,0 +1,12 @@
import re
import sys
APP_NAME_REGEX = r"^[_a-zA-Z][_a-zA-Z0-9]+$"
app_name = "{{ cookiecutter.app_name }}"
if not re.match(APP_NAME_REGEX, app_name):
print(f"ERROR: {app_name} is not a valid Django app name!")
# exits with status 1 to indicate failure
sys.exit(1)

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class {{ cookiecutter.app_name[0]|upper }}{{ cookiecutter.app_name[1:] }}Config(AppConfig):
name = '{{ cookiecutter.app_name }}'

View File

@@ -0,0 +1,28 @@
{
"name": "{{ cookiecutter.app_name }}",
"version": "4.0.0",
"description": "",
"scripts": {
"start": "npm run dev",
"build": "npm run build:clean && npm run build:tailwind",
"build:clean": "rimraf ../static/css/dist",
"build:tailwind": "cross-env NODE_ENV=production tailwindcss --postcss -i ./src/styles.css -o ../static/css/dist/styles.css --minify",
"dev": "cross-env NODE_ENV=development tailwindcss --postcss -i ./src/styles.css -o ../static/css/dist/styles.css -w",
"tailwindcss": "node ./node_modules/tailwindcss/lib/cli.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"cross-env": "^7.0.3",
"postcss": "^8.5.3",
"postcss-import": "^16.1.0",
"postcss-nested": "^7.0.2",
"postcss-simple-vars": "^7.0.1",
"rimraf": "^6.0.1",
"tailwindcss": "^3.4.17"
}
}

View File

@@ -0,0 +1,7 @@
module.exports = {
plugins: {
"postcss-import": {},
"postcss-simple-vars": {},
"postcss-nested": {}
},
}

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,57 @@
/**
* This is a minimal config.
*
* If you need the full config, get it from here:
* https://unpkg.com/browse/tailwindcss@latest/stubs/defaultConfig.stub.js
*/
module.exports = {
content: [
/**
* HTML. Paths to Django template files that will contain Tailwind CSS classes.
*/
/* Templates within theme app (<tailwind_app_name>/templates), e.g. base.html. */
'../templates/**/*.html',
/*
* Main templates directory of the project (BASE_DIR/templates).
* Adjust the following line to match your project structure.
*/
'../../templates/**/*.html',
/*
* Templates in other django apps (BASE_DIR/<any_app_name>/templates).
* Adjust the following line to match your project structure.
*/
'../../**/templates/**/*.html',
/**
* JS: If you use Tailwind CSS in JavaScript, uncomment the following lines and make sure
* patterns match your project structure.
*/
/* JS 1: Ignore any JavaScript in node_modules folder. */
// '!../../**/node_modules',
/* JS 2: Process all JavaScript files in the project. */
// '../../**/*.js',
/**
* Python: If you use Tailwind CSS classes in Python, uncomment the following line
* and make sure the pattern below matches your project structure.
*/
// '../../**/*.py'
],
theme: {
extend: {},
},
plugins: [
/**
* '@tailwindcss/forms' is the forms plugin that provides a minimal styling
* for forms. If you don't like it or have own styling for forms,
* comment the line below to disable '@tailwindcss/forms'.
*/
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
],
}

View File

@@ -0,0 +1,19 @@
{% load static tailwind_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Django Tailwind</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
{% tailwind_css %}
</head>
<body class="bg-gray-50 font-serif leading-normal tracking-normal">
<div class="container mx-auto">
<section class="flex items-center justify-center h-screen">
<h1 class="text-5xl">Django + Tailwind = ❤️</h1>
</section>
</div>
</body>
</html>

View File

@@ -0,0 +1,6 @@
{
"app_name": "",
"_copy_without_render": [
"templates/base.html"
]
}

View File

@@ -0,0 +1,12 @@
import re
import sys
APP_NAME_REGEX = r"^[_a-zA-Z][_a-zA-Z0-9]+$"
app_name = "{{ cookiecutter.app_name }}"
if not re.match(APP_NAME_REGEX, app_name):
print(f"ERROR: {app_name} is not a valid Django app name!")
# exits with status 1 to indicate failure
sys.exit(1)

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class {{ cookiecutter.app_name[0]|upper }}{{ cookiecutter.app_name[1:] }}Config(AppConfig):
name = '{{ cookiecutter.app_name }}'

View File

@@ -0,0 +1,25 @@
{
"name": "{{ cookiecutter.app_name }}",
"version": "4.0.1",
"description": "",
"scripts": {
"start": "npm run dev",
"build": "npm run build:clean && npm run build:tailwind",
"build:clean": "rimraf ../static/css/dist",
"build:tailwind": "cross-env NODE_ENV=production postcss ./src/styles.css -o ../static/css/dist/styles.css --minify",
"dev": "cross-env NODE_ENV=development postcss ./src/styles.css -o ../static/css/dist/styles.css --watch"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@tailwindcss/postcss": "^4.1.0",
"cross-env": "^7.0.3",
"postcss": "^8.5.3",
"postcss-cli": "^11.0.1",
"postcss-nested": "^7.0.2",
"postcss-simple-vars": "^7.0.1",
"rimraf": "^6.0.1",
"tailwindcss": "^4.1.0"
}
}

View File

@@ -0,0 +1,7 @@
module.exports = {
plugins: {
"@tailwindcss/postcss": {},
"postcss-simple-vars": {},
"postcss-nested": {}
},
}

View File

@@ -0,0 +1,10 @@
@import "tailwindcss";
/**
* A catch-all path to Django template files, JavaScript, and Python files
* that contain Tailwind CSS classes and will be scanned by Tailwind to generate the final CSS file.
*
* If your final CSS file is not being updated after code changes, you may want to broaden or narrow
* the scope of this path.
*/
@source "../../../**/*.{html,py,js}";

View File

@@ -0,0 +1,19 @@
{% load static tailwind_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Django Tailwind</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
{% tailwind_css %}
</head>
<body class="bg-gray-50 font-serif leading-normal tracking-normal">
<div class="container mx-auto">
<section class="flex items-center justify-center h-screen">
<h1 class="text-5xl">Django + Tailwind = ❤️</h1>
</section>
</div>
</body>
</html>

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class TailwindConfig(AppConfig):
name = "tailwind"

View File

@@ -0,0 +1,138 @@
import os
from django.core.management.base import CommandError, LabelCommand
from tailwind import get_config
from ...npm import NPM, NPMException
from ...utils import get_tailwind_src_path, install_pip_package
from ...validate import ValidationError, Validations
class Command(LabelCommand):
help = "Runs tailwind commands"
missing_args_message = """
Command argument is missing, please add one of the following:
init - to initialize django-tailwind app
install - to install npm packages necessary to build tailwind css
build - to compile tailwind css into production css
start - to start watching css changes for dev
check-updates - to list possible updates for tailwind css and its dependencies
update - to update tailwind css and its dependencies
Usage example:
python manage.py tailwind start
"""
npm = None
validate = None
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
self.validate = Validations()
def add_arguments(self, parser):
super(Command, self).add_arguments(parser)
parser.add_argument(
"--no-input",
action="store_true",
help="Initializes Tailwind project without user prompts",
)
parser.add_argument(
"--tailwind-version",
default="4",
choices=["3", "4"],
help="Specifies the Tailwind version to install",
)
parser.add_argument(
"--app-name",
help="Sets default app name on Tailwind project initialization",
)
parser.add_argument(
"--no-package-lock",
action="store_true",
help="Disables package-lock.json creation during install",
)
def validate_app(self):
try:
self.validate.has_settings()
app_name = get_config("TAILWIND_APP_NAME")
self.validate.is_installed(app_name)
self.validate.is_tailwind_app(app_name)
except ValidationError as err:
raise CommandError(err)
def handle(self, *labels, **options):
return self.handle_labels(*labels, **options)
def handle_labels(self, *labels, **options):
self.validate.acceptable_label(labels[0])
if labels[0] != "init":
self.validate_app()
self.npm = NPM(cwd=get_tailwind_src_path(get_config("TAILWIND_APP_NAME")))
getattr(self, "handle_" + labels[0].replace("-", "_") + "_command")(*labels[1:], **options)
def handle_init_command(self, **options):
try:
from cookiecutter.main import cookiecutter
except ImportError:
self.stdout.write("Cookiecutter is not found, installing...")
try:
install_pip_package("cookiecutter")
from cookiecutter.main import cookiecutter
except ModuleNotFoundError:
raise CommandError(
"Failed to install 'cookiecutter' via pip. Please install it manually "
"(https://pypi.org/project/cookiecutter/) and run 'python manage.py tailwind init' again."
)
try:
app_path = cookiecutter(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
output_dir=os.getcwd(),
directory=f"app_template_v{options['tailwind_version']}",
no_input=options["no_input"],
overwrite_if_exists=False,
extra_context={"app_name": options["app_name"].strip() if options.get("app_name") else "theme"},
)
app_name = os.path.basename(app_path)
self.stdout.write(
self.style.SUCCESS(
f"Tailwind application '{app_name}' "
f"has been successfully created. "
f"Please add '{app_name}' to INSTALLED_APPS in settings.py, "
f"then run the following command to install Tailwind CSS "
f"dependencies: `python manage.py tailwind install`"
)
)
except Exception as err:
raise CommandError(err)
def handle_install_command(self, **options):
args = ["install"]
if options["no_package_lock"]:
args.append("--no-package-lock")
self.npm_command(*args)
def handle_build_command(self, **options):
self.npm_command("run", "build")
def handle_start_command(self, **options):
self.npm_command("run", "start")
def handle_check_updates_command(self, **options):
self.npm_command("outdated")
def handle_update_command(self, **options):
self.npm_command("update")
def npm_command(self, *args):
try:
self.npm.command(*args)
except NPMException as err:
raise CommandError(err)
except KeyboardInterrupt:
pass

View File

@@ -0,0 +1,37 @@
import subprocess
import sys
from tailwind import get_config
class NPMException(Exception):
pass
class NPM:
cwd = None
npm_bin_path = None
def __init__(self, cwd=None, npm_bin_path=None):
self.npm_bin_path = npm_bin_path if npm_bin_path else get_config("NPM_BIN_PATH")
self.cwd = cwd
def cd(self, cwd):
self.cwd = cwd
def command(self, *args):
try:
subprocess.run([self.npm_bin_path] + list(args), cwd=self.cwd, check=True)
return True
except subprocess.CalledProcessError:
sys.exit(1)
except OSError:
raise NPMException(
"\nIt looks like node.js and/or npm is not installed or cannot be found.\n\n"
"Visit https://nodejs.org to download and install node.js for your system.\n\n"
"If you have npm installed and still getting this error message, "
"set NPM_BIN_PATH variable in settings.py to match path of NPM executable in your system.\n\n"
""
"Example:\n"
'NPM_BIN_PATH = "/usr/local/bin/npm"'
)

View File

@@ -0,0 +1,10 @@
{% load static %}
<link rel="stylesheet" type="text/css" href="{% if is_static_path %}{% static tailwind_css_path %}{% else %}{{ tailwind_css_path }}{% endif %}{% if v %}?v={{ v }}{% endif %}">
{# dev_mode is deprecated. Leaving it here to support legacy browser-sync based configs #}
{% if dev_mode %}
<script id="__bs_script__">//<![CDATA[
document.write("<script async src='//HOST:8383/browser-sync/browser-sync-client.js'><\/script>".replace("HOST", location.hostname));
//]]></script>
{% endif %}

View File

@@ -0,0 +1,3 @@
{% load static %}
<link rel="preload" href="{% if is_static_path %}{% static tailwind_css_path %}{% else %}{{ tailwind_css_path }}{% endif %}{% if v %}?v={{ v }}{% endif %}" as="style">

View File

@@ -0,0 +1,33 @@
import time
from django import template
from django.conf import settings
from tailwind import get_config
from ..utils import is_path_absolute
register = template.Library()
@register.inclusion_tag("tailwind/tags/css.html")
def tailwind_css(v=None):
if v is None and settings.DEBUG:
# append a time-based suffix to force reload of css in dev mode
v = int(time.time())
return {
"dev_mode": get_config("TAILWIND_DEV_MODE"),
"v": v,
"tailwind_css_path": get_config("TAILWIND_CSS_PATH"),
"is_static_path": not is_path_absolute(get_config("TAILWIND_CSS_PATH")),
}
@register.inclusion_tag("tailwind/tags/preload_css.html")
def tailwind_preload_css(v=None):
return {
"v": v,
"tailwind_css_path": get_config("TAILWIND_CSS_PATH"),
"is_static_path": not is_path_absolute(get_config("TAILWIND_CSS_PATH")),
}

View File

@@ -0,0 +1,34 @@
import json
import os
from django.apps import apps
DJANGO_TAILWIND_APP_DIR = os.path.dirname(__file__)
def get_app_path(app_name):
app_label = app_name.split(".")[-1]
return apps.get_app_config(app_label).path
def get_tailwind_src_path(app_name):
return os.path.join(get_app_path(app_name), "static_src")
def get_package_json_path(app_name):
return os.path.join(get_app_path(app_name), "static_src", "package.json")
def get_package_json_contents(app_name):
with open(get_package_json_path(app_name), "r") as f:
return json.load(f)
def is_path_absolute(path):
return path.startswith("/") or path.startswith("http")
def install_pip_package(package):
import pip._internal as pip
pip.main(["install", package])

View File

@@ -0,0 +1,36 @@
import os
from django.apps import apps
from django.conf import settings
from .utils import get_tailwind_src_path
class ValidationError(Exception):
pass
class Validations:
def acceptable_label(self, label):
if label not in [
"init",
"install",
"npm",
"start",
"build",
"check-updates",
"update",
]:
raise ValidationError(f"Subcommand {label} doesn't exist")
def is_installed(self, app_name):
if not apps.is_installed(app_name):
raise ValidationError(f"{app_name} is not in INSTALLED_APPS")
def is_tailwind_app(self, app_name):
if not os.path.isfile(os.path.join(get_tailwind_src_path(app_name), "package.json")):
raise ValidationError(f"'{app_name}' isn't a Tailwind app")
def has_settings(self):
if not hasattr(settings, "TAILWIND_APP_NAME"):
raise ValidationError("TAILWIND_APP_NAME isn't set in settings.py")