from django.core.management.base import BaseCommand from django.apps import apps import inspect import importlib import yaml import os from django.conf import settings from django.template.loaders.app_directories import get_app_template_dirs class Command(BaseCommand): help = "Generate YAML support knowledge base from Django views, models, and templates" def handle(self, *args, **kwargs): output_file = "haikal_kb.yaml" kb = { "metadata": { "system_name": "Haikal", "version": "1.0", "generated_from": "Django", }, "features": {}, "user_workflows": {}, # New section for step-by-step instructions "templates": {}, "glossary": {} } def extract_doc(item): doc = inspect.getdoc(item) return doc.strip() if doc else "" def get_all_views_modules(): view_modules = [] for app in settings.INSTALLED_APPS: try: mod = importlib.import_module(f"{app}.views") view_modules.append((app, mod)) except ImportError: continue return view_modules def get_all_model_classes(): all_models = [] for model in apps.get_models(): all_models.append((model._meta.app_label, model.__name__, extract_doc(model))) return all_models def get_all_templates(): template_dirs = get_app_template_dirs('templates') templates = [] for template_dir in template_dirs: app_name = os.path.basename(os.path.dirname(os.path.dirname(template_dir))) for root, dirs, files in os.walk(template_dir): for file in files: if file.endswith(('.html', '.htm', '.txt')): rel_path = os.path.relpath(os.path.join(root, file), template_dir) with open(os.path.join(root, file), 'r', encoding='utf-8', errors='ignore') as f: try: content = f.read() # Extract template comment documentation if it exists doc = "" if '{# DOC:' in content and '#}' in content: doc_parts = content.split('{# DOC:') for part in doc_parts[1:]: if '#}' in part: doc += part.split('#}')[0].strip() + "\n" except Exception as e: self.stdout.write(self.style.WARNING(f"Error reading {rel_path}: {e}")) continue templates.append((app_name, rel_path, doc.strip())) return templates # Look for workflow documentation files def get_workflow_docs(): workflows = {} workflow_dir = os.path.join(settings.BASE_DIR, 'docs', 'workflows') if os.path.exists(workflow_dir): for file in os.listdir(workflow_dir): if file.endswith('.yaml') or file.endswith('.yml'): try: with open(os.path.join(workflow_dir, file), 'r') as f: workflow_data = yaml.safe_load(f) for workflow_name, workflow_info in workflow_data.items(): workflows[workflow_name] = workflow_info except Exception as e: self.stdout.write(self.style.WARNING(f"Error reading workflow file {file}: {e}")) return workflows # Extract views for app, mod in get_all_views_modules(): for name, obj in inspect.getmembers(mod, inspect.isfunction): doc = extract_doc(obj) if doc: kb["features"][name] = { "description": doc, "source": f"{app}.views.{name}", "type": "view_function" } # Look for @workflow decorator or WORKFLOW tag in docstring if hasattr(obj, 'workflow_steps') or 'WORKFLOW:' in doc: workflow_name = name.replace('_', ' ').title() steps = [] if hasattr(obj, 'workflow_steps'): steps = obj.workflow_steps elif 'WORKFLOW:' in doc: workflow_section = doc.split('WORKFLOW:')[1].strip() steps_text = workflow_section.split('\n') steps = [step.strip() for step in steps_text if step.strip()] if steps: kb["user_workflows"][workflow_name] = { "description": f"How to {name.replace('_', ' ')}", "steps": steps, "source": f"{app}.views.{name}" } # Extract models for app, name, doc in get_all_model_classes(): if doc: kb["features"][name] = { "description": doc, "source": f"{app}.models.{name}", "type": "model_class" } # Extract templates for app, template_path, doc in get_all_templates(): template_id = f"{app}:{template_path}" if doc: # Only include templates with documentation kb["templates"][template_id] = { "description": doc, "path": template_path, "app": app } # Add workflow documentation kb["user_workflows"].update(get_workflow_docs()) # Add manual workflow examples if no workflows were found if not kb["user_workflows"]: kb["user_workflows"] = { "Add New Car": { "description": "How to add a new car to the inventory", "steps": [ "Navigate to the Inventory section by clicking 'Inventory' in the main menu", "Click the 'Add Car' button in the top right corner", "Enter the VIN number or scan it using the barcode scanner", "Select the car make from the dropdown menu", "Select the car series from the available options", "Select the trim level for the car", "Fill in additional details like color, mileage, and price", "Click 'Save' to add the car to inventory, or 'Save & Add Another' to continue adding cars" ], "source": "manual_documentation" }, "Create New Invoice": { "description": "How to create a new invoice", "steps": [ "Navigate to the Finance section by clicking 'Finance' in the main menu", "Click the 'Invoices' tab", "Click the 'Create New Invoice' button", "Select a customer from the dropdown or click 'Add New Customer'", "Select the car(s) to include in the invoice", "Add any additional services or parts by clicking 'Add Item'", "Set the payment terms and due date", "Click 'Save Draft' to save without finalizing, or 'Finalize Invoice' to complete" ], "source": "manual_documentation" } } with open(output_file, "w", encoding="utf-8") as f: yaml.dump(kb, f, allow_unicode=True, sort_keys=False) self.stdout.write(self.style.SUCCESS(f"✅ YAML knowledge base saved to {output_file}"))