diff --git a/src/djinit/core/types.py b/src/djinit/core/types.py index a70454c..da65e67 100644 --- a/src/djinit/core/types.py +++ b/src/djinit/core/types.py @@ -15,6 +15,8 @@ class ProjectMetadata: nested_dir: str | None = None use_database_url: bool = False database_type: str = "postgresql" + use_tailwind: bool = False + use_htmx: bool = False predefined_structure: bool = False unified_structure: bool = False single_structure: bool = False @@ -29,6 +31,8 @@ class ProjectMetadata: "nested_dir": self.nested_dir, "use_database_url": self.use_database_url, "database_type": self.database_type, + "use_tailwind": self.use_tailwind, + "use_htmx": self.use_htmx, "predefined_structure": self.predefined_structure, "unified_structure": self.unified_structure, "single_structure": self.single_structure, diff --git a/src/djinit/creators/files.py b/src/djinit/creators/files.py index 84dcf60..2500cfb 100644 --- a/src/djinit/creators/files.py +++ b/src/djinit/creators/files.py @@ -88,6 +88,10 @@ class FileCreator(BaseService): for name in base_settings_context["app_names"] ] + # Add Tailwind and HTMX to context + base_settings_context["use_tailwind"] = self.metadata.get("use_tailwind", False) + base_settings_context["use_htmx"] = self.metadata.get("use_htmx", False) + for filename, context in [ ("base.py", base_settings_context), ("development.py", dev_context), @@ -134,6 +138,8 @@ class FileCreator(BaseService): context = { "use_database_url": self.metadata.get("use_database_url", True), "database_type": self.metadata.get("database_type", "postgresql"), + "use_tailwind": self.metadata.get("use_tailwind", False), + "use_htmx": self.metadata.get("use_htmx", False), } self._render_and_create_file( "requirements.txt", @@ -413,6 +419,8 @@ class FileCreator(BaseService): "settings": { "use_database_url": self.metadata.get("use_database_url", True), "database_type": self.metadata.get("database_type", "postgresql"), + "use_tailwind": self.metadata.get("use_tailwind", False), + "use_htmx": self.metadata.get("use_htmx", False), }, "cicd": { "github": self.metadata.get("use_github_actions", False), diff --git a/src/djinit/templates/config/settings/base.py-tpl b/src/djinit/templates/config/settings/base.py-tpl index 1e0c491..15a5da0 100644 --- a/src/djinit/templates/config/settings/base.py-tpl +++ b/src/djinit/templates/config/settings/base.py-tpl @@ -26,6 +26,8 @@ THIRD_PARTY_APPS = [ "rest_framework_simplejwt.token_blacklist", "corsheaders", "drf_spectacular", + "django_tailwind_cli", # @IF use_tailwind + "django_htmx", # @IF use_htmx ] USER_DEFINED_APPS = [ @@ -56,6 +58,7 @@ MIDDLEWARE = [ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django_htmx.middleware.HtmxMiddleware", # @IF use_htmx ] ROOT_URLCONF = "[[ project_name ]].urls" @@ -106,10 +109,10 @@ USE_I18N = True USE_TZ = True # Static files and media files -STATIC_URL = "static/" +STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "staticfiles" -MEDIA_URL = "media/" +MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media" STORAGES = { @@ -185,3 +188,40 @@ CORS_PREFLIGHT_MAX_AGE = 86400 # 24 hours SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = "DENY" + +# @IF use_tailwind +# for tailwind +STATICFILES_DIRS = [BASE_DIR / "static/"] +TAILWIND_CLI_VERSION = "2.7.8" +TAILWIND_CLI_USE_DAISY_UI = True +TAILWIND_CLI_CONFIG = { + "theme": { + "extend": { + "colors": { + "primary": "#DC2626", + "secondary": "#EF4444", + "accent": "#FCFECC", + } + } + }, + "daisyui": { + "themes": [ + { + "light": { + "primary": "#DC2626", + "secondary": "#EF4444", + "accent": "#FCFECC", + "neutral": "#1a1a2e", + "base-100": "#ffffff", + "base-200": "#f8fafc", + "base-300": "#e2e8f0", + "info": "#0E4D80", + "success": "#16a34a", + "warning": "#f59e0b", + "error": "#DC2626", + } + } + ] + }, +} +# @ENDIF diff --git a/src/djinit/templates/config/settings/production.py-tpl b/src/djinit/templates/config/settings/production.py-tpl index 630fae0..c454e0f 100644 --- a/src/djinit/templates/config/settings/production.py-tpl +++ b/src/djinit/templates/config/settings/production.py-tpl @@ -65,3 +65,10 @@ CSRF_COOKIE_SECURE = True # Static files STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' + +# @IF use_tailwind +## Tailwind +TAILWIND_CLI_VERSION = "4.1.3" # Pin version +TAILWIND_CLI_AUTOMATIC_DOWNLOAD = False # Use pre-installed CLI +TAILWIND_CLI_DIST_CSS = "static/css/tailwind.min.css" +# @ENDIF diff --git a/src/djinit/templates/project/requirements-tpl b/src/djinit/templates/project/requirements-tpl index cb48ff9..bfefe12 100644 --- a/src/djinit/templates/project/requirements-tpl +++ b/src/djinit/templates/project/requirements-tpl @@ -10,6 +10,8 @@ djangorestframework_simplejwt drf-spectacular django-cors-headers whitenoise +django-tailwind-cli # @IF use_tailwind +django-htmx # @IF use_htmx # @IF database_type == 'mysql' mysqlclient # @ELSE diff --git a/src/djinit/ui/input.py b/src/djinit/ui/input.py index 7482a28..d43a147 100644 --- a/src/djinit/ui/input.py +++ b/src/djinit/ui/input.py @@ -23,6 +23,8 @@ class StructureOptions(TypedDict): single_module_name: str | None database_type: str use_database_url: bool + use_tailwind: bool + use_htmx: bool use_github: bool use_gitlab: bool @@ -171,6 +173,12 @@ class InputCollector: return self._get_selection("Choose database:", choices, default="postgresql") + def get_tailwind_choice(self) -> bool: + return UIFormatter.confirm("Include Tailwind CSS? (via django-tailwind-cli)", default=True) + + def get_htmx_choice(self) -> bool: + return UIFormatter.confirm("Include HTMX? (via django-htmx)", default=True) + def _get_structure_metadata(self, options: StructureOptions) -> Tuple[str, str, list[str], dict]: """Helper method to generate metadata dictionary.""" project_dir = options["project_dir"] @@ -193,6 +201,8 @@ class InputCollector: nested_dir="apps" if not options["single"] else None, use_database_url=options["use_database_url"], database_type=options["database_type"], + use_tailwind=options.get("use_tailwind", False), + use_htmx=options.get("use_htmx", False), predefined_structure=options["predefined"], unified_structure=options["unified"], single_structure=options["single"], @@ -250,6 +260,10 @@ def get_user_input() -> Tuple[str, str, str, list, dict]: database_type = collector.get_database_type_choice() use_database_url = collector.get_database_config_choice() + # Step 3: Tailwind and HTMX + use_tailwind = collector.get_tailwind_choice() + use_htmx = collector.get_htmx_choice() + # Step 3: Django Apps (Standard only) nested = False nested_dir = None @@ -273,6 +287,8 @@ def get_user_input() -> Tuple[str, str, str, list, dict]: nested_dir=nested_dir, use_database_url=use_database_url, database_type=database_type, + use_tailwind=use_tailwind, + use_htmx=use_htmx, ) return project_dir, project_name, app_names[0], app_names, metadata.to_dict() else: @@ -284,6 +300,8 @@ def get_user_input() -> Tuple[str, str, str, list, dict]: single_module_name=single_module_name, database_type=database_type, use_database_url=use_database_url, + use_tailwind=use_tailwind, + use_htmx=use_htmx, use_github=use_github, use_gitlab=use_gitlab, ) @@ -316,6 +334,12 @@ def confirm_setup(project_dir: str, project_name: str, app_names: list, metadata db_type = metadata.get("database_type", "postgresql").capitalize() console.print(f"[{UIColors.HIGHLIGHT}]Database Type:[/{UIColors.HIGHLIGHT}] {db_type}") + use_tailwind = "Yes" if metadata.get("use_tailwind", False) else "No" + console.print(f"[{UIColors.HIGHLIGHT}]Tailwind CSS:[/{UIColors.HIGHLIGHT}] {use_tailwind}") + + use_htmx = "Yes" if metadata.get("use_htmx", False) else "No" + console.print(f"[{UIColors.HIGHLIGHT}]HTMX:[/{UIColors.HIGHLIGHT}] {use_htmx}") + console.print() UIFormatter.print_separator() console.print() diff --git a/src/djinit/utils/django.py b/src/djinit/utils/django.py index 13a6d28..4acf285 100644 --- a/src/djinit/utils/django.py +++ b/src/djinit/utils/django.py @@ -44,6 +44,8 @@ class DjangoHelper: "app_names": [], "use_database_url": metadata.get("use_database_url", True), "database_type": metadata.get("database_type", "postgresql"), + "use_tailwind": metadata.get("use_tailwind", False), + "use_htmx": metadata.get("use_htmx", False), } dev_context = {"secret_key": secret_key}