├── .env ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── apps ├── charts │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── dyn_api │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── helpers.py │ ├── migrations │ │ └── __init__.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── dyn_dt │ ├── .gitkeep │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── templatetags │ │ ├── __init__.py │ │ └── get_attribute.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py └── pages │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── build.sh ├── cli ├── __init__.py ├── common.py ├── h_ai_claude.py ├── h_code_parser.py ├── h_django.py ├── h_django_common.py ├── h_django_deps.py ├── h_django_env.py ├── h_django_settings.py ├── h_django_urls.py ├── h_files.py ├── h_git.py ├── h_shell.py ├── h_util.py └── migrations │ └── __init__.py ├── config ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── db.sqlite3 ├── docker-compose.yml ├── env.sample ├── gulpfile.js ├── gunicorn-cfg.py ├── manage.py ├── nginx └── appseed-app.conf ├── package.json ├── postcss.config.js ├── render.yaml ├── requirements.txt ├── static ├── .gitkeep ├── assets │ └── scss │ │ └── custom.scss └── img │ ├── csv.png │ └── export.png ├── templates ├── .gitkeep ├── charts │ └── index.html ├── dyn_api │ └── index.html ├── dyn_dt │ ├── index.html │ ├── items-table.html │ └── model.html └── includes │ └── sidebar.html └── vite.config.js /.env: -------------------------------------------------------------------------------- 1 | DEBUG=True 2 | 3 | SECRET_KEY= 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.DS_Store 3 | *.egg* 4 | /dist/ 5 | /.idea 6 | /docs/_build/ 7 | /node_modules/ 8 | build/ 9 | env 10 | /staticfiles/ 11 | 12 | #src 13 | #*.sqlite* 14 | 15 | #.env 16 | node_modules 17 | yarn.lock 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.0.22] 2025-05-18 4 | ### Changes 5 | 6 | - Code Refactoring 7 | - Added Dynamic Services 8 | 9 | ## [1.0.21] 2025-04-01 10 | ### Changes 11 | 12 | - Update RM (minor) 13 | 14 | ## [1.0.20] 2024-12-16 15 | ### Changes 16 | 17 | - Preselect the design in the Generator: 18 | - [Django App Generator - Argon Design](https://app-generator.dev/tools/django-generator/argon/) 19 | 20 | ## [1.0.19] 2024-12-15 21 | ### Changes 22 | 23 | > Mention [Django App Generator](https://app-generator.dev/tools/django-generator/) Service: 24 | 25 | - Access the [App Generator](https://app-generator.dev/tools/django-generator/) page 26 | - Select the preferred design 27 | - (Optional) Design Database: edit models and fields 28 | - (Optional) Edit the fields for the extended user model 29 | - (Optional) Enable OAuth for GitHub 30 | - (Optional) Add Celery (async tasks) 31 | - (Optional) Enable Dynamic API Module 32 | - Docker Scripts 33 | - Render CI/Cd Scripts 34 | 35 | **The generated Django project is available as a ZIP Archive and also uploaded to GitHub.** 36 | 37 | ## [1.0.18] 2024-12-05 38 | ### Changes 39 | 40 | - Bump UI Version 41 | - Update Media (minor) 42 | 43 | ## [1.0.17] 2024-12-04 44 | ### Changes 45 | 46 | - Bump UI Version 47 | 48 | ## [1.0.16] 2024-11-24 49 | ### Changes 50 | 51 | - Update RM Links 52 | - 👉 [Django Argon Dashboard](https://app-generator.dev/docs/products/django/argon-dashboard/index.html) - **Complete Documentation** 53 | - 👉 [Django Argon Dashboard PRO](https://app-generator.dev/product/argon-dashboard-pro/django/) - The premium version 54 | 55 | ## [1.0.15] 2024-05-18 56 | ### Changes 57 | 58 | - Updated DOCS (readme) 59 | - [Custom Development](https://appseed.us/custom-development/) Section 60 | - [CI/CD Assistance for AWS, DO](https://appseed.us/terms/#section-ci-cd) 61 | 62 | ## [1.0.14] 2024-03-31 63 | ### Changes 64 | 65 | - Added Local Statics 66 | - Added Gulp for SCSS Compilation 67 | 68 | ## [1.0.13] 2024-03-05 69 | ### Changes 70 | 71 | - Update [Custom Development](https://appseed.us/custom-development/) Section 72 | - New Pricing: `$3,999` 73 | 74 | ## [1.0.12] 2024-03-04 75 | ### Changes 76 | 77 | - Deprecate `distutils` 78 | - use `str2bool` 79 | - Update Deps 80 | - `requirements.txt` 81 | - Update README: [PRO Version](https://appseed.us/product/argon-dashboard2-pro/django/), List features 82 | - `API`, **Charts** 83 | - **DataTables** (Filters, Export) 84 | - **Celery** 85 | - **Media Files Manager** 86 | - **Extended User Profiles** 87 | 88 | ## [1.0.11] 2024-01-07 89 | ### Changes 90 | 91 | - Update LOCAL Template: 92 | - added Dashboard Page 93 | 94 | ## [1.0.10] 2024-01-04 95 | ### Changes 96 | 97 | - Update Settings: 98 | - local static 99 | - local templates 100 | 101 | ## [1.0.9] 2023-10-24 102 | ### Changes 103 | 104 | - Update Dependencies 105 | - Update README 106 | 107 | ## [1.0.8] 2023-01-31 108 | ### Changes 109 | 110 | - Bump UI: [Django Admin Argon](https://github.com/app-generator/django-admin-argon-dashboard) `v1.0.15` 111 | - DOCS Update (readme). New sections: 112 | - `How to customize the theme` 113 | - Render deployment 114 | - Configure the project to use `home/templates` 115 | - Added `custom_footer` sample 116 | 117 | ## [1.0.7] 2023-01-10 118 | ### Changes 119 | 120 | - Bump Theme Version 121 | - [Django Admin Argon](https://github.com/app-generator/django-admin-argon-dashboard) `v1.0.13` 122 | 123 | ## [1.0.6] 2023-01-07 124 | ### Changes 125 | 126 | - Move to theme-based pattern 127 | - [Django Admin Argon](https://github.com/app-generator/django-admin-argon-dashboard) 128 | - 🚀 `Deployment` 129 | - `CI/CD` flow via `Render` 130 | 131 | ## [1.0.5] 2022-08-09 132 | ### Improvements 133 | 134 | - Bump UI Version 135 | - [Argon Dashboard](https://www.creative-tim.com/product/argon-dashboard?AFFILIATE=128200) v2.0.4 136 | 137 | ## [1.0.4] 2022-06-09 138 | ### Improvements 139 | 140 | - Built with [Argon Dashboard Generator](https://appseed.us/generator/argon-dashboard/) 141 | - Timestamp: `2022-06-09 17:45` 142 | 143 | ## [1.0.3] 2022-01-16 144 | ### Improvements 145 | 146 | - Bump Django Codebase to [v2stable.0.1](https://github.com/app-generator/boilerplate-code-django-dashboard/releases) 147 | - Dependencies update (all packages) 148 | - Django==4.0.1 149 | - Settings update for Django 4.x 150 | - `New Parameter`: CSRF_TRUSTED_ORIGINS 151 | - [Origin header checking isn`t performed in older versions](https://docs.djangoproject.com/en/4.0/ref/settings/#csrf-trusted-origins) 152 | 153 | ## [1.0.2] 2021-09-20 154 | ### Improvements 155 | 156 | - Bump Django Codebase to [v2.0.4](https://github.com/app-generator/boilerplate-code-django-dashboard/releases) 157 | - Dependencies update (all packages) 158 | - Use Django==3.2.6 (latest stable version) 159 | - Better Code formatting 160 | - Improved Files organization 161 | - Optimize imports 162 | - Docker Scripts Update 163 | - Tooling: 164 | - Gulp SASS compilation script 165 | - `Update README` - Recompile SCSS (new section) 166 | - Fixes: 167 | - Patch 500 Error when authenticated users access `admin` path (no slash at the end) 168 | - Patch [#16](https://github.com/app-generator/boilerplate-code-django-dashboard/issues/16): Minor issue in Docker 169 | 170 | ## [1.0.1] 2021-01-15 171 | ### Improvements 172 | 173 | - Bump UI: [Jinja Template Argon](https://github.com/app-generator/jinja-argon-dashboard) v1.0.1 174 | - Bump Codebase: [Django Dashboard](https://github.com/app-generator/boilerplate-code-django-dashboard) v1.0.4 175 | 176 | ## [1.0.0] 2020-02-07 177 | ### Initial Release 178 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | 3 | # set environment variables 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | 7 | COPY requirements.txt . 8 | # install python dependencies 9 | RUN pip install --upgrade pip 10 | RUN pip install --no-cache-dir -r requirements.txt 11 | 12 | COPY . . 13 | 14 | # running migrations 15 | RUN python manage.py migrate 16 | 17 | # gunicorn 18 | CMD ["gunicorn", "--config", "gunicorn-cfg.py", "config.wsgi"] 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 - present [AppSeed](http://appseed.us/) 4 | 5 |
6 | 7 | ## Licensing Information 8 | 9 |
10 | 11 | | Item | - | 12 | | ---------------------------------- | --- | 13 | | License Type | MIT | 14 | | Use for print | **YES** | 15 | | Create single personal website/app | **YES** | 16 | | Create single website/app for client | **YES** | 17 | | Create multiple website/apps for clients | **YES** | 18 | | Create multiple SaaS applications | **YES** | 19 | | End-product paying users | **YES** | 20 | | Product sale | **YES** | 21 | | Remove footer credits | **YES** | 22 | | --- | --- | 23 | | Remove copyright mentions from source code | NO | 24 | | Production deployment assistance | NO | 25 | | Create HTML/CSS template for sale | NO | 26 | | Create Theme/Template for CMS for sale | NO | 27 | | Separate sale of our UI Elements | NO | 28 | 29 |
30 | 31 | --- 32 | For more information regarding licensing, please contact the AppSeed Service < *support@appseed.us* > 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Django Argon Dashboard](https://app-generator.dev/product/argon-dashboard/django/) 2 | 3 | **Open-source Django Starter** crafted on top of **[Argon Dashboard](https://app-generator.dev/product/argon-dashboard/)**, an open-source `Bootstrap` UI Kit released by [Creative-Tim](https://app-generator.dev/agency/creative-tim/). The product is designed to deliver the best possible user experience with highly customizable feature-rich pages. 4 | 5 | - 👉 [Django Argon Dashboard](https://app-generator.dev/product/argon-dashboard/django/) - `Product Page` 6 | - 👉 [Django Argon Dashboard](https://django-argon-dash2.onrender.com) - `LIVE Demo` 7 | - 👉 [Django Argon Dashboard](https://app-generator.dev/docs/products/django/argon-dashboard/index.html) - `Documentation` (learn how to use the product) 8 | 9 |
10 | 11 | ## Features 12 | 13 | - Simple, Easy-to-Extend codebase 14 | - [Argon Dashboard](https://app-generator.dev/product/argon-dashboard/) Design Integration 15 | - [Bootstrap](https://app-generator.dev/docs/templates/bootstrap.html) CSS Styling 16 | - Session-based Authentication, Password recovery 17 | - DB Persistence: SQLite (default), can be used with MySql, PgSql 18 | - Apps: 19 | - [DEMO](https://django-argon-dash2.onrender.com/dynamic-dt/product/) **Dynamic DataTables** - generate server-side datatables without coding 20 | - [DEMO](https://django-argon-dash2.onrender.com/api/product/) **Dynamic APIs** - Expose secure APIs without coding 21 | - [DEMO](https://django-argon-dash2.onrender.com/charts/) **Charts** - powered by ApexCharts 22 | - [Django CLI Package](https://app-generator.dev/docs/developer-tools/django-cli/index.html) 23 | - `Commit/rollback Git Changes` 24 | - `Backup & restore DB` 25 | - `Interact with Django Core` 26 | - `Manage Environment` 27 | - `Manage Dependencies` 28 | - [Deployment](https://app-generator.dev/docs/deployment.html) 29 | - Docker/Docker Compose Scripts 30 | - CI/CD for [Render](https://app-generator.dev/docs/deployment/render/index.html) 31 | - [Vite](https://app-generator.dev/docs/technologies/vite/index.html) for assets management 32 | 33 |
34 | 35 | ## [Documentation](https://app-generator.dev/docs/products/django/argon-dashboard/index.html) 36 | 37 | - Understand the codebase structure 38 | - Prepare the environment 39 | - Setting Up the Database 40 | - Start the Project 41 | - Switch from SQLite to MySql or PgSql 42 | - Add a new model and migrate database 43 | - Enable `Dynamic Tables` for a new model 44 | - Enable `Dynamic API` for a new model 45 | - Deploy on Render 46 | 47 | ![Django Argon Dashboard - Modern template for Django Admin Section crafted on top of a modern Bootstrap Design.](https://github.com/user-attachments/assets/f2a12c84-e752-4c36-bb90-7bf7cf63b80c) 48 | 49 |
50 | 51 | ## Deploy LIVE 52 | 53 | > One-click deploy (requires to have already an account). 54 | 55 | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy) 56 | 57 |
58 | 59 | ## [Argon Dashboard PRO Version](https://app-generator.dev/product/argon-dashboard-pro/django/) 60 | 61 | > The premium version provides more features, priority on support, and is more often updated - [Live Demo](https://django-argon-dash2-pro.onrender.com/automotive/). 62 | 63 | - Simple, Easy-to-Extend Codebase 64 | - [Argon Dashboard](https://app-generator.dev/product/argon-dashboard/) Design Integration 65 | - [Bootstrap](https://app-generator.dev/docs/templates/bootstrap.html) CSS Styling 66 | - DB Persistence: SQLite (default), can be used with MySql, PgSql 67 | - Extended Users Profiles 68 | - Authentication 69 | - Session-based 70 | - OAuth GitHub, Google 71 | - Apps: 72 | - [DEMO](https://django-argon-dash2-pro.com/dynamic-dt/product/) **Dynamic DataTables** - generate server-side datatables without coding 73 | - [DEMO](https://django-argon-dash2-pro.com/api/product/) **Dynamic APIs** - Expose secure APIs without coding 74 | - [DEMO](https://django-argon-dash2-pro.com/chart/) **Charts** - powered by ApexCharts 75 | - **React Integration** 76 | - **Media Files Manager** - empower users to manage and preview files with ease 77 | - **Celery** (async tasks) 78 | - [Django CLI Package](https://app-generator.dev/docs/developer-tools/django-cli/index.html) 79 | - `Commit/rollback Git Changes` 80 | - `Backup & restore DB` 81 | - `Interact with Django Core` 82 | - `Manage Environment` 83 | - `Manage Dependencies` 84 | - [Deployment](https://app-generator.dev/docs/deployment.html) 85 | - Docker/Docker Compose Scripts 86 | - CI/CD for [Render](https://app-generator.dev/docs/deployment/render/index.html) 87 | - [Vite](https://app-generator.dev/docs/technologies/vite/index.html) for assets management 88 | 89 | ![Django Argon Dashboard - The premium version](https://github.com/user-attachments/assets/e2bca541-ed94-4369-8ab7-361a7f112e69) 90 | 91 |
92 | 93 | ## `Customize` with [Django App Generator](https://app-generator.dev/tools/django-generator/) 94 | 95 | - Access the [App Generator](https://app-generator.dev/tools/django-generator/) page 96 | - Select the preferred design 97 | - (Optional) Design Database: edit models and fields 98 | - (Optional) Edit the fields for the extended user model 99 | - (Optional) Enable OAuth for GitHub 100 | - (Optional) Add Celery (async tasks) 101 | - (Optional) Enable Dynamic API Module 102 | - Docker Scripts 103 | - Render CI/Cd Scripts 104 | 105 | **The generated Django project is available as a ZIP Archive and also uploaded to GitHub.** 106 | 107 | ![Django Generator - User Interface for choosing the Design](https://github.com/user-attachments/assets/b989c434-1c53-49ff-8dda-b46dbfc142ac) 108 | 109 | ![Django App Generator - User Interface for Edit the Extended User Model](https://github.com/user-attachments/assets/f1a5fb68-a5ba-49c9-a3ae-91716de09912) 110 | 111 |
112 | 113 | --- 114 | [Django Argon Dashboard](https://app-generator.dev/product/argon-dashboard/django/) - Open-Source **Django** Starter provided by [App Generator](https://app-generator.dev). 115 | -------------------------------------------------------------------------------- /apps/charts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/charts/__init__.py -------------------------------------------------------------------------------- /apps/charts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/charts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ChartsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'apps.charts' 7 | -------------------------------------------------------------------------------- /apps/charts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/charts/migrations/__init__.py -------------------------------------------------------------------------------- /apps/charts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /apps/charts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/charts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from apps.charts import views 4 | 5 | urlpatterns = [ 6 | path("", views.index, name="charts"), 7 | ] -------------------------------------------------------------------------------- /apps/charts/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.core import serializers 3 | from apps.pages.models import * 4 | 5 | # Create your views here. 6 | 7 | def index(request): 8 | products = serializers.serialize('json', Product.objects.all()) 9 | context = { 10 | 'segment': 'charts', 11 | 'products': products 12 | } 13 | return render(request, 'charts/index.html', context) 14 | -------------------------------------------------------------------------------- /apps/dyn_api/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | -------------------------------------------------------------------------------- /apps/dyn_api/admin.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.contrib import admin 7 | 8 | # Register your models here. 9 | -------------------------------------------------------------------------------- /apps/dyn_api/apps.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.apps import AppConfig 7 | 8 | class DynApiConfig(AppConfig): 9 | default_auto_field = 'django.db.models.BigAutoField' 10 | name = 'apps.dyn_api' 11 | -------------------------------------------------------------------------------- /apps/dyn_api/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | import datetime, sys, inspect, importlib 7 | 8 | from functools import wraps 9 | 10 | from django.db import models 11 | from django.http import HttpResponseRedirect, HttpResponse 12 | 13 | from rest_framework import serializers 14 | 15 | class Utils: 16 | @staticmethod 17 | def get_class(config, name: str) -> models.Model: 18 | return Utils.model_name_to_class(config[name]) 19 | 20 | @staticmethod 21 | def get_manager(config, name: str) -> models.Manager: 22 | return Utils.get_class(config, name).objects 23 | 24 | @staticmethod 25 | def get_serializer(config, name: str): 26 | class Serializer(serializers.ModelSerializer): 27 | class Meta: 28 | model = Utils.get_class(config, name) 29 | fields = '__all__' 30 | 31 | return Serializer 32 | 33 | @staticmethod 34 | def model_name_to_class(name: str): 35 | 36 | model_name = name.split('.')[-1] 37 | model_import = name.replace('.'+model_name, '') 38 | 39 | module = importlib.import_module(model_import) 40 | cls = getattr(module, model_name) 41 | 42 | return cls 43 | 44 | def check_permission(function): 45 | @wraps(function) 46 | def wrap(viewRequest, *args, **kwargs): 47 | 48 | try: 49 | 50 | # Check user 51 | if viewRequest.request.user.is_authenticated: 52 | 53 | return function(viewRequest, *args, **kwargs) 54 | 55 | # For authentication for guests 56 | return HttpResponseRedirect('/login/') 57 | 58 | except Exception as e: 59 | 60 | # On error 61 | return HttpResponse( 'Error: ' + str( e ) ) 62 | 63 | return wrap 64 | -------------------------------------------------------------------------------- /apps/dyn_api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/dyn_api/migrations/__init__.py -------------------------------------------------------------------------------- /apps/dyn_api/tests.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.test import TestCase 7 | 8 | # Create your tests here. 9 | -------------------------------------------------------------------------------- /apps/dyn_api/urls.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.contrib import admin 7 | from django.urls import path 8 | from apps.dyn_api import views 9 | 10 | urlpatterns = [ 11 | path('api/', views.index, name="dynamic_api"), 12 | 13 | path('api//' , views.DynamicAPI.as_view(), name="model_api"), 14 | path('api//' , views.DynamicAPI.as_view()), 15 | path('api///' , views.DynamicAPI.as_view()), 16 | ] 17 | -------------------------------------------------------------------------------- /apps/dyn_api/views.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from django.http import Http404 7 | 8 | from django.contrib.auth.decorators import login_required 9 | from django.utils.decorators import method_decorator 10 | from django.shortcuts import render, redirect, get_object_or_404 11 | 12 | from rest_framework.generics import get_object_or_404 13 | from rest_framework.views import APIView 14 | from rest_framework.response import Response 15 | from django.http import HttpResponse 16 | 17 | from django.conf import settings 18 | 19 | DYNAMIC_API = {} 20 | 21 | try: 22 | DYNAMIC_API = getattr(settings, 'DYNAMIC_API') 23 | except: 24 | pass 25 | 26 | from .helpers import Utils 27 | 28 | def index(request): 29 | 30 | context = { 31 | 'routes' : settings.DYNAMIC_API.keys(), 32 | 'segment': 'dynamic_api' 33 | } 34 | 35 | return render(request, 'dyn_api/index.html', context) 36 | 37 | class DynamicAPI(APIView): 38 | 39 | # READ : GET api/model/id or api/model 40 | def get(self, request, **kwargs): 41 | 42 | model_id = kwargs.get('id', None) 43 | try: 44 | if model_id is not None: 45 | 46 | # Validate for integer 47 | try: 48 | model_id = int(model_id) 49 | 50 | if model_id < 0: 51 | raise ValueError('Expect positive int') 52 | 53 | except ValueError as e: 54 | return Response(data={ 55 | 'message': 'Input Error = ' + str(e), 56 | 'success': False 57 | }, status=400) 58 | 59 | thing = get_object_or_404(Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')), id=model_id) 60 | model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(instance=thing) 61 | output = model_serializer.data 62 | else: 63 | all_things = Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')).all() 64 | thing_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name')) 65 | output = [] 66 | for thing in all_things: 67 | output.append(thing_serializer(instance=thing).data) 68 | except KeyError: 69 | return Response(data={ 70 | 'message': 'this model is not activated or not exist.', 71 | 'success': False 72 | }, status=400) 73 | except Http404: 74 | return Response(data={ 75 | 'message': 'object with given id not found.', 76 | 'success': False 77 | }, status=404) 78 | return Response(data={ 79 | 'data': output, 80 | 'success': True 81 | }, status=200) 82 | 83 | # CREATE : POST api/model/ 84 | #@check_permission 85 | def post(self, request, **kwargs): 86 | try: 87 | model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(data=request.data) 88 | if model_serializer.is_valid(): 89 | model_serializer.save() 90 | else: 91 | return Response(data={ 92 | **model_serializer.errors, 93 | 'success': False 94 | }, status=400) 95 | except KeyError: 96 | return Response(data={ 97 | 'message': 'this model is not activated or not exist.', 98 | 'success': False 99 | }, status=400) 100 | return Response(data={ 101 | 'message': 'Record Created.', 102 | 'success': True 103 | }, status=200) 104 | 105 | # UPDATE : PUT api/model/id/ 106 | #@check_permission 107 | def put(self, request, **kwargs): 108 | try: 109 | thing = get_object_or_404(Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')), id=kwargs.get('id')) 110 | model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(instance=thing, 111 | data=request.data, 112 | partial=True) 113 | if model_serializer.is_valid(): 114 | model_serializer.save() 115 | else: 116 | return Response(data={ 117 | **model_serializer.errors, 118 | 'success': False 119 | }, status=400) 120 | except KeyError: 121 | return Response(data={ 122 | 'message': 'this model is not activated or not exist.', 123 | 'success': False 124 | }, status=400) 125 | except Http404: 126 | return Response(data={ 127 | 'message': 'object with given id not found.', 128 | 'success': False 129 | }, status=404) 130 | return Response(data={ 131 | 'message': 'Record Updated.', 132 | 'success': True 133 | }, status=200) 134 | 135 | # DELETE : DELETE api/model/id/ 136 | #@check_permission 137 | def delete(self, request, **kwargs): 138 | try: 139 | model_manager = Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')) 140 | to_delete_id = kwargs.get('id') 141 | model_manager.get(id=to_delete_id).delete() 142 | except KeyError: 143 | return Response(data={ 144 | 'message': 'this model is not activated or not exist.', 145 | 'success': False 146 | }, status=400) 147 | except Utils.get_class(DYNAMIC_API, kwargs.get('model_name')).DoesNotExist as e: 148 | return Response(data={ 149 | 'message': 'object with given id not found.', 150 | 'success': False 151 | }, status=404) 152 | return Response(data={ 153 | 'message': 'Record Deleted.', 154 | 'success': True 155 | }, status=200) 156 | -------------------------------------------------------------------------------- /apps/dyn_dt/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/dyn_dt/.gitkeep -------------------------------------------------------------------------------- /apps/dyn_dt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/dyn_dt/__init__.py -------------------------------------------------------------------------------- /apps/dyn_dt/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import * 3 | 4 | # Register your models here. 5 | 6 | admin.site.register(PageItems) 7 | admin.site.register(HideShowFilter) 8 | admin.site.register(ModelFilter) 9 | -------------------------------------------------------------------------------- /apps/dyn_dt/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | class DynDtConfig(AppConfig): 4 | default_auto_field = 'django.db.models.BigAutoField' 5 | name = 'apps.dyn_dt' 6 | -------------------------------------------------------------------------------- /apps/dyn_dt/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | # Create your forms here. 4 | -------------------------------------------------------------------------------- /apps/dyn_dt/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.9 on 2025-05-18 09:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="HideShowFilter", 15 | fields=[ 16 | ( 17 | "id", 18 | models.BigAutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("parent", models.CharField(blank=True, max_length=255, null=True)), 26 | ("key", models.CharField(max_length=255)), 27 | ("value", models.BooleanField(default=False)), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name="ModelFilter", 32 | fields=[ 33 | ( 34 | "id", 35 | models.BigAutoField( 36 | auto_created=True, 37 | primary_key=True, 38 | serialize=False, 39 | verbose_name="ID", 40 | ), 41 | ), 42 | ("parent", models.CharField(blank=True, max_length=255, null=True)), 43 | ("key", models.CharField(max_length=255)), 44 | ("value", models.CharField(max_length=255)), 45 | ], 46 | ), 47 | migrations.CreateModel( 48 | name="PageItems", 49 | fields=[ 50 | ( 51 | "id", 52 | models.BigAutoField( 53 | auto_created=True, 54 | primary_key=True, 55 | serialize=False, 56 | verbose_name="ID", 57 | ), 58 | ), 59 | ("parent", models.CharField(blank=True, max_length=255, null=True)), 60 | ("items_per_page", models.IntegerField(default=25)), 61 | ], 62 | ), 63 | ] 64 | -------------------------------------------------------------------------------- /apps/dyn_dt/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/dyn_dt/migrations/__init__.py -------------------------------------------------------------------------------- /apps/dyn_dt/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | # Create your models here. 5 | 6 | class PageItems(models.Model): 7 | parent = models.CharField(max_length=255, null=True, blank=True) 8 | items_per_page = models.IntegerField(default=25) 9 | 10 | class HideShowFilter(models.Model): 11 | parent = models.CharField(max_length=255, null=True, blank=True) 12 | key = models.CharField(max_length=255) 13 | value = models.BooleanField(default=False) 14 | 15 | def __str__(self): 16 | return self.key 17 | 18 | class ModelFilter(models.Model): 19 | parent = models.CharField(max_length=255, null=True, blank=True) 20 | key = models.CharField(max_length=255) 21 | value = models.CharField(max_length=255) 22 | 23 | def __str__(self): 24 | return self.key -------------------------------------------------------------------------------- /apps/dyn_dt/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/dyn_dt/templatetags/__init__.py -------------------------------------------------------------------------------- /apps/dyn_dt/templatetags/get_attribute.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from datetime import datetime 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.filter(name="getattribute") 8 | def getattribute(value, arg): 9 | try: 10 | attr_value = getattr(value, arg) 11 | 12 | if isinstance(attr_value, datetime): 13 | return attr_value.strftime("%Y-%m-%d %H:%M:%S") 14 | 15 | return attr_value 16 | except: 17 | return '' 18 | 19 | 20 | @register.filter 21 | def get(dict_data, key): 22 | return dict_data.get(key, []) -------------------------------------------------------------------------------- /apps/dyn_dt/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/dyn_dt/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from apps.dyn_dt import views 3 | 4 | urlpatterns = [ 5 | path('dynamic-dt/', views.index, name="dynamic_dt"), 6 | 7 | path('create-filter//', views.create_filter, name="create_filter"), 8 | path('create-page-items//', views.create_page_items, name="create_page_items"), 9 | path('create-hide-show-items//', views.create_hide_show_filter, name="create_hide_show_filter"), 10 | path('delete-filter///', views.delete_filter, name="delete_filter"), 11 | path('create//', views.create, name="create"), 12 | path('delete///', views.delete, name="delete"), 13 | path('update///', views.update, name="update"), 14 | 15 | path('export-csv//', views.ExportCSVView.as_view(), name='export_csv'), 16 | 17 | path('dynamic-dt//', views.model_dt, name="model_dt"), 18 | ] 19 | -------------------------------------------------------------------------------- /apps/dyn_dt/utils.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Q 2 | 3 | def user_filter(request, queryset, fields, fk_fields=[]): 4 | value = request.GET.get('search') 5 | 6 | if value: 7 | dynamic_q = Q() 8 | for field in fields: 9 | if field not in fk_fields: 10 | dynamic_q |= Q(**{f'{field}__icontains': value}) 11 | return queryset.filter(dynamic_q) 12 | 13 | return queryset -------------------------------------------------------------------------------- /apps/dyn_dt/views.py: -------------------------------------------------------------------------------- 1 | import requests, base64, json, csv 2 | from django.shortcuts import render, redirect, get_object_or_404 3 | from django.contrib.auth.decorators import login_required 4 | from django.utils import timezone 5 | from django.http import HttpResponse, JsonResponse 6 | from django.utils.safestring import mark_safe 7 | from django.conf import settings 8 | from django.urls import reverse 9 | from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage 10 | from django.urls import reverse 11 | from django.views import View 12 | from django.db import models 13 | from pprint import pp 14 | 15 | from apps.dyn_dt.models import ModelFilter, PageItems, HideShowFilter 16 | from apps.dyn_dt.utils import user_filter 17 | 18 | from cli import * 19 | 20 | # Create your views here. 21 | 22 | def index(request): 23 | 24 | context = { 25 | 'routes' : settings.DYNAMIC_DATATB.keys(), 26 | 'segment': 'dynamic_dt' 27 | } 28 | 29 | return render(request, 'dyn_dt/index.html', context) 30 | 31 | def create_filter(request, model_name): 32 | model_name = model_name.lower() 33 | if request.method == "POST": 34 | keys = request.POST.getlist('key') 35 | values = request.POST.getlist('value') 36 | for i in range(len(keys)): 37 | key = keys[i] 38 | value = values[i] 39 | 40 | ModelFilter.objects.update_or_create( 41 | parent=model_name, 42 | key=key, 43 | defaults={'value': value} 44 | ) 45 | 46 | return redirect(reverse('model_dt', args=[model_name])) 47 | 48 | 49 | def create_page_items(request, model_name): 50 | model_name = model_name.lower() 51 | if request.method == 'POST': 52 | items = request.POST.get('items') 53 | page_items, created = PageItems.objects.update_or_create( 54 | parent=model_name, 55 | defaults={'items_per_page':items} 56 | ) 57 | return redirect(reverse('model_dt', args=[model_name])) 58 | 59 | 60 | def create_hide_show_filter(request, model_name): 61 | model_name = model_name.lower() 62 | if request.method == "POST": 63 | data_str = list(request.POST.keys())[0] 64 | data = json.loads(data_str) 65 | 66 | HideShowFilter.objects.update_or_create( 67 | parent=model_name, 68 | key=data.get('key'), 69 | defaults={'value': data.get('value')} 70 | ) 71 | 72 | response_data = {'message': 'Model updated successfully'} 73 | return JsonResponse(response_data) 74 | 75 | return JsonResponse({'error': 'Invalid request'}, status=400) 76 | 77 | 78 | def delete_filter(request, model_name, id): 79 | model_name = model_name.lower() 80 | filter_instance = ModelFilter.objects.get(id=id, parent=model_name) 81 | filter_instance.delete() 82 | return redirect(reverse('model_dt', args=[model_name])) 83 | 84 | 85 | def get_model_field_names(model, field_type): 86 | """Returns a list of field names based on the given field type.""" 87 | return [ 88 | field.name for field in model._meta.get_fields() 89 | if isinstance(field, field_type) 90 | ] 91 | 92 | def model_dt(request, aPath): 93 | aModelName = None 94 | aModelClass = None 95 | choices_dict = {} 96 | 97 | if aPath in settings.DYNAMIC_DATATB.keys(): 98 | aModelName = settings.DYNAMIC_DATATB[aPath] 99 | aModelClass = name_to_class(aModelName) 100 | 101 | if not aModelClass: 102 | return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) 103 | 104 | #db_fields = [field.name for field in aModelClass._meta.get_fields() if not field.is_relation] 105 | db_fields = [field.name for field in aModelClass._meta.fields] 106 | fk_fields = get_model_fk_values(aModelClass) 107 | db_filters = [] 108 | for f in db_fields: 109 | if f not in fk_fields.keys(): 110 | db_filters.append( f ) 111 | 112 | for field in aModelClass._meta.fields: 113 | if field.choices: 114 | choices_dict[field.name] = field.choices 115 | 116 | field_names = [] 117 | for field_name in db_fields: 118 | fields, created = HideShowFilter.objects.get_or_create(key=field_name, parent=aPath.lower()) 119 | if fields.key in db_fields: 120 | field_names.append(fields) 121 | 122 | model_series = {} 123 | for f in db_fields: 124 | f_values = list ( aModelClass.objects.values_list( f, flat=True) ) 125 | model_series[ f ] = ', '.join( str(i) for i in f_values) 126 | 127 | # model filter 128 | filter_string = {} 129 | filter_instance = ModelFilter.objects.filter(parent=aPath.lower()) 130 | for filter_data in filter_instance: 131 | if filter_data.key in db_fields: 132 | filter_string[f'{filter_data.key}__icontains'] = filter_data.value 133 | 134 | order_by = request.GET.get('order_by', 'id') 135 | if order_by not in db_fields: 136 | order_by = 'id' 137 | 138 | queryset = aModelClass.objects.filter(**filter_string).order_by(order_by) 139 | item_list = user_filter(request, queryset, db_fields, fk_fields.keys()) 140 | 141 | # pagination 142 | page_items = PageItems.objects.filter(parent=aPath.lower()).last() 143 | p_items = 25 144 | if page_items: 145 | p_items = page_items.items_per_page 146 | 147 | page = request.GET.get('page', 1) 148 | paginator = Paginator(item_list, p_items) 149 | 150 | try: 151 | items = paginator.page(page) 152 | except PageNotAnInteger: 153 | return redirect(reverse('model_dt', args=[aPath])) 154 | except EmptyPage: 155 | return redirect(reverse('model_dt', args=[aPath])) 156 | 157 | read_only_fields = ('id', ) 158 | 159 | integer_fields = get_model_field_names(aModelClass, models.IntegerField) 160 | date_time_fields = get_model_field_names(aModelClass, models.DateTimeField) 161 | email_fields = get_model_field_names(aModelClass, models.EmailField) 162 | text_fields = get_model_field_names(aModelClass, (models.TextField, models.CharField)) 163 | 164 | context = { 165 | 'page_title': 'Dynamic DataTable - ' + aPath.lower().title(), 166 | 'link': aPath, 167 | 'field_names': field_names, 168 | 'db_field_names': db_fields, 169 | 'db_filters': db_filters, 170 | 'items': items, 171 | 'page_items': p_items, 172 | 'filter_instance': filter_instance, 173 | 'read_only_fields': read_only_fields, 174 | 175 | 'integer_fields': integer_fields, 176 | 'date_time_fields': date_time_fields, 177 | 'email_fields': email_fields, 178 | 'text_fields': text_fields, 179 | 'fk_fields_keys': list( fk_fields.keys() ), 180 | 'fk_fields': fk_fields , 181 | 'choices_dict': choices_dict, 182 | 'segment': 'dynamic_dt' 183 | } 184 | return render(request, 'dyn_dt/model.html', context) 185 | 186 | 187 | @login_required(login_url='/accounts/login/') 188 | def create(request, aPath): 189 | aModelClass = None 190 | 191 | if aPath in settings.DYNAMIC_DATATB.keys(): 192 | aModelName = settings.DYNAMIC_DATATB[aPath] 193 | aModelClass = name_to_class(aModelName) 194 | 195 | if not aModelClass: 196 | return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) 197 | 198 | if request.method == 'POST': 199 | data = {} 200 | fk_fields = get_model_fk(aModelClass) 201 | 202 | for attribute, value in request.POST.items(): 203 | if attribute == 'csrfmiddlewaretoken': 204 | continue 205 | 206 | # Process FKs 207 | if attribute in fk_fields.keys(): 208 | value = name_to_class( fk_fields[attribute] ).objects.filter(id=value).first() 209 | 210 | data[attribute] = value if value else '' 211 | 212 | aModelClass.objects.create(**data) 213 | 214 | return redirect(request.META.get('HTTP_REFERER')) 215 | 216 | 217 | @login_required(login_url='/accounts/login/') 218 | def delete(request, aPath, id): 219 | aModelClass = None 220 | 221 | if aPath in settings.DYNAMIC_DATATB.keys(): 222 | aModelName = settings.DYNAMIC_DATATB[aPath] 223 | aModelClass = name_to_class(aModelName) 224 | 225 | if not aModelClass: 226 | return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) 227 | 228 | item = aModelClass.objects.get(id=id) 229 | item.delete() 230 | return redirect(request.META.get('HTTP_REFERER')) 231 | 232 | 233 | @login_required(login_url='/accounts/login/') 234 | def update(request, aPath, id): 235 | aModelClass = None 236 | 237 | if aPath in settings.DYNAMIC_DATATB.keys(): 238 | aModelName = settings.DYNAMIC_DATATB[aPath] 239 | aModelClass = name_to_class(aModelName) 240 | 241 | if not aModelClass: 242 | return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) 243 | 244 | item = aModelClass.objects.get(id=id) 245 | fk_fields = get_model_fk(aModelClass) 246 | 247 | if request.method == 'POST': 248 | for attribute, value in request.POST.items(): 249 | 250 | if attribute == 'csrfmiddlewaretoken': 251 | continue 252 | 253 | if getattr(item, attribute, value) is not None: 254 | 255 | # Process FKs 256 | if attribute in fk_fields.keys(): 257 | value = name_to_class( fk_fields[attribute] ).objects.filter(id=value).first() 258 | 259 | setattr(item, attribute, value) 260 | 261 | item.save() 262 | 263 | return redirect(request.META.get('HTTP_REFERER')) 264 | 265 | 266 | 267 | # Export as CSV 268 | class ExportCSVView(View): 269 | def get(self, request, aPath): 270 | aModelName = None 271 | aModelClass = None 272 | 273 | if aPath in settings.DYNAMIC_DATATB.keys(): 274 | aModelName = settings.DYNAMIC_DATATB[aPath] 275 | aModelClass = name_to_class(aModelName) 276 | 277 | if not aModelClass: 278 | return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) 279 | 280 | db_field_names = [field.name for field in aModelClass._meta.get_fields()] 281 | fields = [] 282 | show_fields = HideShowFilter.objects.filter(value=False, parent=aPath.lower()) 283 | 284 | for field in show_fields: 285 | if field.key in db_field_names: 286 | fields.append(field.key) 287 | else: 288 | print(f"Field {field.key} does not exist in {aModelClass} model.") 289 | 290 | response = HttpResponse(content_type='text/csv') 291 | response['Content-Disposition'] = f'attachment; filename="{aPath.lower()}.csv"' 292 | 293 | writer = csv.writer(response) 294 | writer.writerow(fields) # Write the header 295 | 296 | filter_string = {} 297 | filter_instance = ModelFilter.objects.filter(parent=aPath.lower()) 298 | for filter_data in filter_instance: 299 | filter_string[f'{filter_data.key}__icontains'] = filter_data.value 300 | 301 | order_by = request.GET.get('order_by', 'id') 302 | queryset = aModelClass.objects.filter(**filter_string).order_by(order_by) 303 | 304 | items = user_filter(request, queryset, db_field_names) 305 | 306 | for item in items: 307 | row_data = [] 308 | for field in fields: 309 | try: 310 | row_data.append(getattr(item, field)) 311 | except AttributeError: 312 | row_data.append('') 313 | writer.writerow(row_data) 314 | 315 | return response -------------------------------------------------------------------------------- /apps/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/pages/__init__.py -------------------------------------------------------------------------------- /apps/pages/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/pages/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PagesConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.pages" 7 | -------------------------------------------------------------------------------- /apps/pages/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.9 on 2025-05-18 09:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Product", 15 | fields=[ 16 | ("id", models.AutoField(primary_key=True, serialize=False)), 17 | ("name", models.CharField(max_length=100)), 18 | ("info", models.CharField(default="", max_length=100)), 19 | ("price", models.IntegerField(blank=True, null=True)), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /apps/pages/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/apps/pages/migrations/__init__.py -------------------------------------------------------------------------------- /apps/pages/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | class Product(models.Model): 6 | id = models.AutoField(primary_key=True) 7 | name = models.CharField(max_length = 100) 8 | info = models.CharField(max_length = 100, default = '') 9 | price = models.IntegerField(blank=True, null=True) 10 | 11 | def __str__(self): 12 | return self.name 13 | -------------------------------------------------------------------------------- /apps/pages/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/pages/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path('', views.index, name='index'), 7 | ] 8 | -------------------------------------------------------------------------------- /apps/pages/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse 3 | 4 | # Create your views here. 5 | 6 | def index(request): 7 | 8 | # Page from the theme 9 | return render(request, 'pages/dashboard.html', {'segment': 'dashboard'}) 10 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # exit on error 3 | set -o errexit 4 | 5 | python -m pip install --upgrade pip 6 | 7 | pip install -r requirements.txt 8 | 9 | python manage.py collectstatic --no-input 10 | python manage.py migrate 11 | -------------------------------------------------------------------------------- /cli/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | from .common import * 7 | from .h_shell import * 8 | from .h_code_parser import * 9 | from .h_git import * 10 | from .h_util import * 11 | from .h_files import * 12 | from .h_django import * 13 | from .h_django_common import * 14 | from .h_django_deps import * 15 | from .h_django_env import * 16 | from .h_django_urls import * 17 | from .h_django_settings import * 18 | from .h_ai_claude import * 19 | 20 | -------------------------------------------------------------------------------- /cli/common.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | import os, subprocess 7 | from pprint import pp 8 | 9 | # DJANGO Globals 10 | DJANGO_APPS = None 11 | 12 | # Globals 13 | DIR_ROOT = '.' # points to the root 14 | DIR_TMPL = os.path.join( DIR_ROOT, 'templates' ) 15 | DIR_STATIC = os.path.join( DIR_ROOT, 'static' ) 16 | 17 | DIR_DJ_CONFIG = 'config' 18 | DIR_DJ_APP_DEFAULT = 'home' 19 | 20 | FILE_DJ_MANAGE_s = 'manage.py' 21 | FILE_DJ_ENV_s = '.env' 22 | FILE_DJ_DEPS_s = 'requirements.txt' 23 | FILE_DJ_URLS_s = os.path.join( DIR_DJ_CONFIG , 'urls.py' ) 24 | FILE_DJ_SETTINGS_s = os.path.join( DIR_DJ_CONFIG , 'settings.py' ) 25 | FILE_DJ_INIT_s = os.path.join( DIR_DJ_CONFIG , '__init__.py' ) 26 | FILE_DJ_MODELS_s = os.path.join( DIR_DJ_APP_DEFAULT , 'models.py' ) 27 | 28 | FILE_CI_BUILD_s = 'build.sh' 29 | FILE_CI_RENDER_s = 'render.yaml' 30 | FILE_DOCKER_s = 'Dockerfile' 31 | FILE_README_s = 'README.md' 32 | 33 | class COMMON: 34 | 35 | NULL = None # not set 36 | NA = -1 # not set 37 | OK = 0 # All good (unix style) 38 | ERR = 1 # Err bumped (unix style) 39 | NOT_FOUND = 2 # file or directory not found 40 | INPUT_ERR = 3 # file or directory not found 41 | PROCESSED = 4 42 | 43 | POS_FIRST = 'FIRST' 44 | POS_END = 'END' 45 | 46 | CHART_VERBS = ['sum', 'count', 'avg', 'min', 'max'] 47 | CHART_VERB_SUM = 'sum' 48 | CHART_VERB_COUNT = 'count' 49 | CHART_VERB_AVG = 'avg' 50 | CHART_VERB_MIN = 'min' 51 | CHART_VERB_MAX = 'max' 52 | 53 | ROWS_MAX = 9999 54 | CSV_SEP = ',' 55 | 56 | # Settings vars typologies 57 | CFG_VAR_NA = 10 # Type is undetected 58 | CFG_VAR_SIMPLE = 11 # Ex: SECRET_KEY 59 | CFG_VAR_LIST = 12 # Ex: INSTALLED_APPS, MIDDLEWARE 60 | CFG_VAR_DICT = 13 # List of Dicts, Ex: AUTH_PASSWORD_VALIDATORS 61 | 62 | TAB = ' ' 63 | TAB2 = TAB + TAB 64 | TAB3 = TAB2 + TAB 65 | 66 | TYPE_STRING = 'string' 67 | TYPE_STRING_DJ = 'models.CharField' 68 | 69 | TYPE_TEXT = 'text' 70 | TYPE_TEXT_DJ = 'models.TextField' 71 | 72 | TYPE_INT = 'int' 73 | TYPE_INT_DJ = 'models.IntegerField' 74 | 75 | TYPE_INTEGER = 'integer' 76 | TYPE_INTINTEGER_DJ = 'models.IntegerField' 77 | 78 | TYPE_NUMBER = 'number' 79 | TYPE_NUMBER_DJ = 'models.IntegerField' 80 | 81 | TYPE_FLOAT = 'float' 82 | TYPE_FLOAT_DJ = 'models.FloatField' 83 | 84 | TYPE_DATE = 'date' 85 | TYPE_DATE_DJ = 'models.DateTimeField' 86 | 87 | TYPE_TIME = 'date' 88 | TYPE_TIME_DJ = 'models.DateTimeField' 89 | 90 | # Recover errors for COMMON class 91 | def errInfo( aErrorCode ): 92 | 93 | if COMMON.NA == aErrorCode: return 'Not Set' 94 | if COMMON.ERR == aErrorCode: return 'Error Generic' 95 | if COMMON.OK == aErrorCode: return 'OK' 96 | if COMMON.NOT_FOUND == aErrorCode: return 'Not Found' 97 | if COMMON.INPUT_ERR == aErrorCode: return 'Input error' 98 | 99 | return str( aErrorCode ) 100 | 101 | def commonTxt( aCode ): 102 | 103 | if COMMON.CFG_VAR_NA == aCode: return 'CFG Var unknown typology' 104 | if COMMON.CFG_VAR_SIMPLE == aCode: return 'CFG Var SIMPLE' 105 | if COMMON.CFG_VAR_LIST == aCode: return 'CFG Var LIST' 106 | if COMMON.CFG_VAR_MIXED == aCode: return 'CFG Var MIXT (list of dicts)' 107 | 108 | return str( aCode ) 109 | 110 | class DbField: 111 | CHAR_FIELD = "models.CharField" 112 | TEXT_FIELD = "models.TextField" 113 | INTEGER_FIELD = "models.IntegerField" 114 | BOOLEAN_FIELD = "models.BooleanField" 115 | DATE_FIELD = "models.DateTimeField" 116 | FLOAT_FIELD = "models.FloatField" 117 | BOOL_FIELD = "models.BooleanField" 118 | FK_FIELD = "models.ForeignKey" 119 | NA = None 120 | 121 | def str_to_db_type( aStr ): 122 | if not aStr: 123 | return None 124 | 125 | # input normalization 126 | aStr = aStr.lower().replace(' ', '') 127 | 128 | if 'int' == aStr: return DbField.INTEGER_FIELD 129 | if 'integer' == aStr: return DbField.INTEGER_FIELD 130 | if 'num' == aStr: return DbField.INTEGER_FIELD 131 | if 'number' == aStr: return DbField.INTEGER_FIELD 132 | 133 | if 'str' == aStr: return DbField.CHAR_FIELD 134 | if 'string' == aStr: return DbField.CHAR_FIELD 135 | 136 | if 'text' == aStr: return DbField.TEXT_FIELD 137 | 138 | if 'float' == aStr: return DbField.FLOAT_FIELD 139 | 140 | if 'date' == aStr: return DbField.DATE_FIELD 141 | if 'time' == aStr: return DbField.DATE_FIELD 142 | 143 | if 'bool' == aStr: return DbField.BOOL_FIELD 144 | 145 | return DbField.NA 146 | 147 | # Pandas to Django Type Mapping 148 | django_fields = { 149 | 'int' : 'models.IntegerField(blank=True, null=True)', 150 | 'integer' : 'models.IntegerField(blank=True, null=True)', 151 | 'string' : "models.TextField(blank=True, null=True)", 152 | 'string_unique' : "models.TextField(blank=True, null=False, unique=True)", 153 | 'object' : "models.TextField(blank=True, null=True)", 154 | 'object_unique' : "models.TextField(blank=True, null=False, unique=True)", 155 | 'int64' : 'models.IntegerField(blank=True, null=True)', 156 | 'float64' : 'models.FloatField(blank=True, null=True)', 157 | 'bool' : 'models.BooleanField(null=True)', 158 | } 159 | 160 | def exec_process(aCmd): 161 | try: 162 | return os.system( aCmd ) 163 | except Exception as e: 164 | print(' > ERR: ' + str(e) ) 165 | return -1 166 | 167 | def exec_subprocess( full_cmd ): 168 | 169 | retcode = COMMON.OK 170 | stdout = '' 171 | stderr = '' 172 | 173 | try: 174 | 175 | # create project in src 176 | result = subprocess.run( full_cmd.split(' ')) 177 | 178 | retcode = result.check_returncode() 179 | 180 | except Exception as e: 181 | 182 | retcode = COMMON.ERR 183 | 184 | return retcode 185 | 186 | def h_del_lsep( line ): 187 | 188 | if line: 189 | line = line.replace('\n', '').replace('\r', '') 190 | 191 | return line 192 | 193 | def remove_prefix(text, prefix): 194 | if text.startswith(prefix): 195 | return text[len(prefix):] 196 | return text 197 | 198 | -------------------------------------------------------------------------------- /cli/h_ai_claude.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | import random, string, json, statistics, re, pprint, time 7 | from datetime import datetime 8 | 9 | from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT 10 | 11 | from django.conf import settings 12 | from django.http import JsonResponse 13 | 14 | from .common import * 15 | from .h_util import * 16 | from .h_code_parser import * 17 | from .h_django import * 18 | 19 | def model_suggest_charts(aModelClassImport, aDebug=False): 20 | 21 | start_time = time.time() 22 | 23 | retVal = COMMON.ERR 24 | 25 | model_class = name_to_class( aModelClassImport ) 26 | 27 | if not model_class: 28 | print( f" > ERR getting class for model [{aModelClassImport}]" ) 29 | return retVal, None, None, None 30 | 31 | retVal, csv_content = h_model_to_csv( aModelClassImport ) 32 | 33 | if COMMON.OK != retVal: 34 | print( f" > ERR getting CSV Representation for model [{aModelClassImport}]" ) 35 | return retVal, None, None, None 36 | 37 | csv_header = csv_content[0] 38 | csv_content.pop() 39 | 40 | aQuestion = f"I need to extract charts from a CSV with the following fields: {csv_header}." 41 | aQuestion += "Here are the first lines from the file." 42 | 43 | idx = 0 44 | for l in csv_content: 45 | 46 | idx += 1 47 | if idx > 5: 48 | break 49 | 50 | aQuestion += l + '\n' 51 | 52 | aQuestion += '\nBased on the above information and field data, please suggest all relevant charts and context information in JSON format with folowing sections:' 53 | aQuestion += '\nNode Summary: with a title about the data provided in the CSV and a description with full input.' 54 | aQuestion += '\nAnoter node "potential_uses" where are suggestions regarding the reports we can get from the input.' 55 | aQuestion += '\nSuggested charts node will present a list of chart types with a short explanation and the relevant fields correlated with the x-axis, y-axis.' 56 | aQuestion += '\nHere is the expected response that should be a valid JSON without extra text fields in response (helps response extraction and processing):' 57 | aQuestion += '\n{' 58 | aQuestion += '\n "summary":{' 59 | aQuestion += '\n "title":"CONTENT_HERE",' 60 | aQuestion += '\n "description":"CONTENT_HERE",' 61 | aQuestion += '\n "},' 62 | aQuestion += '\n "potential_uses":[' 63 | aQuestion += '\n "FIRST use explanation",' 64 | aQuestion += '\n "And the next ones",' 65 | aQuestion += '\n "],' 66 | aQuestion += '\n "suggested_charts":[' 67 | aQuestion += '\n {},' 68 | aQuestion += '\n {},' 69 | aQuestion += '\n "],' 70 | aQuestion += '\n}' 71 | 72 | if aDebug: 73 | print('>>>>>>>>>>>>>>>>>>>>>>>>') 74 | print( aQuestion ) 75 | print('<<<<<<<<<<<<<<<<<<<<<<<<') 76 | 77 | message = f"{HUMAN_PROMPT}{aQuestion}\n\n{AI_PROMPT}" 78 | 79 | client = Anthropic(api_key=getattr(settings, 'ANTHROPIC_API_KEY')) 80 | 81 | response = None 82 | response_title = None 83 | response_json = None 84 | response_conclusion = None 85 | response_data = None 86 | 87 | try: 88 | 89 | response = client.completions.create( 90 | model="claude-2.1", 91 | prompt=message, 92 | max_tokens_to_sample=1000, 93 | ) 94 | 95 | response = response.completion.split('```') 96 | response_title = response[0] 97 | response_json = response[1].replace('json', '') 98 | response_conclusion = response[2] 99 | response_data = json.loads(response_json) 100 | 101 | retVal = COMMON.OK 102 | 103 | except json.JSONDecodeError: 104 | print(f"> ERR: {str(e)}") 105 | retVal = COMMON.ERR 106 | except Exception as e: 107 | print(f"> ERR: {str(e)}") 108 | retVal = COMMON.ERR 109 | 110 | print("--- %s seconds ---" % (time.time() - start_time)) 111 | return retVal, response_title, response_conclusion, response_data 112 | 113 | ''' 114 | aCvsFile needs to be in `media` folder 115 | ''' 116 | def csv_suggest_charts(aCvsFile, aDebug=False): 117 | 118 | start_time = time.time() 119 | 120 | retVal = COMMON.ERR 121 | 122 | csv_content = file_load( os.path.join('media', aCvsFile), True ) 123 | 124 | if not csv_content: 125 | print( f" > Input file [{aCvsFile}], not found in the MEDIA folder" ) 126 | return retVal, None, None, None 127 | 128 | csv_header = csv_content[0] 129 | csv_content.pop() 130 | 131 | aQuestion = f"I need to extract charts from a CSV with the following fields: {csv_header}." 132 | aQuestion += "Here are the first lines from the file." 133 | 134 | idx = 0 135 | for l in csv_content: 136 | 137 | idx += 1 138 | if idx > 5: 139 | break 140 | 141 | aQuestion += l + '\n' 142 | 143 | aQuestion += '\nBased on the above information and field data, please suggest all relevant charts and context information in JSON format with folowing sections:' 144 | aQuestion += '\nNode Summary: with a title about the data provided in the CSV and a description with full input.' 145 | aQuestion += '\nAnoter node "potential_uses" where are suggestions regarding the reports we can get from the input.' 146 | aQuestion += '\nSuggested charts node will present a list of chart types with a short explanation and the relevant fields correlated with the x-axis, y-axis.' 147 | aQuestion += '\nHere is the expected response that should be a valid JSON without extra text fields in response (helps response extraction and processing):' 148 | aQuestion += '\n{' 149 | aQuestion += '\n "summary":{' 150 | aQuestion += '\n "title":"CONTENT_HERE",' 151 | aQuestion += '\n "description":"CONTENT_HERE",' 152 | aQuestion += '\n "},' 153 | aQuestion += '\n "potential_uses":[' 154 | aQuestion += '\n "FIRST use explanation",' 155 | aQuestion += '\n "And the next ones",' 156 | aQuestion += '\n "],' 157 | aQuestion += '\n "suggested_charts":[' 158 | aQuestion += '\n {},' 159 | aQuestion += '\n {},' 160 | aQuestion += '\n "],' 161 | aQuestion += '\n}' 162 | 163 | if aDebug: 164 | print('>>>>>>>>>>>>>>>>>>>>>>>>') 165 | print( aQuestion ) 166 | print('<<<<<<<<<<<<<<<<<<<<<<<<') 167 | 168 | message = f"{HUMAN_PROMPT}{aQuestion}\n\n{AI_PROMPT}" 169 | 170 | client = Anthropic(api_key=getattr(settings, 'ANTHROPIC_API_KEY')) 171 | 172 | response = None 173 | response_title = None 174 | response_json = None 175 | response_conclusion = None 176 | response_data = None 177 | 178 | try: 179 | 180 | response = client.completions.create( 181 | model="claude-2.1", 182 | prompt=message, 183 | max_tokens_to_sample=1000, 184 | ) 185 | 186 | response = response.completion.split('```') 187 | response_title = response[0] 188 | response_json = response[1].replace('json', '') 189 | response_conclusion = response[2] 190 | response_data = json.loads(response_json) 191 | 192 | retVal = COMMON.OK 193 | 194 | except json.JSONDecodeError: 195 | print(f"> ERR: {str(e)}") 196 | retVal = COMMON.ERR 197 | except Exception as e: 198 | print(f"> ERR: {str(e)}") 199 | retVal = COMMON.ERR 200 | 201 | print("--- %s seconds ---" % (time.time() - start_time)) 202 | return retVal, response_title, response_conclusion, response_data 203 | 204 | ''' 205 | aCvsFile needs to be in `media` folder 206 | ''' 207 | def csv_query(aCvsFile, aDataQuery, aRowLimit=10, aDebug=False): 208 | 209 | start_time = time.time() 210 | 211 | retVal = COMMON.ERR 212 | 213 | csv_content = file_load( os.path.join('media', aCvsFile), True ) 214 | 215 | if not csv_content: 216 | print( f" > Input file [{aCvsFile}], not found in the MEDIA folder" ) 217 | return retVal, None, None, None 218 | 219 | csv_header = csv_content[0] 220 | csv_content.pop() 221 | 222 | aQuestion = f"{aDataQuery,} from this CSV file with the following fields: {csv_header}." 223 | aQuestion += "Here is the content." 224 | 225 | idx = 0 226 | for l in csv_content: 227 | 228 | idx += 1 229 | if idx > aRowLimit: 230 | break 231 | 232 | aQuestion += l + '\n' 233 | 234 | aQuestion += '\n}' 235 | 236 | if aDebug: 237 | print('>>>>>>>>>>>>>>>>>>>>>>>>') 238 | print( aQuestion ) 239 | print('<<<<<<<<<<<<<<<<<<<<<<<<') 240 | 241 | message = f"{HUMAN_PROMPT}{aQuestion}\n\n{AI_PROMPT}" 242 | 243 | client = Anthropic(api_key=getattr(settings, 'ANTHROPIC_API_KEY')) 244 | 245 | response = None 246 | 247 | try: 248 | 249 | response = client.completions.create( 250 | model="claude-2.1", 251 | prompt=message, 252 | max_tokens_to_sample=1000, 253 | ) 254 | 255 | response = response.completion 256 | retVal = COMMON.OK 257 | 258 | except Exception as e: 259 | print(f"> ERR: {str(e)}") 260 | retVal = COMMON.ERR 261 | 262 | print("--- %s seconds ---" % (time.time() - start_time)) 263 | return retVal, response 264 | 265 | -------------------------------------------------------------------------------- /cli/h_code_parser.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | import os, ast, astor, importlib 7 | 8 | from .common import * 9 | from .h_files import * 10 | from .h_util import * 11 | 12 | def name_to_class(name: str): 13 | 14 | try: 15 | # Process the path 16 | cls_name = name.split('.')[-1] # Extract Class Name 17 | cls_import = name.replace('.'+cls_name, '') # Extract Import path 18 | 19 | module = importlib.import_module(cls_import) # Here is expected a valid package 20 | 21 | # If all good, a class is returned 22 | return getattr(module, cls_name) 23 | except: 24 | 25 | # Nothing found, bozzo input 26 | return None 27 | 28 | def h_model_to_csv(aModelClassImport, aNbrRows=COMMON.ROWS_MAX): 29 | 30 | retVal = COMMON.ERR 31 | dataset = [] 32 | header = [] 33 | 34 | aModelClass = name_to_class( aModelClassImport ) 35 | 36 | if not aModelClass: 37 | print( f" > ERR getting class for model [{aModelClassImport}]" ) 38 | return retVal, None 39 | 40 | for f in aModelClass._meta.fields: 41 | header.append( f.name ) 42 | 43 | dataset.append( h_list_to_str(header) ) 44 | 45 | idx = 0 46 | for row in aModelClass.objects.all(): 47 | 48 | idx += 1 49 | if idx > aNbrRows: 50 | break 51 | 52 | dataset_row = [] 53 | 54 | for f in header: 55 | data = getattr(row, f) 56 | 57 | if not data: 58 | dataset_row.append('') 59 | continue 60 | 61 | data = str( data ) 62 | 63 | if COMMON.CSV_SEP in data: 64 | 65 | dataset_row.append( f"\"{data}\"" ) 66 | 67 | else: 68 | 69 | dataset_row.append( data ) 70 | 71 | dataset.append( h_list_to_str( dataset_row ) ) 72 | 73 | return COMMON.OK, dataset 74 | 75 | class PythonFileClassManipulator: 76 | def __init__(self, file_path): 77 | self.file_path = file_path 78 | with open(file_path, 'r') as file: 79 | self.source_code = file.read() 80 | self.tree = ast.parse(self.source_code) 81 | 82 | def get_class_names(self): 83 | return [node.name for node in ast.walk(self.tree) if isinstance(node, ast.ClassDef)] 84 | 85 | def extract_class_code(self, class_name): 86 | for node in ast.walk(self.tree): 87 | if isinstance(node, ast.ClassDef) and node.name == class_name: 88 | # Get the source code lines 89 | source_lines = self.source_code.splitlines() 90 | 91 | # Find the start and end lines of the class 92 | start_line = node.lineno - 1 # ast line numbers are 1-indexed 93 | end_line = self._find_class_end(node, source_lines) 94 | 95 | # Extract and return the class code 96 | class_code = '\n'.join(source_lines[start_line:end_line]) 97 | return class_code 98 | 99 | print(f"Class '{class_name}' not found in the file.") 100 | return None 101 | 102 | def _find_class_end(self, class_node, source_lines): 103 | # Find the last line of the class definition 104 | end_line = class_node.lineno 105 | indent = self._get_indent(source_lines[end_line - 1]) 106 | 107 | for line_num in range(end_line, len(source_lines)): 108 | if line_num >= len(source_lines): 109 | return line_num 110 | line = source_lines[line_num] 111 | if line.strip() and self._get_indent(line) <= indent: 112 | return line_num 113 | 114 | return len(source_lines) 115 | 116 | def _get_indent(self, line): 117 | return len(line) - len(line.lstrip()) 118 | 119 | def replace_class(self, class_name, new_class_code): 120 | new_class_ast = ast.parse(new_class_code).body[0] 121 | 122 | for i, node in enumerate(self.tree.body): 123 | if isinstance(node, ast.ClassDef) and node.name == class_name: 124 | self.tree.body[i] = new_class_ast 125 | break 126 | else: 127 | raise ValueError(f"Class '{class_name}' not found in the file.") 128 | 129 | def save_modified_file(self, output_path=None): 130 | modified_code = astor.to_source(self.tree) 131 | output_path = output_path or self.file_path 132 | with open(output_path, 'w') as file: 133 | file.write(modified_code) 134 | 135 | def add_field_to_class(class_code, new_field_name, new_field_value): 136 | # Parse the class code into an AST 137 | tree = ast.parse(class_code) 138 | 139 | # Find the class definition 140 | for node in ast.walk(tree): 141 | if isinstance(node, ast.ClassDef): 142 | # Create a new AST node for the field 143 | new_field = ast.Assign( 144 | targets=[ast.Name(id=new_field_name, ctx=ast.Store())], 145 | value=ast.Constant(value=new_field_value) 146 | ) 147 | 148 | # Add the new field to the class body 149 | node.body.append(new_field) 150 | 151 | # Convert the modified AST back to source code 152 | modified_code = astor.to_source(tree) 153 | return modified_code 154 | 155 | def create_field_node(field_name, field_type, **kwargs): 156 | if field_type != DbField.FK_FIELD: 157 | raise ValueError("This function is specifically for adding ForeignKey fields.") 158 | 159 | related_model = kwargs.pop('related_model', None) 160 | if not related_model: 161 | raise ValueError("'related_model' is required for ForeignKey fields.") 162 | 163 | on_delete = kwargs.pop('on_delete', None) 164 | if not on_delete: 165 | raise ValueError("'on_delete' is required for ForeignKey fields.") 166 | 167 | return ast.Assign( 168 | targets=[ast.Name(id=field_name, ctx=ast.Store())], 169 | value=ast.Call( 170 | func=ast.Attribute( 171 | value=ast.Name(id='models', ctx=ast.Load()), 172 | attr='ForeignKey', 173 | ctx=ast.Load() 174 | ), 175 | args=[ast.Name(id=related_model, ctx=ast.Load())], 176 | keywords=[ 177 | ast.keyword( 178 | arg='on_delete', 179 | value=ast.Attribute( 180 | value=ast.Name(id='models', ctx=ast.Load()), 181 | attr=on_delete.split('.')[-1], 182 | ctx=ast.Load() 183 | ) 184 | ), 185 | *[ast.keyword(arg=key, value=ast.Str(s=value) if isinstance(value, str) else ast.Constant(value=value)) 186 | for key, value in kwargs.items()] 187 | ] 188 | ) 189 | ) 190 | 191 | def add_fk_to_django_model(model_code, field_name, field_type, position=None, **kwargs): 192 | # Parse the model code into an AST 193 | tree = ast.parse(model_code) 194 | 195 | # Find the class definition 196 | class_def = next((node for node in tree.body if isinstance(node, ast.ClassDef)), None) 197 | if not class_def: 198 | raise ValueError("No class definition found in the provided code.") 199 | 200 | # Create the new field node 201 | new_field = create_field_node(field_name, field_type, **kwargs) 202 | 203 | # Add the new field to the class body 204 | if position is None or position >= len(class_def.body): 205 | class_def.body.append(new_field) 206 | else: 207 | class_def.body.insert(position, new_field) 208 | 209 | # Convert the modified AST back to source code 210 | modified_code = astor.to_source(tree) 211 | return modified_code 212 | 213 | def add_field_to_django_model(model_code, field_name, field_type, position=None, **kwargs): 214 | tree = ast.parse(model_code) 215 | 216 | for node in ast.walk(tree): 217 | if isinstance(node, ast.ClassDef): 218 | # Create the new field 219 | field_args = [ast.keyword(arg=key, value=ast.Constant(value=value)) for key, value in kwargs.items()] 220 | new_field = ast.Assign( 221 | targets=[ast.Name(id=field_name, ctx=ast.Store())], 222 | value=ast.Call( 223 | func=ast.Name(id=field_type, ctx=ast.Load()), 224 | args=[], 225 | keywords=field_args 226 | ) 227 | ) 228 | 229 | # Add the new field to the class body 230 | if position is None or position >= len(node.body): 231 | node.body.append(new_field) 232 | else: 233 | node.body.insert(position, new_field) 234 | 235 | # Convert the modified AST back to source code 236 | modified_code = astor.to_source(tree) 237 | return modified_code 238 | 239 | def remove_field_from_django_model(model_code, field_name): 240 | 241 | # Parse the model code into an AST 242 | tree = ast.parse(model_code) 243 | 244 | # Find the class definition 245 | class_def = next((node for node in tree.body if isinstance(node, ast.ClassDef)), None) 246 | if not class_def: 247 | raise ValueError("No class definition found in the provided code.") 248 | 249 | # Remove the field from the class body 250 | class_def.body = [node for node in class_def.body if not (isinstance(node, ast.Assign) and 251 | isinstance(node.targets[0], ast.Name) and 252 | node.targets[0].id == field_name)] 253 | 254 | # Convert the modified AST back to source code 255 | modified_code = astor.to_source(tree) 256 | return modified_code 257 | 258 | def manipulate_python_file(file_path, class_to_replace, new_class_code): 259 | manipulator = PythonFileClassManipulator(file_path) 260 | 261 | print("Classes found in the file:") 262 | class_names = manipulator.get_class_names() 263 | for name in class_names: 264 | print(f"- {name}") 265 | 266 | if class_to_replace in class_names: 267 | manipulator.replace_class(class_to_replace, new_class_code) 268 | manipulator.save_modified_file() 269 | print(f"\nClass '{class_to_replace}' has been replaced and the file has been updated.") 270 | else: 271 | print(f"\nClass '{class_to_replace}' not found in the file.") 272 | 273 | -------------------------------------------------------------------------------- /cli/h_django.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | import random, string, time, sys 7 | from datetime import datetime 8 | import django 9 | from django.utils import timezone 10 | from django.contrib.auth import get_user_model 11 | 12 | from .common import * 13 | from .h_files import * 14 | from .h_util import * 15 | from .h_shell import * 16 | from .h_code_parser import * 17 | 18 | def get_django(): 19 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", DIR_DJ_CONFIG + ".settings") 20 | from collections import OrderedDict 21 | from django.apps import apps 22 | from django.conf import settings 23 | from django.core import management 24 | 25 | # Needs a single init 26 | if not apps.ready: 27 | apps.app_configs = OrderedDict() 28 | apps.ready = False 29 | apps.populate(settings.INSTALLED_APPS) 30 | 31 | return apps 32 | 33 | def check_db_conn(): 34 | apps = get_django() 35 | 36 | from django.db import connection 37 | from django.db.utils import OperationalError 38 | db_conn = None 39 | while not db_conn: 40 | try: 41 | connection.ensure_connection() 42 | db_conn = True 43 | except OperationalError: 44 | print('Database unavailable, waiting 1 second...') 45 | time.sleep(1) 46 | 47 | print('Connecton OK') 48 | 49 | def get_apps(): 50 | retVal = [] 51 | apps = get_django() 52 | for app in apps.get_app_configs(): 53 | retVal.append( app.name ) 54 | return retVal 55 | 56 | def get_models(aApp): 57 | 58 | retVal = [] 59 | apps = get_django() 60 | models = apps.get_app_config( aApp ).get_models() 61 | for m in models: 62 | retVal.append( m ) 63 | return retVal 64 | 65 | def get_models_name(aApp): 66 | 67 | retVal = [] 68 | models = get_models( aApp ) 69 | for m in models: 70 | retVal.append( m.__name__ ) 71 | return retVal 72 | 73 | def get_model_by_name(aApp, aModelname): 74 | 75 | models = get_models( aApp ) 76 | for m in models: 77 | if m.__name__ == aModelname: 78 | return m 79 | return None 80 | 81 | def get_model_fields(aModelClass): 82 | retVal = [] 83 | for f in aModelClass._meta.fields: 84 | retVal.append( f ) 85 | return retVal 86 | 87 | def get_model_fk(aModelClass): 88 | retVal = {} 89 | for f in aModelClass._meta.fields: 90 | if type( f ) is django.db.models.fields.related.ForeignKey: 91 | #print( ' FK: ' + ) 92 | f_class = f.related_model.__module__ + '.' + f.related_model.__name__ 93 | retVal[ f.name ] = f_class 94 | return retVal 95 | 96 | def get_model_fk_values(aModelClass): 97 | retVal = {} 98 | for f in aModelClass._meta.fields: 99 | if type( f ) is django.db.models.fields.related.ForeignKey: 100 | f_class = f.related_model.__module__ + '.' + f.related_model.__name__ 101 | retVal[ f.name ] = list( name_to_class( f_class ).objects.all() ) 102 | return retVal 103 | 104 | # v = verbose 105 | def get_model_fields_v(aModelClass): 106 | retVal = {} 107 | for f in aModelClass._meta.fields: 108 | retVal[ f.name ] = f.__class__.__name__ 109 | return retVal 110 | 111 | def check_model_migration( aModelClass ): 112 | 113 | from django.db.utils import OperationalError 114 | try: 115 | aModelClass.objects.last() 116 | return True 117 | except OperationalError: 118 | return False 119 | 120 | def extract_class_code(aFilePath, aClassName): 121 | 122 | file_content = file_load( aFilePath ) 123 | if not file_content: 124 | print(' > ERR loading file: ' + aFilePath) 125 | return None 126 | 127 | manipulator = PythonFileClassManipulator(aFilePath) 128 | return manipulator.extract_class_code(aClassName) 129 | 130 | def add_model(aAppName, aModelName): 131 | 132 | target_file = os.path.join(DIR_ROOT, aAppName, 'models.py') 133 | 134 | if aAppName not in get_apps(): 135 | print(' > ERR: App not registered: ' + aAppName) 136 | print(' |- Expected on of: ' + str( get_apps() ) ) 137 | return 138 | 139 | if aModelName in get_models_name(aAppName): 140 | print(' > ERR: ' + aModelName + ' already defined in ' + aAppName) 141 | return 142 | else: 143 | target_file_c = file_load( target_file ) 144 | model_class = f"class {aModelName}(models.Model)" 145 | if model_class in target_file_c: 146 | print(' > ERR: ' + aModelName + ' already defined in ' + aAppName) 147 | return 148 | 149 | model_code = file_load( os.path.join(DIR_ROOT, 'templates', 'generator', 'model.tmpl') ) 150 | if not model_code: 151 | print(' > ERR loading template ') 152 | return 153 | 154 | model_code = model_code.replace('__MODEL_NAME__', aModelName) 155 | 156 | file_append( target_file, model_code ) 157 | 158 | # format code 159 | exec_format_code( target_file ) 160 | 161 | # Check dry-run status 162 | exec_migration() 163 | 164 | def add_model_field(aAppName, aModelName, aFieldName, aFieldType, **kwargs): 165 | 166 | file_path = os.path.join( aAppName, 'models.py' ) 167 | 168 | # Check Input 169 | if aAppName not in get_apps(): 170 | print(' > ERR: App not registered: ' + aAppName) 171 | print(' |- Expected on of: ' + str( get_apps() ) ) 172 | return 173 | 174 | file_c = extract_class_code( file_path, aModelName ) 175 | if not file_c: 176 | print(' > ERR: Model [' +aModelName+ '] not found in app: ' + aAppName) 177 | return 178 | 179 | str1 = aFieldName + '=' 180 | str2 = aFieldName + ' =' 181 | str3 = aFieldName + ' = ' 182 | if str1 in file_c or str2 in file_c or str3 in file_c: 183 | print(' > ERR: Field [' +aFieldName+ '] already in model ' + aModelName) 184 | return 185 | 186 | aFieldTypeDB = str_to_db_type( aFieldType ) 187 | aFieldProps = {} 188 | aFieldProps['blank'] = True 189 | aFieldProps['null'] = True 190 | aFieldClass = None 191 | 192 | if DbField.CHAR_FIELD == aFieldTypeDB: 193 | aFieldProps['max_length']=255 194 | 195 | if DbField.NA == aFieldTypeDB: 196 | 197 | # We can have class type 198 | aFieldClass = name_to_class(aFieldType) 199 | if not aFieldClass: 200 | print(' > ERR: Unsupported aFieldType [' +aFieldType+ '] ') 201 | return 202 | 203 | aFieldTypeDB = DbField.FK_FIELD 204 | 205 | kwargs = aFieldProps 206 | 207 | manipulator = PythonFileClassManipulator(file_path) 208 | model_code = manipulator.extract_class_code(aModelName) 209 | model_code_upd = None 210 | 211 | if DbField.FK_FIELD == aFieldTypeDB: 212 | model_code_upd = add_fk_to_django_model( model_code, field_name=aFieldName, field_type=aFieldTypeDB, related_model=aFieldClass.__name__, on_delete="models.CASCADE", position=1, **kwargs) 213 | else: 214 | model_code_upd = add_field_to_django_model( model_code, field_name=aFieldName, field_type=aFieldTypeDB, position=1, **kwargs) 215 | 216 | manipulator.replace_class(aModelName, model_code_upd ) 217 | manipulator.save_modified_file() 218 | 219 | # format code 220 | exec_format_code( file_path ) 221 | 222 | # Check dry-run status 223 | exec_migration() 224 | 225 | def del_model_field(aAppName, aModelName, aFieldName): 226 | 227 | file_path = os.path.join( aAppName, 'models.py' ) 228 | 229 | # Check Input 230 | if aAppName not in get_apps(): 231 | print(' > ERR: App not registered: ' + aAppName) 232 | print(' |- Expected on of: ' + str( get_apps() ) ) 233 | return 234 | 235 | file_c = extract_class_code( file_path, aModelName ) 236 | if not file_c: 237 | print(' > ERR: Model [' +aModelName+ '] not found in app: ' + aAppName) 238 | return 239 | 240 | manipulator = PythonFileClassManipulator(file_path) 241 | model_code = manipulator.extract_class_code(aModelName) 242 | model_code_upd = remove_field_from_django_model(model_code, aFieldName) 243 | 244 | manipulator.replace_class(aModelName, model_code_upd ) 245 | manipulator.save_modified_file() 246 | 247 | # format code 248 | exec_format_code( file_path ) 249 | 250 | # Check dry-run status 251 | exec_migration() 252 | 253 | def get_users(): 254 | return get_user_model().objects.all() 255 | 256 | def get_user(aInput): 257 | 258 | user = get_users().filter(username=aInput).first() 259 | if not user: 260 | user = get_users().filter(email=aInput).first() 261 | 262 | return user 263 | -------------------------------------------------------------------------------- /cli/h_django_common.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | from .common import * 7 | from .h_files import * 8 | from .h_util import * 9 | 10 | def cfg_load( FILE_PATH=FILE_DJ_SETTINGS_s ): 11 | 12 | retcode = COMMON.OK 13 | content = [] 14 | 15 | try: 16 | 17 | raw_content = file_load( FILE_PATH, True ) # as list 18 | 19 | if not raw_content: 20 | print ('Err loading ['+FILE_PATH+'] file') 21 | return COMMON.NOT_FOUND, None 22 | 23 | # print('Content: ' + raw_content) 24 | content = raw_content 25 | 26 | except Exception as e: 27 | 28 | print('Err loading ['+FILE_PATH+']: ' + str(e) ) 29 | retcode = COMMON.ERR 30 | 31 | return retcode, content 32 | 33 | def cfg_save( FILE_PATH, aContent ): 34 | 35 | retcode = COMMON.OK 36 | 37 | try: 38 | 39 | file_content = '' 40 | 41 | if type( aContent ) is list: 42 | for line in aContent: 43 | file_content += line + '\n' 44 | 45 | # expect a string here 46 | else: 47 | file_content = aContent 48 | 49 | file_write( FILE_PATH, file_content) 50 | 51 | except Exception as e: 52 | 53 | retcode = COMMON.ERR 54 | 55 | return retcode 56 | 57 | def cfg_format( FILE_PATH ): 58 | 59 | retcode = COMMON.OK 60 | 61 | try: 62 | 63 | if not ( file_exists( FILE_PATH ) ): 64 | print( 'Err locate ['+FILE_PATH+']' ) 65 | return COMMON.ERR 66 | 67 | # Apply 'Black' formatter over the file 68 | result = exec_process( 'black ' + FILE_PATH ) 69 | 70 | #if COMMON.OK != result: 71 | # print('Err formating settings') 72 | # exit(1) 73 | 74 | except Exception as e: 75 | 76 | print('Err formating exeption: ' + str(e) ) 77 | retcode = COMMON.ERR 78 | 79 | return retcode 80 | 81 | def file_format( FILE_PATH ): 82 | 83 | return cfg_format( FILE_PATH ) 84 | 85 | def file_process( FILE_PATH, MARKER, aContent ): 86 | 87 | retcode, content = cfg_load( FILE_PATH ) 88 | 89 | file_c = [] 90 | 91 | MARKER_BEGIN = '#' + MARKER 92 | MARKER_END = '#END' + MARKER 93 | processing = False 94 | 95 | for line in content: 96 | 97 | if MARKER_BEGIN in line: 98 | 99 | processing = True 100 | 101 | content_new = '' 102 | content_new += MARKER_BEGIN + '\n' 103 | content_new += aContent + '\n' 104 | content_new += MARKER_END + '\n' 105 | 106 | file_c.append( content_new ) 107 | 108 | elif processing: 109 | 110 | if MARKER_END in line: 111 | processing = False 112 | 113 | else: 114 | file_c.append( line ) 115 | 116 | return cfg_save( FILE_PATH, file_c ) 117 | 118 | def h_var_typology( content ): 119 | 120 | if not content: 121 | return COMMON.CFG_VAR_NA 122 | 123 | if '=' in content and '[' in content: 124 | return COMMON.CFG_VAR_LIST 125 | 126 | if '=' in content and '{' in content: 127 | return COMMON.CFG_VAR_DICT 128 | 129 | if '=' in content and not '[' in content and not '}' in content: 130 | return COMMON.CFG_VAR_SIMPLE 131 | 132 | # Default is unknown 133 | return COMMON.CFG_VAR_NA 134 | 135 | def h_extract_sections( content ): 136 | 137 | file_imports = [] 138 | file_sections = [] 139 | 140 | for line in content: 141 | 142 | # import here 143 | if 'from ' in line or 'import ' in line: 144 | file_imports.append ( h_del_lsep( line ) ) # strip line separators 145 | 146 | # main sections here 147 | if '=' in line: 148 | file_sections.append( line.split('=')[0].strip() ) 149 | 150 | print("Imports : " + str( file_imports ) ) 151 | print("Sections : " + str( file_sections ) ) 152 | 153 | return file_sections 154 | 155 | def cfg_imports( FILE_PATH ): 156 | 157 | retcode = COMMON.OK 158 | imports = [] 159 | 160 | retcode, content = cfg_load( FILE_PATH ) 161 | 162 | if COMMON.OK != retcode: 163 | 164 | print('Err loading ['+FILE_PATH+'] file') 165 | return retcode, None 166 | 167 | for line in content: 168 | 169 | # import here 170 | if 'from ' in line or 'import ' in line: 171 | imports.append ( h_del_lsep( line ) ) # strip line separators 172 | 173 | return retcode, imports 174 | 175 | def cfg_sections( FILE_PATH ): 176 | 177 | retcode = COMMON.OK 178 | sections = [] 179 | 180 | retcode, content = cfg_load( FILE_PATH ) 181 | 182 | if COMMON.OK != retcode: 183 | 184 | print('Err loading ['+FILE_PATH+'] file') 185 | return retcode, None 186 | 187 | for line in content: 188 | 189 | # import here 190 | if '=' in line and '#' not in line: 191 | sections.append( line.split('=')[0].strip() ) 192 | 193 | return retcode, sections 194 | 195 | def cfg_var_upd( FILE_PATH, var_name, var_value, SkipQuotes=False): 196 | 197 | retcode = COMMON.NOT_FOUND 198 | 199 | retcode, content = cfg_load( FILE_PATH ) 200 | 201 | if COMMON.OK != retcode: 202 | 203 | print('Err loading ['+FILE_PATH+'] file') 204 | return retcode 205 | 206 | new_content = [] 207 | found = False 208 | for line in content: 209 | 210 | if var_name not in line: 211 | 212 | new_content.append( line ) 213 | continue 214 | 215 | found = True 216 | 217 | # variable found 218 | retcode = COMMON.OK 219 | 220 | var_typology = h_var_typology( line ) 221 | 222 | if COMMON.CFG_VAR_SIMPLE == var_typology: 223 | 224 | aValue_c = var_value 225 | if aValue_c.lower() == 'random': 226 | var_value = h_random() 227 | 228 | if SkipQuotes: 229 | line = var_name + ' = ' + var_value 230 | else: 231 | line = var_name + ' = ' + '"' + var_value + '"' 232 | 233 | new_content.append( line ) 234 | 235 | if not found: 236 | line = var_name + ' = ' + '"' + var_value + '"' 237 | new_content.append( line + '\n' ) 238 | 239 | # Variable was found and successfully processed 240 | if COMMON.OK == retcode: 241 | cfg_save( FILE_PATH, new_content ) 242 | 243 | return retcode 244 | 245 | def cfg_var_comment( FILE_PATH, var_name): 246 | 247 | retcode = COMMON.NOT_FOUND 248 | 249 | retcode, content = cfg_load( FILE_PATH ) 250 | 251 | if COMMON.OK != retcode: 252 | 253 | print('Err loading ['+FILE_PATH+'] file') 254 | return retcode 255 | 256 | new_content = [] 257 | found = False 258 | for line in content: 259 | 260 | if var_name not in line: 261 | 262 | new_content.append( line ) 263 | continue 264 | 265 | found = True 266 | 267 | # variable found 268 | retcode = COMMON.OK 269 | 270 | var_typology = h_var_typology( line ) 271 | 272 | if COMMON.CFG_VAR_SIMPLE == var_typology: 273 | 274 | line = '#' + line 275 | 276 | new_content.append( line + '\n' ) 277 | 278 | if not found: 279 | line = var_name + ' = ' + '"' + var_value + '"' 280 | new_content.append( line + '\n' ) 281 | 282 | # Variable was found and successfully processed 283 | if COMMON.OK == retcode: 284 | cfg_save( new_content ) 285 | 286 | return retcode 287 | 288 | def cfg_var_print( FILE_PATH, var_name ): 289 | 290 | retcode = COMMON.NOT_FOUND 291 | 292 | retcode, content = cfg_load( FILE_PATH ) 293 | 294 | if COMMON.OK != retcode: 295 | 296 | print('Err loading ['+FILE_PATH+'] file') 297 | return retcode 298 | 299 | new_content = [] 300 | for line in content: 301 | 302 | if var_name not in line: 303 | 304 | new_content.append( line ) 305 | continue 306 | 307 | else: 308 | 309 | # variable found 310 | retcode = COMMON.OK 311 | 312 | var_typology = h_var_typology( line ) 313 | 314 | print(' > Var found : ' + h_del_lsep( line ) ) 315 | print(' > Var typology : ' + commonTxt( var_typology ) ) 316 | 317 | if COMMON.OK != retcode: 318 | print(' > Var not found ') 319 | 320 | return retcode 321 | 322 | def cfg_section_get( FILE_PATH, section ): 323 | 324 | retcode = COMMON.NOT_FOUND 325 | 326 | retcode, content = cfg_load( FILE_PATH ) 327 | 328 | if COMMON.OK != retcode: 329 | 330 | print('Err loading ['+FILE_PATH+'] file') 331 | return retcode 332 | 333 | section_content = [] 334 | section_begin = False 335 | section_end = False 336 | retcode = COMMON.NOT_FOUND 337 | 338 | # This is used to count [, { 339 | section_control_begin = '' 340 | section_control_end = '' 341 | section_control_idx = 0 342 | 343 | for line in content: 344 | 345 | line = h_del_lsep( line ) 346 | 347 | # We have an end here 348 | if section_end: 349 | break 350 | 351 | # Computing is over 352 | if section_begin and section_control_idx == 0: 353 | section_end = True 354 | retcode = COMMON.OK 355 | continue 356 | 357 | # Computing is active 358 | if section_begin: 359 | 360 | section_content.append( line ) 361 | 362 | if section_control_begin in line: 363 | section_control_idx += 1 364 | 365 | if section_control_end in line: 366 | section_control_idx -= 1 367 | 368 | # Other things 369 | if not section_begin and section not in line: 370 | continue 371 | 372 | # Here is a match 373 | if '=' in line: 374 | section_begin = True 375 | 376 | # Detect topology 377 | var_typology = h_var_typology( line ) 378 | 379 | # Simple, one-time 380 | if COMMON.CFG_VAR_SIMPLE == var_typology: 381 | 382 | section_content.append( line ) 383 | 384 | section_end = True 385 | retcode = COMMON.OK 386 | 387 | # Lists (we can have mixed types) 388 | if COMMON.CFG_VAR_LIST == var_typology: 389 | 390 | # save the first line 391 | section_content.append( line ) 392 | 393 | section_begin = True 394 | 395 | retcode = COMMON.OK 396 | 397 | section_control_begin = '[' 398 | section_control_end = ']' 399 | section_control_idx = 1 400 | 401 | # Dicts (we can have mixed types) 402 | if COMMON.CFG_VAR_DICT == var_typology: 403 | 404 | # save the first line 405 | section_content.append( line ) 406 | 407 | section_begin = True 408 | 409 | retcode = COMMON.OK 410 | 411 | section_control_begin = '{' 412 | section_control_end = '}' 413 | section_control_idx = 1 414 | 415 | # Exit 416 | if COMMON.OK == retcode: 417 | #print( 'BEGIN >>>' ) 418 | #line_nb = 0 419 | #for item in section_content: 420 | # line_nb += 1 421 | # print ( str(line_nb) + '|'+ item ) 422 | #print( '<<< END' ) 423 | pass 424 | else: 425 | print( ' > Section ['+section+'] not found ' ) 426 | 427 | # Exit point 428 | return retcode, section_content 429 | 430 | def cfg_section_update( FILE_PATH, aSectionName, aSectionContent ): 431 | 432 | retcode = COMMON.OK 433 | 434 | retcode, content = cfg_load( FILE_PATH ) # as list 435 | 436 | if COMMON.OK != retcode: 437 | 438 | print('Err loading ['+FILE_PATH+'] file') 439 | return retcode 440 | 441 | content_p = [] 442 | 443 | section_start = False 444 | section_end = False 445 | 446 | for line in content: 447 | 448 | if line.startswith( aSectionName ): 449 | 450 | section_start = True 451 | content_p.append(aSectionContent) 452 | continue 453 | 454 | if not section_start: 455 | 456 | content_p.append( line ) 457 | 458 | if section_start and not section_end and (']' == line): 459 | 460 | section_end = True 461 | continue 462 | 463 | if section_end: 464 | 465 | content_p.append( line ) 466 | 467 | # Exit 468 | if COMMON.OK == retcode: 469 | 470 | # Save the content 471 | cfg_save(FILE_PATH, content_p ) 472 | cfg_format( FILE_PATH ) 473 | 474 | # Exit point 475 | return retcode 476 | 477 | def cfg_section_list( FILE_PATH, SECTION_NAME ): 478 | 479 | retcode = COMMON.OK 480 | 481 | # load INSTALLED_APPS 482 | retcode, content = cfg_section_get(FILE_PATH, SECTION_NAME) 483 | 484 | if COMMON.OK != retcode: 485 | 486 | print('Err loading ['+FILE_PATH+'] -> ['+SECTION_NAME+'] section') 487 | return retcode, None 488 | 489 | return retcode, content[1:(len(content)-1)] # slice, cut first & last element 490 | 491 | def cfg_section_add_item( FILE_PATH, SECTION_NAME, aItem, SkipQuotes=False ): 492 | 493 | # Check for duplicates 494 | retcode, all_items = cfg_section_list( FILE_PATH, SECTION_NAME) 495 | 496 | if COMMON.OK != retcode: 497 | 498 | print('Err loading ['+FILE_PATH+'] -> ['+SECTION_NAME+']') 499 | return retcode, None 500 | 501 | retcode, section_content = cfg_section_get( FILE_PATH, SECTION_NAME ) 502 | 503 | if COMMON.OK != retcode: 504 | 505 | print('Err loading ['+FILE_PATH+'] -> ['+SECTION_NAME+']') 506 | return retcode, None 507 | 508 | # Processing (LIST) type 509 | section_content_str = '' 510 | 511 | for line in section_content: 512 | 513 | # insert new app at the end 514 | if ']' in line: 515 | if SkipQuotes: 516 | section_content_str += ' ' + aItem + ',\n' 517 | else: 518 | section_content_str += ' "' + aItem + '",\n' 519 | 520 | section_content_str += line + '\n' 521 | 522 | retcode = cfg_section_update( FILE_PATH, SECTION_NAME, section_content_str) 523 | 524 | # Exit 525 | if COMMON.OK == retcode: 526 | print( 'Section ['+SECTION_NAME+'] updated successfully' ) 527 | else: 528 | print( 'Error updating ['+SECTION_NAME+'] section' ) 529 | 530 | # Exit point 531 | return retcode, section_content 532 | 533 | def cfg_section_add_item_first( FILE_PATH, SECTION_NAME, aItem ): 534 | 535 | # Check for duplicates 536 | retcode, all_items = cfg_section_list( FILE_PATH, SECTION_NAME) 537 | 538 | if COMMON.OK != retcode: 539 | 540 | print('Err loading ['+FILE_PATH+'] -> ['+SECTION_NAME+']') 541 | return retcode, None 542 | 543 | retcode, section_content = cfg_section_get( FILE_PATH, SECTION_NAME ) 544 | 545 | if COMMON.OK != retcode: 546 | 547 | print('Err loading ['+FILE_PATH+'] -> ['+SECTION_NAME+']') 548 | return retcode, None 549 | 550 | # Processing (LIST) type 551 | section_content_str = '' 552 | 553 | is_first_line = False 554 | for line in section_content: 555 | 556 | # insert new app at the end 557 | if '[' in line: 558 | is_first_line = True 559 | section_content_str += line + '\n' 560 | continue 561 | 562 | if is_first_line: 563 | is_first_line = False 564 | section_content_str += ' "' + aItem + '",\n' 565 | 566 | # All other lines 567 | section_content_str += line + '\n' 568 | 569 | retcode = cfg_section_update( FILE_PATH, SECTION_NAME, section_content_str) 570 | 571 | # Exit 572 | if COMMON.OK == retcode: 573 | print( 'Section ['+SECTION_NAME+'] updated successfully' ) 574 | else: 575 | print( 'Error updating ['+SECTION_NAME+'] section' ) 576 | 577 | # Exit point 578 | return retcode, section_content 579 | -------------------------------------------------------------------------------- /cli/h_django_deps.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | from .common import * 7 | from .h_files import * 8 | from .h_util import * 9 | 10 | def deps_list( ): 11 | 12 | # Use project prefix 13 | FILE_DJ_DEPS = os.path.join( DIR_ROOT, FILE_DJ_DEPS_s ) 14 | 15 | # Check app exists 16 | requirements = file_load( FILE_DJ_DEPS, True ) 17 | 18 | # Check app exists 19 | if not requirements: 20 | print('ERR: ' + FILE_DJ_DEPS+ ' not found') 21 | return None 22 | 23 | print( '> Dependencies:' ) 24 | for line in requirements: 25 | if '#' not in line: 26 | print( ' |-- ' + line ) 27 | 28 | def deps_add( aModule, aVersion=None): 29 | 30 | # Use project prefix 31 | FILE_DJ_DEPS = os.path.join( DIR_ROOT, FILE_DJ_DEPS_s ) 32 | 33 | # Check app exists 34 | requirements = file_load( FILE_DJ_DEPS, True ) 35 | 36 | # Check app exists 37 | if not requirements: 38 | print('ERR: ' + FILE_DJ_DEPS+ ' not found') 39 | return None 40 | 41 | aModule = aModule.lower() 42 | found = False 43 | 44 | requirements_p = [] 45 | for line in requirements: 46 | line = line.lower() 47 | 48 | # module laready there, update version 49 | if aModule == line or ((aModule +'==') in line): 50 | 51 | found = True 52 | if aVersion: 53 | line = aModule + '==' + aVersion 54 | else: 55 | line = aModule 56 | 57 | requirements_p.append( line ) 58 | 59 | if not found: 60 | if aVersion: 61 | aModule += '==' + aVersion 62 | 63 | requirements_p.append( aModule ) 64 | 65 | file_write( FILE_DJ_DEPS, requirements_p) 66 | 67 | def deps_delete( aModule ): 68 | 69 | # Use project prefix 70 | FILE_DJ_DEPS = os.path.join( DIR_ROOT, FILE_DJ_DEPS_s ) 71 | 72 | # Check app exists 73 | requirements = file_load( FILE_DJ_DEPS, True ) 74 | 75 | # Check app exists 76 | if not requirements: 77 | print('ERR: ' + FILE_DJ_DEPS+ ' not found') 78 | return None 79 | 80 | aModule = aModule.lower() 81 | 82 | requirements_p = [] 83 | for line in requirements: 84 | line = line.lower() 85 | 86 | # module laready there, update version 87 | if aModule == line or ((aModule +'==') in line): 88 | pass 89 | else: 90 | requirements_p.append( line ) 91 | 92 | file_write( FILE_DJ_DEPS, requirements_p) 93 | -------------------------------------------------------------------------------- /cli/h_django_env.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | from .common import * 7 | from .h_files import * 8 | from .h_util import * 9 | 10 | def env_check( ): 11 | 12 | # Use project prefix 13 | FILE_DJ_ENV = os.path.join( DIR_ROOT, FILE_DJ_ENV_s ) 14 | 15 | # Check app exists 16 | if not file_exists( FILE_DJ_ENV ): 17 | content = '# GENERATED' + '\n' 18 | file_create(FILE_DJ_ENV, content) 19 | 20 | return 21 | 22 | def env_list( ): 23 | 24 | # Use project prefix 25 | FILE_DJ_ENV = os.path.join( DIR_ROOT, FILE_DJ_ENV_s ) 26 | 27 | env_check( ) 28 | 29 | # Check app exists 30 | env = file_load( FILE_DJ_ENV, True ) 31 | 32 | # Check app exists 33 | if not env: 34 | print('ERR: ' + FILE_DJ_ENV+ ' not found') 35 | return None 36 | 37 | print( '> ENVIRONMENT:' ) 38 | for line in env: 39 | if '#' not in line: 40 | print( ' |-- ' + line ) 41 | 42 | def env_add( aEnvVar, aValue): 43 | 44 | # Use project prefix 45 | FILE_DJ_ENV = os.path.join( DIR_ROOT, FILE_DJ_ENV_s ) 46 | 47 | env_check( ) 48 | 49 | # Check app exists 50 | env = file_load( FILE_DJ_ENV, True ) 51 | 52 | # Check app exists 53 | if not env: 54 | print('ERR: ' + FILE_DJ_ENV+ ' not found') 55 | return None 56 | 57 | found = False 58 | 59 | aValue_c = aValue 60 | if aValue_c.lower() == 'random': 61 | aValue = h_random() 62 | 63 | env_p = [] 64 | for line in env: 65 | 66 | # module laready there, update version 67 | if (aEnvVar +'=') in line: 68 | found = True 69 | line = aEnvVar + '=' + aValue 70 | 71 | env_p.append( line ) 72 | 73 | if not found: 74 | aEnvVar += '=' + aValue 75 | 76 | env_p.append( aEnvVar ) 77 | 78 | file_write( FILE_DJ_ENV, env_p) 79 | 80 | def env_delete( aEnvVar ): 81 | 82 | # Use project prefix 83 | FILE_DJ_ENV = os.path.join( DIR_ROOT, FILE_DJ_ENV_s ) 84 | 85 | # Check app exists 86 | env = file_load( FILE_DJ_ENV, True ) 87 | 88 | # Check app exists 89 | if not env: 90 | return 91 | 92 | env_p = [] 93 | for line in env: 94 | 95 | # module laready there, update version 96 | if not (aEnvVar +'=') in line: 97 | env_p.append( line ) 98 | 99 | file_write( FILE_DJ_ENV, env_p) 100 | 101 | def env_comment( aEnvVar ): 102 | 103 | # Use project prefix 104 | FILE_DJ_ENV = os.path.join( DIR_ROOT, FILE_DJ_ENV_s ) 105 | 106 | # Check app exists 107 | env = file_load( FILE_DJ_ENV, True ) 108 | 109 | # Check app exists 110 | if not env: 111 | return 112 | 113 | found = False 114 | 115 | env_p = [] 116 | for line in env: 117 | 118 | # module laready there, update version 119 | if line.startswith( aEnvVar + '='): 120 | found = True 121 | line = '#' + line 122 | 123 | env_p.append( line ) 124 | 125 | file_write( FILE_DJ_ENV, env_p) 126 | 127 | def env_uncomment( aEnvVar ): 128 | 129 | # Use project prefix 130 | FILE_DJ_ENV = os.path.join( DIR_ROOT, FILE_DJ_ENV_s ) 131 | 132 | # Check app exists 133 | env = file_load( FILE_DJ_ENV, True ) 134 | 135 | # Check app exists 136 | if not env: 137 | return 138 | 139 | found = False 140 | 141 | env_p = [] 142 | for line in env: 143 | 144 | # module laready there, update version 145 | if line.startswith( '#' + aEnvVar + '='): 146 | found = True 147 | line = line[1:] 148 | 149 | env_p.append( line ) 150 | 151 | file_write( FILE_DJ_ENV, env_p) 152 | -------------------------------------------------------------------------------- /cli/h_django_settings.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | from .common import * 7 | from .h_files import * 8 | from .h_util import * 9 | from .h_django_common import * 10 | 11 | def settings_load( ): 12 | 13 | # Use project prefix 14 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 15 | 16 | return cfg_load( FILE_DJ_SETTINGS ) 17 | 18 | def settings_imports( ): 19 | 20 | # Use project prefix 21 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 22 | 23 | return cfg_imports( FILE_DJ_SETTINGS ) 24 | 25 | def settings_sections( ): 26 | 27 | # Use project prefix 28 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 29 | 30 | return cfg_sections( FILE_DJ_SETTINGS ) 31 | 32 | def settings_var_upd( aVarName, aVarValue): 33 | 34 | # Use project prefix 35 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 36 | 37 | return cfg_var_upd( FILE_DJ_SETTINGS, aVarName, aVarValue ) 38 | 39 | def settings_var_upd_bool( aVarName, aVarValue): 40 | 41 | # Use project prefix 42 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 43 | 44 | return cfg_var_upd( FILE_DJ_SETTINGS, aVarName, aVarValue, True ) 45 | 46 | def settings_var_print( aVarName ): 47 | 48 | # Use project prefix 49 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 50 | 51 | return cfg_var_print( FILE_DJ_SETTINGS, aVarName ) 52 | 53 | def settings_section_get( aSectionName ): 54 | 55 | # Use project prefix 56 | FILE_DJ_SETTINGS = os.path.join( FILE_DJ_SETTINGS_s ) 57 | 58 | return cfg_section_get( FILE_DJ_SETTINGS, aSectionName ) 59 | 60 | def settings_section_update( aSectionName, aSectionContent ): 61 | 62 | # Use project prefix 63 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 64 | 65 | return cfg_section_update( FILE_DJ_SETTINGS, aSectionName, aSectionContent) 66 | 67 | def settings_apps_list( ): 68 | 69 | # Use project prefix 70 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 71 | 72 | return cfg_section_list( FILE_DJ_SETTINGS, 'INSTALLED_APPS') 73 | 74 | def settings_apps_add( aNewApp, aPos=COMMON.POS_END ): 75 | 76 | # Use project prefix 77 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 78 | 79 | if COMMON.POS_END == aPos: 80 | cfg_section_add_item( FILE_DJ_SETTINGS, 'INSTALLED_APPS', aNewApp ) 81 | else: 82 | cfg_section_add_item_first( FILE_DJ_SETTINGS, 'INSTALLED_APPS', aNewApp ) 83 | 84 | def settings_middleware_add( aNewApp, aPos=COMMON.POS_END ): 85 | 86 | # Use project prefix 87 | FILE_DJ_SETTINGS = os.path.join( DIR_ROOT, FILE_DJ_SETTINGS_s ) 88 | 89 | if COMMON.POS_END == aPos: 90 | cfg_section_add_item( FILE_DJ_SETTINGS, 'MIDDLEWARE', aNewApp ) 91 | else: 92 | cfg_section_add_item_first( FILE_DJ_SETTINGS, 'MIDDLEWARE', aNewApp ) 93 | 94 | def settings_dyn_get(aSectionName): 95 | 96 | ret, cfg_dyn_l = settings_section_get( aSectionName ) 97 | 98 | if COMMON.OK != ret: 99 | print(' > ERR getting section: ' + aSectionName ) 100 | return ret, None 101 | 102 | # here we store the values 103 | rules = {} 104 | 105 | # cut head, tail 106 | rules_l = cfg_dyn_l[1:len(cfg_dyn_l)-1] 107 | 108 | for line in rules_l: 109 | key = line.split(':')[0].replace("'", '').replace('"', '').replace(',', '').replace(' ', '') 110 | val = line.split(':')[1].replace("'", '').replace('"', '').replace(',', '').replace(' ', '') 111 | rules[key] = val 112 | 113 | return COMMON.OK, rules 114 | 115 | def settings_dyn_set(aSectionName, aDict): 116 | 117 | aDictContent = aSectionName + ' = {' + os.linesep 118 | for k in aDict.keys(): 119 | aDictContent += COMMON.TAB + f"'{k}' : '{aDict[k]}'," + os.linesep 120 | 121 | aDictContent += '}' 122 | 123 | return settings_section_update( aSectionName, aDictContent ) 124 | 125 | def settings_dyn_add(aSectionName, aKey, aVal): 126 | 127 | ret, aDict = settings_dyn_get(aSectionName) 128 | if COMMON.OK != ret: 129 | return ret, None 130 | 131 | aDict[aKey] = aVal 132 | 133 | pp( aDict ) 134 | 135 | return settings_dyn_set(aSectionName, aDict) 136 | 137 | def settings_dyn_del(aSectionName, aKey): 138 | 139 | ret, aDict = settings_dyn_get(aSectionName) 140 | if COMMON.OK != ret: 141 | return None 142 | 143 | if aKey in aDict: 144 | del aDict[ aKey ] 145 | 146 | return settings_dyn_set(aSectionName, aDict) 147 | -------------------------------------------------------------------------------- /cli/h_django_urls.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | from .common import * 7 | from .h_files import * 8 | from .h_util import * 9 | from .h_django_common import * 10 | 11 | def urls_load( ): 12 | 13 | # Use project prefix 14 | FILE_DJ_URLS = os.path.join( DIR_ROOT, FILE_DJ_URLS_s ) 15 | 16 | return cfg_load( FILE_DJ_URLS ) 17 | 18 | def urls_imports( ): 19 | 20 | # Use project prefix 21 | FILE_DJ_URLS = os.path.join( DIR_ROOT, FILE_DJ_URLS_s ) 22 | 23 | return cfg_imports( FILE_DJ_URLS ) 24 | 25 | def urls_sections( ): 26 | 27 | # Use project prefix 28 | FILE_DJ_URLS = os.path.join( DIR_ROOT, FILE_DJ_URLS_s ) 29 | 30 | return cfg_sections( FILE_DJ_URLS ) 31 | 32 | def urls_save( content ): 33 | 34 | # Use project prefix 35 | FILE_DJ_URLS = os.path.join( DIR_ROOT, FILE_DJ_URLS_s ) 36 | 37 | return cfg_save( FILE_DJ_URLS, content ) 38 | 39 | def urls_format( ): 40 | 41 | # Use project prefix 42 | FILE_DJ_URLS = os.path.join( DIR_ROOT, FILE_DJ_URLS_s ) 43 | 44 | return cfg_format( FILE_DJ_URLS ) 45 | 46 | def urls_section_get( ): 47 | 48 | # Use project prefix 49 | FILE_DJ_URLS = os.path.join( DIR_ROOT, FILE_DJ_URLS_s ) 50 | 51 | return cfg_section_get( FILE_DJ_URLS, 'urlpatterns' ) 52 | 53 | def urls_list( ): 54 | 55 | # Use project prefix 56 | FILE_DJ_URLS = os.path.join( DIR_ROOT, FILE_DJ_URLS_s ) 57 | 58 | return cfg_section_list( FILE_DJ_URLS, 'urlpatterns' ) 59 | 60 | def urls_add_rule( aNewValue ): 61 | 62 | # Use project prefix 63 | FILE_DJ_URLS = os.path.join( DIR_ROOT, FILE_DJ_URLS_s ) 64 | 65 | return cfg_section_add_item( FILE_DJ_URLS, 'urlpatterns', aNewValue, True ) 66 | -------------------------------------------------------------------------------- /cli/h_files.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) AppSeed.us 4 | """ 5 | 6 | import os, fnmatch, shutil, json 7 | 8 | from .common import * 9 | from .h_util import * 10 | 11 | def dir_create(dir_path): 12 | try: 13 | if not os.path.exists(dir_path): 14 | os.mkdir(dir_path) 15 | except Exception as e: 16 | raise e 17 | 18 | def dir_exists ( aPath ): 19 | return os.path.isdir( aPath ) 20 | 21 | def dir_rm ( aPath ): 22 | if os.path.exists( aPath ): 23 | shutil.rmtree( aPath ) 24 | 25 | def file_exists( aPath ): 26 | 27 | try: 28 | 29 | if open( aPath, 'r'): 30 | return True 31 | 32 | except: 33 | return False 34 | 35 | def file_save( aPath, aContent ): 36 | 37 | with open(aPath, 'w') as f: 38 | 39 | if isinstance(aContent, str): 40 | f.write( aContent ) 41 | return True 42 | 43 | if isinstance(aContent, list): 44 | content_str = '' 45 | for line in aContent: 46 | content_str += line + '\n' 47 | 48 | f.write( content_str ) 49 | return True 50 | 51 | if isinstance(aContent, dict): 52 | 53 | content_str = '' 54 | for key, value in aContent.items(): 55 | content_str += key + '=' + value + '\n' 56 | 57 | f.write( content_str ) 58 | return True 59 | 60 | return False 61 | 62 | def file_append( aPath, aNewContent ): 63 | 64 | with open(aPath, "r") as file: 65 | 66 | content = file.read() 67 | content += '\n' + aNewContent 68 | 69 | return file_save( aPath, content ) 70 | 71 | return False 72 | 73 | def file_load( path, as_list=False ): 74 | 75 | try: 76 | 77 | f = open( path, 'r') 78 | if f: 79 | 80 | if as_list: 81 | content = f.read().splitlines() 82 | else: 83 | content = f.read() 84 | 85 | f.close() 86 | return content 87 | 88 | except UnicodeDecodeError as err: 89 | 90 | print(" *** UnicodeDecodeError: {0}".format(err)) 91 | return None 92 | 93 | except Exception as e: 94 | 95 | print (' *** Err loading file: ' + str( e ) ) 96 | return None 97 | 98 | # Dummy wrapper 99 | def file_content( aFilePath, aMark=None ): 100 | return file_load( aFilePath, aMark ) 101 | 102 | def file_rm ( aPath ): 103 | if file_exists( aPath ): 104 | os.remove( aPath ) 105 | 106 | def list_files( aPath, aExcludePath, aExt=None ): 107 | 108 | matches = [] 109 | 110 | for root, dirnames, filenames in os.walk( aPath ): 111 | 112 | # Exclude DIRs like ENV, migrations .. etc 113 | if any(ext in root for ext in aExcludePath): 114 | continue 115 | 116 | if aExt: 117 | 118 | for filename in fnmatch.filter(filenames, '*.' + aExt ): 119 | 120 | item = os.path.join(root, filename) 121 | 122 | matches.append( item ) 123 | else: 124 | 125 | for filename in filenames: 126 | 127 | item = os.path.join(root, filename) 128 | 129 | matches.append( item ) 130 | 131 | return matches 132 | 133 | def file_write( path, content, f_append=False ): 134 | 135 | try: 136 | 137 | f = None 138 | 139 | if file_exists( path ): 140 | if f_append: 141 | f = open( path, 'a+') 142 | else: 143 | f = open( path, 'w+') 144 | else: 145 | f = open( path, 'w+') 146 | 147 | if not f: 148 | print( 'Error open file ' ) 149 | return False 150 | 151 | if type(content) is list: 152 | content_p = '' 153 | for line in content: 154 | content_p += line + '\n' 155 | 156 | content = content_p 157 | 158 | f.seek(0) 159 | f.write( content ) 160 | f.truncate() 161 | 162 | f.close() 163 | return True 164 | 165 | except Exception as e: 166 | 167 | print( 'ERR file_write(): ' + str( e ) ) 168 | return False 169 | 170 | except: 171 | 172 | print ( ' *** Err processing file ' + str(path) ) 173 | return False 174 | 175 | def file_create( path, content='' ): 176 | 177 | return file_write( path, content ) 178 | 179 | def json_load( aPath ): 180 | 181 | if file_exists( aPath ): 182 | return json.loads( file_load( aPath ) ) 183 | 184 | return None 185 | -------------------------------------------------------------------------------- /cli/h_git.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | import os 7 | from .common import * 8 | from .h_files import * 9 | from .h_util import * 10 | 11 | def git_changes(): 12 | try: 13 | 14 | if 0 == exec_process('git diff --name-only'): 15 | return True 16 | 17 | return False 18 | 19 | except Exception as e: 20 | print(' > ERR: ' + str(e) ) 21 | return -1 22 | 23 | def git_log(): 24 | try: 25 | 26 | if 0 == exec_process('git log --oneline --graph --all'): 27 | return True 28 | 29 | return False 30 | 31 | except Exception as e: 32 | print(' > ERR: ' + str(e) ) 33 | return -1 34 | 35 | def git_commit(): 36 | try: 37 | 38 | git_comment = input(' Add Comment: ') 39 | 40 | # add dummy if not provided 41 | if not git_comment or '' == git_comment: 42 | git_comment = '' 43 | 44 | if 0 == exec_process( f"git commit -am \"{git_comment}\"" ): 45 | if 0 == exec_process( 'git push' ): 46 | return True 47 | 48 | return False 49 | 50 | except Exception as e: 51 | print(' > ERR: ' + str(e) ) 52 | return -1 53 | 54 | def git_tag(): 55 | try: 56 | 57 | git_tag = input(' TAG Name: ') 58 | git_comment = input(' TAG Comment: ') 59 | 60 | if 0 == exec_process( f"git tag -a {git_tag} -m '{git_comment}'" ): 61 | return True 62 | 63 | return False 64 | 65 | except Exception as e: 66 | print(' > ERR: ' + str(e) ) 67 | return -1 68 | 69 | def git_list_tags(): 70 | try: 71 | 72 | if 0 == exec_process('git describe --tags --abbrev=0'): 73 | return True 74 | 75 | return False 76 | 77 | except Exception as e: 78 | print(' > ERR: ' + str(e) ) 79 | return -1 80 | 81 | def git_revert(): 82 | try: 83 | 84 | confirm = input('DANGER: This command reverts the latest commit. Confirm y/N: ') 85 | if 'y' != confirm.strip().lower(): 86 | # nothing is done 87 | return False 88 | 89 | if 0 == exec_process('git reset --hard HEAD~1'): 90 | if 0 == exec_process('git push origin HEAD --force'): 91 | return True 92 | 93 | return False 94 | 95 | except Exception as e: 96 | print(' > ERR: ' + str(e) ) 97 | return -1 98 | -------------------------------------------------------------------------------- /cli/h_shell.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | import os 7 | from .common import * 8 | from .h_files import * 9 | from .h_util import * 10 | 11 | def check_migrations(): 12 | try: 13 | 14 | if 0 == exec_process('python manage.py makemigrations --check --dry-run'): 15 | return True 16 | 17 | return False 18 | 19 | except Exception as e: 20 | print(' > ERR: ' + str(e) ) 21 | return -1 22 | 23 | def exec_migration(): 24 | try: 25 | 26 | if 0 == exec_process('python manage.py makemigrations'): 27 | if 0 == exec_process('python manage.py migrate'): 28 | return True 29 | 30 | return False 31 | 32 | except Exception as e: 33 | print(' > ERR: ' + str(e) ) 34 | return -1 35 | 36 | def create_admin(): 37 | try: 38 | 39 | if 0 == exec_process('python manage.py createsuperuser '): 40 | return True 41 | 42 | return False 43 | 44 | except Exception as e: 45 | print(' > ERR: ' + str(e) ) 46 | return -1 47 | 48 | def exec_project_start(aPort=8000): 49 | try: 50 | 51 | if 0 == exec_process('python manage.py runserver ' + str(aPort) ): 52 | return True 53 | 54 | return False 55 | 56 | except Exception as e: 57 | print(' > ERR: ' + str(e) ) 58 | return -1 59 | 60 | def exec_project_shell(): 61 | try: 62 | 63 | if 0 == exec_process('python manage.py shell'): 64 | return True 65 | 66 | return False 67 | 68 | except Exception as e: 69 | print(' > ERR: ' + str(e) ) 70 | return -1 71 | 72 | def exec_format_code( aFilePath ): 73 | try: 74 | 75 | if 0 == exec_process('black ' + aFilePath ): 76 | return True 77 | 78 | return False 79 | 80 | except Exception as e: 81 | print(' > ERR: ' + str(e) ) 82 | return -1 83 | -------------------------------------------------------------------------------- /cli/h_util.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) App-Generator.dev | AppSeed.us 4 | """ 5 | 6 | import random, string 7 | from datetime import datetime 8 | 9 | from .common import * 10 | 11 | def h_random(aLen=6): 12 | letters = string.ascii_letters 13 | digits = string.digits 14 | chars = '_<>,.+' 15 | return ''.join(random.choices( letters + digits + chars, k=aLen)) 16 | 17 | def h_random_ascii(aLen=6): 18 | letters = string.ascii_letters 19 | digits = string.digits 20 | return ''.join(random.choices( letters + digits, k=aLen)) 21 | 22 | def h_ts(): 23 | return datetime.now().strftime("%Y-%m-%d-%H-%M-%S") 24 | 25 | def h_list_to_str(aList, aSep=COMMON.CSV_SEP): 26 | return aSep.join(aList) 27 | -------------------------------------------------------------------------------- /cli/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/cli/migrations/__init__.py -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/config/__init__.py -------------------------------------------------------------------------------- /config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for core project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for core project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.1.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.1/ref/settings/ 11 | """ 12 | 13 | import os, random, string 14 | from pathlib import Path 15 | from dotenv import load_dotenv 16 | from str2bool import str2bool 17 | 18 | load_dotenv() # take environment variables from .env. 19 | 20 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 21 | BASE_DIR = Path(__file__).resolve().parent.parent 22 | 23 | # Quick-start development settings - unsuitable for production 24 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 25 | 26 | # SECURITY WARNING: keep the secret key used in production secret! 27 | SECRET_KEY = os.environ.get('SECRET_KEY', 'Super_Secr3t_9999') 28 | 29 | # Enable/Disable DEBUG Mode 30 | DEBUG = str2bool(os.environ.get('DEBUG')) 31 | #print(' DEBUG -> ' + str(DEBUG) ) 32 | 33 | ALLOWED_HOSTS = ['*'] 34 | 35 | CSRF_TRUSTED_ORIGINS = ['http://localhost:8000', 'http://localhost:5085', 'http://127.0.0.1:8000', 'http://127.0.0.1:5085'] 36 | 37 | RENDER_EXTERNAL_HOSTNAME = os.environ.get('RENDER_EXTERNAL_HOSTNAME') 38 | if RENDER_EXTERNAL_HOSTNAME: 39 | ALLOWED_HOSTS.append(RENDER_EXTERNAL_HOSTNAME) 40 | 41 | # Application definition 42 | 43 | INSTALLED_APPS = [ 44 | "jazzmin", 45 | 'admin_argon.apps.AdminArgonConfig', 46 | "django.contrib.admin", 47 | "django.contrib.auth", 48 | "django.contrib.contenttypes", 49 | "django.contrib.sessions", 50 | "django.contrib.messages", 51 | "django.contrib.staticfiles", 52 | 53 | # Serve UI pages 54 | "apps.pages", 55 | 56 | # Dynamic DT 57 | "apps.dyn_dt", 58 | 59 | # Dynamic API 60 | "apps.dyn_api", 61 | 62 | # Charts 63 | "apps.charts", 64 | 65 | # Tooling API-GEN 66 | 'rest_framework', # Include DRF # <-- NEW 67 | 'rest_framework.authtoken', # Include DRF Auth # <-- NEW 68 | ] 69 | 70 | MIDDLEWARE = [ 71 | "django.middleware.security.SecurityMiddleware", 72 | "whitenoise.middleware.WhiteNoiseMiddleware", 73 | "django.contrib.sessions.middleware.SessionMiddleware", 74 | "django.middleware.common.CommonMiddleware", 75 | "django.middleware.csrf.CsrfViewMiddleware", 76 | "django.contrib.auth.middleware.AuthenticationMiddleware", 77 | "django.contrib.messages.middleware.MessageMiddleware", 78 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 79 | ] 80 | 81 | ROOT_URLCONF = "config.urls" 82 | 83 | UI_TEMPLATES = os.path.join(BASE_DIR, 'templates') 84 | 85 | TEMPLATES = [ 86 | { 87 | "BACKEND": "django.template.backends.django.DjangoTemplates", 88 | "DIRS": [UI_TEMPLATES], 89 | "APP_DIRS": True, 90 | "OPTIONS": { 91 | "context_processors": [ 92 | "django.template.context_processors.debug", 93 | "django.template.context_processors.request", 94 | "django.contrib.auth.context_processors.auth", 95 | "django.contrib.messages.context_processors.messages", 96 | ], 97 | }, 98 | }, 99 | ] 100 | 101 | WSGI_APPLICATION = "config.wsgi.application" 102 | 103 | 104 | # Database 105 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 106 | 107 | DB_ENGINE = os.getenv('DB_ENGINE' , None) 108 | DB_USERNAME = os.getenv('DB_USERNAME' , None) 109 | DB_PASS = os.getenv('DB_PASS' , None) 110 | DB_HOST = os.getenv('DB_HOST' , None) 111 | DB_PORT = os.getenv('DB_PORT' , None) 112 | DB_NAME = os.getenv('DB_NAME' , None) 113 | 114 | if DB_ENGINE and DB_NAME and DB_USERNAME: 115 | DATABASES = { 116 | 'default': { 117 | 'ENGINE' : 'django.db.backends.' + DB_ENGINE, 118 | 'NAME' : DB_NAME, 119 | 'USER' : DB_USERNAME, 120 | 'PASSWORD': DB_PASS, 121 | 'HOST' : DB_HOST, 122 | 'PORT' : DB_PORT, 123 | }, 124 | } 125 | else: 126 | DATABASES = { 127 | 'default': { 128 | 'ENGINE': 'django.db.backends.sqlite3', 129 | 'NAME': 'db.sqlite3', 130 | } 131 | } 132 | 133 | # Password validation 134 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 135 | 136 | AUTH_PASSWORD_VALIDATORS = [ 137 | { 138 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 139 | }, 140 | { 141 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 142 | }, 143 | { 144 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 145 | }, 146 | { 147 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 148 | }, 149 | ] 150 | 151 | 152 | # Internationalization 153 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 154 | 155 | LANGUAGE_CODE = "en-us" 156 | 157 | TIME_ZONE = "UTC" 158 | 159 | USE_I18N = True 160 | 161 | USE_TZ = True 162 | 163 | 164 | # Static files (CSS, JavaScript, Images) 165 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 166 | 167 | STATIC_URL = '/static/' 168 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 169 | 170 | STATICFILES_DIRS = ( 171 | os.path.join(BASE_DIR, 'static'), 172 | ) 173 | 174 | #if not DEBUG: 175 | # STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 176 | 177 | # Default primary key field type 178 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 179 | 180 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 181 | 182 | LOGIN_REDIRECT_URL = '/' 183 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 184 | 185 | 186 | # ### DYNAMIC_DATATB Settings ### 187 | DYNAMIC_DATATB = { 188 | # SLUG -> Import_PATH 189 | 'product' : "apps.pages.models.Product", 190 | } 191 | ######################################## 192 | 193 | # Syntax: URI -> Import_PATH 194 | DYNAMIC_API = { 195 | # SLUG -> Import_PATH 196 | 'product' : "apps.pages.models.Product", 197 | } 198 | 199 | REST_FRAMEWORK = { 200 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 201 | 'rest_framework.authentication.SessionAuthentication', 202 | 'rest_framework.authentication.TokenAuthentication', 203 | ], 204 | } 205 | ######################################## 206 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | """core URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path('', include('apps.pages.urls')), 21 | path('', include('apps.dyn_dt.urls')), 22 | path('', include('apps.dyn_api.urls')), 23 | path('charts/', include('apps.charts.urls')), 24 | path("admin/", admin.site.urls), 25 | path("", include('admin_argon.urls')) 26 | ] 27 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for core project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/db.sqlite3 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | appseed-app: 4 | container_name: appseed_app 5 | restart: always 6 | build: . 7 | networks: 8 | - db_network 9 | - web_network 10 | nginx: 11 | container_name: nginx 12 | restart: always 13 | image: "nginx:latest" 14 | ports: 15 | - "5085:5085" 16 | volumes: 17 | - ./nginx:/etc/nginx/conf.d 18 | networks: 19 | - web_network 20 | depends_on: 21 | - appseed-app 22 | networks: 23 | db_network: 24 | driver: bridge 25 | web_network: 26 | driver: bridge 27 | -------------------------------------------------------------------------------- /env.sample: -------------------------------------------------------------------------------- 1 | # True for development, False for production 2 | DEBUG=True 3 | 4 | SECRET_KEY= 5 | 6 | # DB_ENGINE=mysql 7 | # DB_HOST=localhost 8 | # DB_NAME=appseed_db 9 | # DB_USERNAME=appseed_db_usr 10 | # DB_PASS=pass 11 | # DB_PORT=3306 -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ========================================================= 4 | * AppSeed - Simple SCSS compiler via Gulp 5 | ========================================================= 6 | 7 | */ 8 | 9 | var autoprefixer = require('gulp-autoprefixer'); 10 | var browserSync = require('browser-sync').create(); 11 | var cleanCss = require('gulp-clean-css'); 12 | var gulp = require('gulp'); 13 | const npmDist = require('gulp-npm-dist'); 14 | var sass = require('gulp-sass')(require('node-sass')); 15 | var wait = require('gulp-wait'); 16 | var sourcemaps = require('gulp-sourcemaps'); 17 | var rename = require("gulp-rename"); 18 | 19 | // Define COMMON paths 20 | 21 | const paths = { 22 | src: { 23 | base: './static', 24 | css: './static/css', 25 | scss: './static/scss', 26 | node_modules: './node_modules/', 27 | vendor: './vendor' 28 | } 29 | }; 30 | 31 | // Compile SCSS 32 | gulp.task('scss', function() { 33 | return gulp.src([paths.src.scss + '/argon-dashboard.scss']) 34 | .pipe(wait(500)) 35 | .pipe(sourcemaps.init()) 36 | .pipe(sass().on('error', sass.logError)) 37 | .pipe(autoprefixer({ 38 | overrideBrowserslist: ['> 1%'] 39 | })) 40 | .pipe(sourcemaps.write('.')) 41 | .pipe(gulp.dest(paths.src.css)) 42 | .pipe(browserSync.stream()); 43 | }); 44 | 45 | // CSS 46 | gulp.task('css', function() { 47 | return gulp.src([ 48 | paths.src.css + '/argon-dashboard.css' 49 | ]) 50 | .pipe(cleanCss()) 51 | .pipe(rename(function(path) { 52 | // Updates the object in-place 53 | path.extname = ".min.css"; 54 | })) 55 | .pipe(gulp.dest(paths.src.css)) 56 | }); 57 | 58 | // Minify CSS 59 | gulp.task('minify:css', function() { 60 | return gulp.src([ 61 | paths.src.css + '/argon-dashboard.css' 62 | ]) 63 | .pipe(cleanCss()) 64 | .pipe(rename(function(path) { 65 | // Updates the object in-place 66 | path.extname = ".min.css"; 67 | })) 68 | .pipe(gulp.dest(paths.src.css)) 69 | }); 70 | 71 | // Default Task: Compile SCSS and minify the result 72 | gulp.task('default', gulp.series('scss', 'minify:css')); 73 | -------------------------------------------------------------------------------- /gunicorn-cfg.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | bind = '0.0.0.0:5005' 7 | workers = 1 8 | accesslog = '-' 9 | loglevel = 'debug' 10 | capture_output = True 11 | enable_stdio_inheritance = True 12 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /nginx/appseed-app.conf: -------------------------------------------------------------------------------- 1 | upstream webapp { 2 | server appseed_app:5005; 3 | } 4 | 5 | server { 6 | listen 5085; 7 | server_name localhost; 8 | 9 | location / { 10 | proxy_pass http://webapp; 11 | proxy_set_header Host $host; 12 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "django-argon-dashboard", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "vite build --watch --mode development", 8 | "build": "vite build --mode production && npm run minify-css", 9 | "minify-css": "postcss static/assets/css/*.css --dir static/assets/css --no-map --ext .min.css" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "autoprefixer": "^10.4.20", 16 | "cssnano": "^7.0.6", 17 | "postcss": "^8.5.3", 18 | "postcss-cli": "^11.0.0", 19 | "sass": "^1.85.1", 20 | "vite": "^6.2.0" 21 | } 22 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('cssnano')({ 4 | preset: 'default', 5 | }), 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /render.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | - type: web 3 | name: django-argon-dash2-latest 4 | plan: free 5 | env: python 6 | region: frankfurt # region should be same as your database region. 7 | buildCommand: "./build.sh" 8 | startCommand: "gunicorn config.wsgi:application" 9 | envVars: 10 | - key: DEBUG 11 | value: False 12 | - key: SECRET_KEY 13 | generateValue: true 14 | - key: WEB_CONCURRENCY 15 | value: 4 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Core 2 | django==4.2.9 3 | python-dotenv==1.0.1 4 | str2bool==1.1 5 | 6 | # UI & Admin UI 7 | django-jazzmin==3.0.1 8 | django-admin-argon-dashboard==1.0.25 9 | 10 | # Tools 11 | django-debug-toolbar==4.4.6 12 | djangorestframework==3.15.2 13 | requests==2.32.3 14 | pandas==2.2.3 15 | graphviz==0.20.3 16 | astor==0.8.1 17 | 18 | # AI 19 | anthropic==0.34.2 20 | 21 | # Deployment 22 | whitenoise==6.7.0 23 | gunicorn==23.0.0 24 | 25 | # DB 26 | #psycopg2-binary==2.9.9 27 | #mysqlclient==2.1.1 28 | django-dbbackup==4.2.1 29 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/static/.gitkeep -------------------------------------------------------------------------------- /static/assets/scss/custom.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | Custom SCSS 4 | 5 | */ -------------------------------------------------------------------------------- /static/img/csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/static/img/csv.png -------------------------------------------------------------------------------- /static/img/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/static/img/export.png -------------------------------------------------------------------------------- /templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/app-generator/django-argon-dashboard/6dc80e35f774fb2ed132f615de14d03f629f9941/templates/.gitkeep -------------------------------------------------------------------------------- /templates/charts/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load static %} 3 | 4 | {% block title %}Charts{% endblock title %} 5 | 6 | {% block content %} 7 | 8 |
9 |
10 |
11 |
12 |
13 |
Bar Chart
14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 |
24 |
Pie Chart
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 | {% endblock content %} 35 | 36 | {% block extra_js %} 37 | 38 | 61 | {% endblock extra_js %} 62 | -------------------------------------------------------------------------------- /templates/dyn_api/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load static %} 3 | 4 | {% block title %} Dynamic APIs {% endblock title %} 5 | 6 | {% block content %} 7 | 8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | Available Routes - defined in settings.DYNAMIC_API - Read Documentation. 16 |
17 |
18 |
19 |
    20 | {% for link in routes %} 21 |
  • 22 | {{ link }} 23 |
  • 24 | {% endfor %} 25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | {% endblock content %} 33 | 34 | 35 | {% block extra_js %} 36 | 37 | 38 | 39 | {% endblock extra_js %} -------------------------------------------------------------------------------- /templates/dyn_dt/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load static %} 3 | 4 | {% block title %} {% if page_title %} page_title {% else %} Dynamic DataTables {% endif %} {% endblock title %} 5 | 6 | {% block content %} 7 | 8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | Available Routes - defined in settings.DYNAMIC_DATATB - Read Documentation. 16 |
17 |
18 |
19 |
    20 | {% for link in routes %} 21 |
  • 22 | {{ link }} 23 |
  • 24 | {% endfor %} 25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | {% endblock content %} 33 | 34 | 35 | {% block extra_js %} 36 | 37 | 38 | 39 | {% endblock extra_js %} -------------------------------------------------------------------------------- /templates/dyn_dt/items-table.html: -------------------------------------------------------------------------------- 1 | {% load get_attribute %} 2 | 3 |
4 | 5 | 6 | 7 | {% for field in db_field_names %} 8 | 9 | {% endfor %} 10 | 11 | 12 | 13 | {% for item in items %} 14 | 15 | {% for field_name in db_field_names %} 16 | 17 | {% endfor %} 18 | 19 | {% endfor %} 20 | 21 |
{{ field }}
{{ item|getattribute:field_name }}
22 |
-------------------------------------------------------------------------------- /templates/dyn_dt/model.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load static get_attribute %} 3 | 4 | {% block title %} {% if page_title %} {{page_title}} {% else %} Dynamic DataTables {% endif %} {% endblock title %} 5 | 6 | {% block extrastyle %} 7 | 8 | 47 | 48 | {% endblock extrastyle %} 49 | 50 | {% block content %} 51 | 52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 | 69 |
70 |
71 | 88 |
89 |
90 |
91 |
92 |
93 | {% csrf_token %} 94 | 102 |
103 |
104 | 105 | img 106 | 107 |
108 | {% if request.user.is_authenticated %} 109 |
110 | 113 |
114 | {% endif %} 115 |
116 |
117 |
118 | 119 |
120 |
121 | {% csrf_token %} 122 | 123 |
124 |

Filters

125 | 126 |
127 | 128 |
129 | {% if filter_instance %} 130 | {% for filter_data in filter_instance %} 131 |
132 |
133 | 139 | 140 |
141 | X 142 |
143 | {% endfor %} 144 | {% endif %} 145 |
146 | 147 |
148 | 149 |
150 |
151 | 152 | 153 | 154 | {% for field in db_field_names %} 155 | 156 | {% endfor %} 157 | 158 | 159 | 160 | {% for item in items %} 161 | 162 | {% for field_name in db_field_names %} 163 | 164 | {% endfor %} 165 | 166 | {% if request.user.is_authenticated %} 167 | 171 | {% else %} 172 | 175 | {% endif %} 176 | 177 | 178 | 179 | 252 | 253 | 254 | 279 | 280 | 281 | 315 | 316 | {% endfor %} 317 | 318 |
{{ field }}
{{ item|getattribute:field_name }} 168 | 169 | 170 | 173 | 174 |
319 |
320 |
321 | {% if items.has_other_pages %} 322 | 349 | {% endif %} 350 |
351 |
352 | 353 | 354 | 388 | 389 | 390 | 459 | 460 |
461 |
462 |
463 |
464 | 465 | {% endblock content %} 466 | 467 | 468 | {% block extra_js %} 469 | 470 | 526 | 527 | 545 | 546 | 582 | 583 | {% endblock extra_js %} -------------------------------------------------------------------------------- /templates/includes/sidebar.html: -------------------------------------------------------------------------------- 1 | {% load i18n static admin_argon %} 2 | 3 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import autoprefixer from "autoprefixer"; 3 | import cssnano from "cssnano"; 4 | import path from "path"; 5 | 6 | export default defineConfig(({ mode }) => { 7 | const isProduction = mode === "production"; 8 | 9 | return { 10 | css: { 11 | postcss: { 12 | plugins: [ 13 | autoprefixer(), 14 | isProduction && cssnano(), 15 | ].filter(Boolean), 16 | }, 17 | }, 18 | build: { 19 | outDir: "static", 20 | emptyOutDir: false, 21 | rollupOptions: { 22 | input: path.resolve(__dirname, "static/assets/scss/custom.scss"), 23 | output: { 24 | assetFileNames: (assetInfo) => { 25 | if (assetInfo.name === "custom.css") { 26 | return "assets/css/custom.css"; 27 | } 28 | return "assets/css/[name].[ext]"; 29 | }, 30 | }, 31 | }, 32 | }, 33 | }; 34 | }); --------------------------------------------------------------------------------