feat(config): add database type selection and configuration
This commit is contained in:
6 files changed
+89
-24
No files matched your search
@@ -14,6 +14,7 @@ class ProjectMetadata:
|
||||
nested_apps: bool = False
|
||||
nested_dir: str | None = None
|
||||
use_database_url: bool = False
|
||||
database_type: str = "postgresql"
|
||||
predefined_structure: bool = False
|
||||
unified_structure: bool = False
|
||||
single_structure: bool = False
|
||||
@@ -27,6 +28,7 @@ class ProjectMetadata:
|
||||
"nested_apps": self.nested_apps,
|
||||
"nested_dir": self.nested_dir,
|
||||
"use_database_url": self.use_database_url,
|
||||
"database_type": self.database_type,
|
||||
"predefined_structure": self.predefined_structure,
|
||||
"unified_structure": self.unified_structure,
|
||||
"single_structure": self.single_structure,
|
||||
|
||||
@@ -70,7 +70,10 @@ class FileManager:
|
||||
return self._render_and_create_file(".gitignore", "project/gitignore.j2", {}, "Created .gitignore file")
|
||||
|
||||
def create_requirements(self) -> bool:
|
||||
context = {"use_database_url": self.metadata.get("use_database_url", True)}
|
||||
context = {
|
||||
"use_database_url": self.metadata.get("use_database_url", True),
|
||||
"database_type": self.metadata.get("database_type", "postgresql"),
|
||||
}
|
||||
return self._render_and_create_file(
|
||||
"requirements.txt",
|
||||
"project/requirements.j2",
|
||||
@@ -93,6 +96,7 @@ class FileManager:
|
||||
context = {
|
||||
"project_name": self.module_name,
|
||||
"use_database_url": self.metadata.get("use_database_url", True),
|
||||
"database_type": self.metadata.get("database_type", "postgresql"),
|
||||
}
|
||||
return self._render_and_create_file(
|
||||
".env.sample",
|
||||
@@ -220,6 +224,7 @@ class FileManager:
|
||||
"project_name": "core", # Project module is 'core'
|
||||
"app_names": ["apps"], # 'apps' is the main app
|
||||
"use_database_url": self.metadata.get("use_database_url", True),
|
||||
"database_type": self.metadata.get("database_type", "postgresql"),
|
||||
}
|
||||
dev_context = {"secret_key": secret_key}
|
||||
|
||||
@@ -294,6 +299,7 @@ class FileManager:
|
||||
"project_name": self.module_name,
|
||||
"app_names": [self.module_name], # Project acts as the app
|
||||
"use_database_url": self.metadata.get("use_database_url", True),
|
||||
"database_type": self.metadata.get("database_type", "postgresql"),
|
||||
}
|
||||
dev_context = {"secret_key": secret_key}
|
||||
|
||||
|
||||
@@ -18,12 +18,18 @@ DATABASES = {
|
||||
# Using individual database parameters
|
||||
DATABASES = {
|
||||
'default': {
|
||||
{% if database_type == 'mysql' %}
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'PORT': os.getenv('DB_PORT', '3306'),
|
||||
{% else %}
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'PORT': os.getenv('DB_PORT', '5432'),
|
||||
{% endif %}
|
||||
'NAME': os.getenv('DB_NAME'),
|
||||
'USER': os.getenv('DB_USER'),
|
||||
'PASSWORD': os.getenv('DB_PASSWORD'),
|
||||
'HOST': os.getenv('DB_HOST'),
|
||||
'PORT': os.getenv('DB_PORT'),
|
||||
'HOST': os.getenv('DB_HOST'),
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
@@ -5,7 +5,11 @@ ALLOWED_HOSTS=api.your-domain.com,www.your-domain.com
|
||||
|
||||
{% if use_database_url %}
|
||||
# Database URL (recommended for production)
|
||||
DATABASE_URL=postgres://user:password@host:port/database_name
|
||||
{% if database_type == 'mysql' %}
|
||||
DATABASE_URL=mysql://user:password@localhost:3306/{{ project_name }}
|
||||
{% else %}
|
||||
DATABASE_URL=postgres://user:password@localhost:5432/{{ project_name }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# Database (individual parameters)
|
||||
DB_NAME=
|
||||
|
||||
@@ -10,6 +10,10 @@ djangorestframework_simplejwt
|
||||
drf-spectacular
|
||||
django-cors-headers
|
||||
whitenoise
|
||||
psycopg2-binary
|
||||
{% if database_type == 'mysql' %}
|
||||
mysqlclient
|
||||
{% else %}
|
||||
psycopg[binary]
|
||||
{% endif %}
|
||||
gunicorn
|
||||
{% if use_database_url %}dj-database-url{% endif %}
|
||||
+63
-20
@@ -158,7 +158,7 @@ class InputCollector:
|
||||
|
||||
def get_cicd_choice(self) -> Tuple[bool, bool]:
|
||||
UIFormatter.print_separator()
|
||||
console.print(f"\n[{UIColors.INFO}]Step 3: CI/CD Pipeline[/{UIColors.INFO}]\n")
|
||||
console.print(f"\n[{UIColors.INFO}]CI/CD Pipeline[/{UIColors.INFO}]\n")
|
||||
console.print()
|
||||
|
||||
for option in CICDOption:
|
||||
@@ -201,7 +201,6 @@ class InputCollector:
|
||||
|
||||
def get_database_config_choice(self) -> bool:
|
||||
UIFormatter.print_separator()
|
||||
console.print(f"\n[{UIColors.INFO}]Database Configuration[/{UIColors.INFO}]\n")
|
||||
console.print()
|
||||
console.print(f" [{UIColors.SUCCESS}]Y[/{UIColors.SUCCESS}] Use DATABASE_URL (recommended for production)")
|
||||
console.print(f" [{UIColors.SUCCESS}]N[/{UIColors.SUCCESS}] Use individual database parameters")
|
||||
@@ -209,6 +208,22 @@ class InputCollector:
|
||||
|
||||
return CharReader.get_yes_no(f"[{UIColors.HIGHLIGHT}]Use DATABASE_URL? (Y/n):[/{UIColors.HIGHLIGHT}]") == "y"
|
||||
|
||||
def get_database_type_choice(self) -> str:
|
||||
console.print()
|
||||
console.print(f"[{UIColors.INFO}]Select Database Type[/{UIColors.INFO}]\n")
|
||||
console.print(f" [{UIColors.SUCCESS}]1[/{UIColors.SUCCESS}] PostgreSQL")
|
||||
console.print(f" [{UIColors.SUCCESS}]2[/{UIColors.SUCCESS}] MySQL")
|
||||
console.print()
|
||||
|
||||
choice = CharReader.get_validated_char_input(
|
||||
f"[{UIColors.HIGHLIGHT}]Choose database (1/2):[/{UIColors.HIGHLIGHT}]",
|
||||
valid_keys=["1", "2"],
|
||||
default=None,
|
||||
error_message="Invalid input '{response}'. Please enter '1' or '2'.",
|
||||
)
|
||||
|
||||
return "mysql" if choice == "2" else "postgresql"
|
||||
|
||||
def _get_structure_metadata(
|
||||
self,
|
||||
project_dir: str,
|
||||
@@ -221,12 +236,15 @@ class InputCollector:
|
||||
project_name = project_dir
|
||||
app_names: list[str] = []
|
||||
|
||||
# Ask for workflows so shared templates for CI/CD can be added
|
||||
use_github, use_gitlab = self.get_cicd_choice()
|
||||
|
||||
# Ask database configuration
|
||||
# Step 2: Database Configuration
|
||||
console.print(f"\n[{UIColors.INFO}]Step 2: Database Configuration[/{UIColors.INFO}]")
|
||||
database_type = self.get_database_type_choice()
|
||||
use_database_url = self.get_database_config_choice()
|
||||
|
||||
# Step 3: CI/CD
|
||||
console.print(f"\n[{UIColors.INFO}]Step 3: CI/CD Pipeline[/{UIColors.INFO}]")
|
||||
use_github, use_gitlab = self.get_cicd_choice()
|
||||
|
||||
# Default package_name to "backend" if project_dir is "." or empty
|
||||
package_name = get_package_name(project_dir)
|
||||
|
||||
@@ -242,6 +260,7 @@ class InputCollector:
|
||||
nested_apps=True,
|
||||
nested_dir="apps",
|
||||
use_database_url=use_database_url,
|
||||
database_type=database_type,
|
||||
predefined_structure=predefined,
|
||||
unified_structure=unified,
|
||||
single_structure=single,
|
||||
@@ -277,10 +296,10 @@ class CharReader:
|
||||
return default.lower()
|
||||
|
||||
@staticmethod
|
||||
def _get_validated_char_input(
|
||||
def get_validated_char_input(
|
||||
prompt: str,
|
||||
valid_keys: list[str],
|
||||
default: str,
|
||||
default: str | None,
|
||||
error_message: str,
|
||||
) -> str:
|
||||
attempt = 0
|
||||
@@ -292,7 +311,9 @@ class CharReader:
|
||||
|
||||
# Handle Enter key (default)
|
||||
if CharReader._is_enter_key(response):
|
||||
return CharReader._handle_default(default)
|
||||
if default is not None:
|
||||
return CharReader._handle_default(default)
|
||||
# If default is None, treat as invalid input (fall through)
|
||||
|
||||
response = response.lower()
|
||||
|
||||
@@ -312,18 +333,31 @@ class CharReader:
|
||||
)
|
||||
console.print()
|
||||
else:
|
||||
# Control character entered, treat as Enter
|
||||
return CharReader._handle_default(default)
|
||||
# Control character entered (e.g. Enter with no default)
|
||||
if default is not None:
|
||||
return CharReader._handle_default(default)
|
||||
|
||||
UIFormatter.print_error("Input required.")
|
||||
attempt += 1
|
||||
if attempt < CharReader.MAX_ATTEMPTS:
|
||||
console.print(
|
||||
f"[{UIColors.MUTED}]Please try again ({attempt}/{CharReader.MAX_ATTEMPTS}).[/{UIColors.MUTED}]"
|
||||
)
|
||||
console.print()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
console.print()
|
||||
UIFormatter.print_info("\nOperation cancelled by user.")
|
||||
raise
|
||||
|
||||
# Max attempts reached - use default
|
||||
# Max attempts reached
|
||||
console.print()
|
||||
UIFormatter.print_warning(f"Maximum attempts reached. Using default: '{default.upper()}'.")
|
||||
return default.lower()
|
||||
if default is not None:
|
||||
UIFormatter.print_warning(f"Maximum attempts reached. Using default: '{default.upper()}'.")
|
||||
return default.lower()
|
||||
|
||||
UIFormatter.print_error("Maximum attempts reached. Exiting.")
|
||||
sys.exit(1)
|
||||
|
||||
@staticmethod
|
||||
def get_yes_no(prompt: str, default: str = None) -> str:
|
||||
@@ -335,7 +369,7 @@ class CharReader:
|
||||
else:
|
||||
default = "y"
|
||||
|
||||
return CharReader._get_validated_char_input(
|
||||
return CharReader.get_validated_char_input(
|
||||
prompt=prompt,
|
||||
valid_keys=["y", "n"],
|
||||
default=default,
|
||||
@@ -345,7 +379,7 @@ class CharReader:
|
||||
@staticmethod
|
||||
def get_cicd_choice(prompt: str, valid_keys: list[str], default: str = "n") -> str:
|
||||
valid_keys_str = ", ".join(f"'{k}'" for k in valid_keys)
|
||||
return CharReader._get_validated_char_input(
|
||||
return CharReader.get_validated_char_input(
|
||||
prompt=prompt,
|
||||
valid_keys=valid_keys,
|
||||
default=default,
|
||||
@@ -355,7 +389,7 @@ class CharReader:
|
||||
@staticmethod
|
||||
def get_structure_choice() -> str:
|
||||
"""Get structure type choice without requiring Enter key."""
|
||||
return CharReader._get_validated_char_input(
|
||||
return CharReader.get_validated_char_input(
|
||||
prompt=f"[{UIColors.HIGHLIGHT}]Choose structure type (1/2/3/4) [default: 1]:[/{UIColors.HIGHLIGHT}]",
|
||||
valid_keys=["1", "2", "3", "4"],
|
||||
default="1",
|
||||
@@ -488,15 +522,20 @@ def get_user_input() -> Tuple[str, str, str, list, dict]:
|
||||
|
||||
console.print()
|
||||
UIFormatter.print_separator()
|
||||
console.print(f"\n[{UIColors.INFO}]Step 2: Django Apps[/{UIColors.INFO}]\n")
|
||||
console.print(f"\n[{UIColors.INFO}]Step 2: Database Configuration[/{UIColors.INFO}]")
|
||||
database_type = collector.get_database_type_choice()
|
||||
use_database_url = collector.get_database_config_choice()
|
||||
|
||||
console.print()
|
||||
UIFormatter.print_separator()
|
||||
console.print(f"\n[{UIColors.INFO}]Step 3: Django Apps[/{UIColors.INFO}]\n")
|
||||
nested, nested_dir = collector.get_nested_apps_config()
|
||||
app_names = collector.get_app_names()
|
||||
console.print()
|
||||
|
||||
console.print(f"\n[{UIColors.INFO}]Step 4: CI/CD Pipeline[/{UIColors.INFO}]")
|
||||
use_github, use_gitlab = collector.get_cicd_choice()
|
||||
|
||||
use_database_url = collector.get_database_config_choice()
|
||||
|
||||
package_name = get_package_name(project_dir)
|
||||
metadata = ProjectMetadata(
|
||||
package_name=package_name,
|
||||
@@ -505,6 +544,7 @@ def get_user_input() -> Tuple[str, str, str, list, dict]:
|
||||
nested_apps=nested,
|
||||
nested_dir=nested_dir,
|
||||
use_database_url=use_database_url,
|
||||
database_type=database_type,
|
||||
)
|
||||
|
||||
console.print()
|
||||
@@ -532,6 +572,9 @@ def confirm_setup(project_dir: str, project_name: str, app_names: list, metadata
|
||||
|
||||
db_config = "DATABASE_URL" if metadata.get("use_database_url", True) else "Individual parameters"
|
||||
console.print(f"[{UIColors.HIGHLIGHT}]Database Config:[/{UIColors.HIGHLIGHT}] {db_config}")
|
||||
|
||||
db_type = metadata.get("database_type", "postgresql").capitalize()
|
||||
console.print(f"[{UIColors.HIGHLIGHT}]Database Type:[/{UIColors.HIGHLIGHT}] {db_type}")
|
||||
|
||||
console.print()
|
||||
UIFormatter.print_separator()
|
||||
|
||||
Reference in new issue
Block a user