diff --git a/.djinit b/.djinit new file mode 100644 index 0000000..6af21df --- /dev/null +++ b/.djinit @@ -0,0 +1,21 @@ +{ + "project_name": ".", + "structure": { + "standard": false, + "predefined": false, + "unified": false, + "single": true + }, + "apps": { + "nested": false, + "nested_dir": null + }, + "settings": { + "use_database_url": true, + "database_type": "mysql" + }, + "cicd": { + "github": true, + "gitlab": false + } +} \ No newline at end of file diff --git a/.env.sample b/.env.sample index 86ef8ca..0fc7602 100644 --- a/.env.sample +++ b/.env.sample @@ -1,13 +1,11 @@ # Django settings -DJANGO_SETTINGS_MODULE=config.settings.development +DJANGO_SETTINGS_MODULE=src.settings.development SECRET_KEY= your-secret-key ALLOWED_HOSTS=api.your-domain.com,www.your-domain.com # Database URL (recommended for production) -DATABASE_URL=postgres://user:password@host:port/database_name +DATABASE_URL=mysql://user:password@localhost:3306/src -# Frontend URL -FRONTEND_URL=http://frontend.your-domain.com # Email settings EMAIL_HOST= diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Procfile b/Procfile index 7ebdf0b..a6e4121 100644 --- a/Procfile +++ b/Procfile @@ -1,20 +1,20 @@ -# Procfile for config +# Procfile for . # Used for deploying to PaaS platforms like Heroku, Railway, Render, etc. # Web server process (using Gunicorn) # Adjust workers based on your needs: (2 x CPU cores) + 1 -web: gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 2 --timeout 120 --access-logfile - --error-logfile - --log-level info +web: gunicorn ..wsgi:application --bind 0.0.0.0:$PORT --workers 2 --timeout 120 --access-logfile - --error-logfile - --log-level info # Alternative web server configurations (uncomment to use): -# web: gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 4 --threads 2 --timeout 120 -# web: gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 2 --worker-class gevent --worker-connections 1000 --timeout 120 +# web: gunicorn ..wsgi:application --bind 0.0.0.0:$PORT --workers 4 --threads 2 --timeout 120 +# web: gunicorn ..wsgi:application --bind 0.0.0.0:$PORT --workers 2 --worker-class gevent --worker-connections 1000 --timeout 120 # Release task (runs migrations before deployment) release: python manage.py migrate --noinput && python manage.py collectstatic --noinput # Uncomment below to run scheduled tasks (if using Celery) -# worker: celery -A config worker --loglevel=info -# beat: celery -A config beat --loglevel=info +# worker: celery -A . worker --loglevel=info +# beat: celery -A . beat --loglevel=info # Development server (for local testing with Foreman/Overmind) # web: python manage.py runserver 0.0.0.0:$PORT diff --git a/README.md b/README.md index 1105a99..2752c7a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# config +# . -A Django project created with django-setup. +A Django project created with [djinitx/djinit/dj](https://github.com/S4NKALP/djinit). ## Features @@ -11,6 +11,22 @@ A Django project created with django-setup. ## Setup +### Using Just (Recommended) + +1. Set environment variables in `.env` file + +2. Run setup (installs dependencies, runs migrations, creates superuser): + ```bash + just setup + ``` + +3. Start development server: + ```bash + just dev + ``` + +### Traditional Method + 1. Install dependencies: ```bash pip install -r requirements.txt @@ -33,31 +49,54 @@ A Django project created with django-setup. python manage.py runserver ``` +## Available Commands + +Run `just` to see all available commands, including: + +- `just dev` - Run development server +- `just migrate` - Run migrations +- `just makemigrations` - Create migrations +- `just createsuperuser` - Create superuser +- `just test` - Run tests +- `just lint` - Lint code +- `just format` - Format code +- `just shell` - Django shell +- `just clean` - Clean cache files + ## Project Structure ``` -config/ -├── config/ +./ +├── src/ │ ├── settings/ │ │ ├── base.py │ │ ├── development.py │ │ └── production.py +│ ├── admin/ +│ ├── api/ # API endpoints organized by model +│ │ ├── user/ # Example: if you have a User model +│ │ │ ├── views.py +│ │ │ ├── serializers.py +│ │ │ └── urls.py +│ │ └── urls.py +│ ├── models/ # Django models +│ ├── tests/ │ ├── urls.py │ ├── wsgi.py │ └── asgi.py -├── clients/ -├── core/ -├── finance/ -├── productivity/ -├── services/ -├── core/ -├── notifications/ -├── projects/ ├── manage.py ├── requirements.txt ├── pyproject.toml ├── .env.sample └── README.md + +**Note:** For the single folder layout, organize your API endpoints by model name under the `api/` directory. +For example, if you have a `User` model in `models/user.py`, create: +- `api/user/views.py` - API views for User +- `api/user/serializers.py` - Serializers for User +- `api/user/urls.py` - URL patterns for User endpoints + +See the README files in `api/` and `models/` directories for more details. ``` ## API Documentation diff --git a/apps/clients/apps.py b/apps/clients/apps.py deleted file mode 100644 index 8f1a25f..0000000 --- a/apps/clients/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""generated with djinit""" - -from django.apps import AppConfig - - -class ClientsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'clients' diff --git a/apps/clients/routes.py b/apps/clients/routes.py deleted file mode 100644 index 01f74cc..0000000 --- a/apps/clients/routes.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -generated with djinit - -API routes for clients app. -""" - -from rest_framework.routers import DefaultRouter -from . import views - -# Create router instance -router = DefaultRouter() - -# Register viewsets with router -# Example: -# router.register(r'clients', views.ClientsViewSet) - -# Get URL patterns -urlpatterns = router.urls diff --git a/apps/clients/serializers.py b/apps/clients/serializers.py deleted file mode 100644 index 1dde732..0000000 --- a/apps/clients/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -generated with djinit -serializers for clients app. -""" - -from rest_framework import serializers - - -# Add your serializers here -# Example: -# class ClientsSerializer(serializers.ModelSerializer): -# class Meta: -# model = YourModel -# fields = '__all__' diff --git a/apps/clients/tests.py b/apps/clients/tests.py deleted file mode 100644 index 7fdd344..0000000 --- a/apps/clients/tests.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.test import TestCase - -# Create your tests here. diff --git a/apps/clients/urls.py b/apps/clients/urls.py deleted file mode 100644 index d783bdc..0000000 --- a/apps/clients/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -"""generated with djinit""" - -from django.urls import path -from . import views - -app_name = 'clients' - -urlpatterns = [ - # Add your URL patterns here - # path('', views.index, name='index'), -] diff --git a/apps/clients/views.py b/apps/clients/views.py deleted file mode 100644 index 7df6a0d..0000000 --- a/apps/clients/views.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.shortcuts import render - -# Create your views here. diff --git a/apps/core/admin.py b/apps/core/admin.py deleted file mode 100644 index dc9bfc6..0000000 --- a/apps/core/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.contrib import admin - -# Register your models here. diff --git a/apps/core/apps.py b/apps/core/apps.py deleted file mode 100644 index 90b25b0..0000000 --- a/apps/core/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""generated with djinit""" - -from django.apps import AppConfig - - -class CoreConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'core' diff --git a/apps/core/models.py b/apps/core/models.py deleted file mode 100644 index 980c8e5..0000000 --- a/apps/core/models.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.db import models - -# Create your models here. diff --git a/apps/core/routes.py b/apps/core/routes.py deleted file mode 100644 index b300b23..0000000 --- a/apps/core/routes.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -generated with djinit - -API routes for core app. -""" - -from rest_framework.routers import DefaultRouter -from . import views - -# Create router instance -router = DefaultRouter() - -# Register viewsets with router -# Example: -# router.register(r'core', views.CoreViewSet) - -# Get URL patterns -urlpatterns = router.urls diff --git a/apps/core/serializers.py b/apps/core/serializers.py deleted file mode 100644 index 90821de..0000000 --- a/apps/core/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -generated with djinit -serializers for core app. -""" - -from rest_framework import serializers - - -# Add your serializers here -# Example: -# class CoreSerializer(serializers.ModelSerializer): -# class Meta: -# model = YourModel -# fields = '__all__' diff --git a/apps/core/tests.py b/apps/core/tests.py deleted file mode 100644 index 7fdd344..0000000 --- a/apps/core/tests.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.test import TestCase - -# Create your tests here. diff --git a/apps/core/urls.py b/apps/core/urls.py deleted file mode 100644 index bd6e2f2..0000000 --- a/apps/core/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -"""generated with djinit""" - -from django.urls import path -from . import views - -app_name = 'core' - -urlpatterns = [ - # Add your URL patterns here - # path('', views.index, name='index'), -] diff --git a/apps/core/views.py b/apps/core/views.py deleted file mode 100644 index 7df6a0d..0000000 --- a/apps/core/views.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.shortcuts import render - -# Create your views here. diff --git a/apps/finance/admin.py b/apps/finance/admin.py deleted file mode 100644 index dc9bfc6..0000000 --- a/apps/finance/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.contrib import admin - -# Register your models here. diff --git a/apps/finance/apps.py b/apps/finance/apps.py deleted file mode 100644 index 9c73f0a..0000000 --- a/apps/finance/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""generated with djinit""" - -from django.apps import AppConfig - - -class FinanceConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'finance' diff --git a/apps/finance/models.py b/apps/finance/models.py deleted file mode 100644 index 980c8e5..0000000 --- a/apps/finance/models.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.db import models - -# Create your models here. diff --git a/apps/finance/routes.py b/apps/finance/routes.py deleted file mode 100644 index 456116f..0000000 --- a/apps/finance/routes.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -generated with djinit - -API routes for finance app. -""" - -from rest_framework.routers import DefaultRouter -from . import views - -# Create router instance -router = DefaultRouter() - -# Register viewsets with router -# Example: -# router.register(r'finance', views.FinanceViewSet) - -# Get URL patterns -urlpatterns = router.urls diff --git a/apps/finance/serializers.py b/apps/finance/serializers.py deleted file mode 100644 index 3c65e56..0000000 --- a/apps/finance/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -generated with djinit -serializers for finance app. -""" - -from rest_framework import serializers - - -# Add your serializers here -# Example: -# class FinanceSerializer(serializers.ModelSerializer): -# class Meta: -# model = YourModel -# fields = '__all__' diff --git a/apps/finance/tests.py b/apps/finance/tests.py deleted file mode 100644 index 7fdd344..0000000 --- a/apps/finance/tests.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.test import TestCase - -# Create your tests here. diff --git a/apps/finance/urls.py b/apps/finance/urls.py deleted file mode 100644 index c510689..0000000 --- a/apps/finance/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -"""generated with djinit""" - -from django.urls import path -from . import views - -app_name = 'finance' - -urlpatterns = [ - # Add your URL patterns here - # path('', views.index, name='index'), -] diff --git a/apps/finance/views.py b/apps/finance/views.py deleted file mode 100644 index 7df6a0d..0000000 --- a/apps/finance/views.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.shortcuts import render - -# Create your views here. diff --git a/apps/notifications/admin.py b/apps/notifications/admin.py deleted file mode 100644 index dc9bfc6..0000000 --- a/apps/notifications/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.contrib import admin - -# Register your models here. diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py deleted file mode 100644 index 7535af1..0000000 --- a/apps/notifications/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""generated with djinit""" - -from django.apps import AppConfig - - -class NotificationsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'notifications' diff --git a/apps/notifications/models.py b/apps/notifications/models.py deleted file mode 100644 index 980c8e5..0000000 --- a/apps/notifications/models.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.db import models - -# Create your models here. diff --git a/apps/notifications/routes.py b/apps/notifications/routes.py deleted file mode 100644 index 1325f95..0000000 --- a/apps/notifications/routes.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -generated with djinit - -API routes for notifications app. -""" - -from rest_framework.routers import DefaultRouter -from . import views - -# Create router instance -router = DefaultRouter() - -# Register viewsets with router -# Example: -# router.register(r'notifications', views.NotificationsViewSet) - -# Get URL patterns -urlpatterns = router.urls diff --git a/apps/notifications/serializers.py b/apps/notifications/serializers.py deleted file mode 100644 index 076b944..0000000 --- a/apps/notifications/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -generated with djinit -serializers for notifications app. -""" - -from rest_framework import serializers - - -# Add your serializers here -# Example: -# class NotificationsSerializer(serializers.ModelSerializer): -# class Meta: -# model = YourModel -# fields = '__all__' diff --git a/apps/notifications/tests.py b/apps/notifications/tests.py deleted file mode 100644 index 7fdd344..0000000 --- a/apps/notifications/tests.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.test import TestCase - -# Create your tests here. diff --git a/apps/notifications/urls.py b/apps/notifications/urls.py deleted file mode 100644 index f3195c5..0000000 --- a/apps/notifications/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -"""generated with djinit""" - -from django.urls import path -from . import views - -app_name = 'notifications' - -urlpatterns = [ - # Add your URL patterns here - # path('', views.index, name='index'), -] diff --git a/apps/notifications/views.py b/apps/notifications/views.py deleted file mode 100644 index 7df6a0d..0000000 --- a/apps/notifications/views.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.shortcuts import render - -# Create your views here. diff --git a/apps/productivity/admin.py b/apps/productivity/admin.py deleted file mode 100644 index dc9bfc6..0000000 --- a/apps/productivity/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.contrib import admin - -# Register your models here. diff --git a/apps/productivity/apps.py b/apps/productivity/apps.py deleted file mode 100644 index 029ed10..0000000 --- a/apps/productivity/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""generated with djinit""" - -from django.apps import AppConfig - - -class ProductivityConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'productivity' diff --git a/apps/productivity/models.py b/apps/productivity/models.py deleted file mode 100644 index 980c8e5..0000000 --- a/apps/productivity/models.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.db import models - -# Create your models here. diff --git a/apps/productivity/routes.py b/apps/productivity/routes.py deleted file mode 100644 index 92042f7..0000000 --- a/apps/productivity/routes.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -generated with djinit - -API routes for productivity app. -""" - -from rest_framework.routers import DefaultRouter -from . import views - -# Create router instance -router = DefaultRouter() - -# Register viewsets with router -# Example: -# router.register(r'productivity', views.ProductivityViewSet) - -# Get URL patterns -urlpatterns = router.urls diff --git a/apps/productivity/serializers.py b/apps/productivity/serializers.py deleted file mode 100644 index 76c4ccf..0000000 --- a/apps/productivity/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -generated with djinit -serializers for productivity app. -""" - -from rest_framework import serializers - - -# Add your serializers here -# Example: -# class ProductivitySerializer(serializers.ModelSerializer): -# class Meta: -# model = YourModel -# fields = '__all__' diff --git a/apps/productivity/tests.py b/apps/productivity/tests.py deleted file mode 100644 index 7fdd344..0000000 --- a/apps/productivity/tests.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.test import TestCase - -# Create your tests here. diff --git a/apps/productivity/urls.py b/apps/productivity/urls.py deleted file mode 100644 index 52942c4..0000000 --- a/apps/productivity/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -"""generated with djinit""" - -from django.urls import path -from . import views - -app_name = 'productivity' - -urlpatterns = [ - # Add your URL patterns here - # path('', views.index, name='index'), -] diff --git a/apps/productivity/views.py b/apps/productivity/views.py deleted file mode 100644 index 7df6a0d..0000000 --- a/apps/productivity/views.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.shortcuts import render - -# Create your views here. diff --git a/apps/projects/admin.py b/apps/projects/admin.py deleted file mode 100644 index dc9bfc6..0000000 --- a/apps/projects/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.contrib import admin - -# Register your models here. diff --git a/apps/projects/apps.py b/apps/projects/apps.py deleted file mode 100644 index 829afd2..0000000 --- a/apps/projects/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""generated with djinit""" - -from django.apps import AppConfig - - -class ProjectsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'projects' diff --git a/apps/projects/models.py b/apps/projects/models.py deleted file mode 100644 index 980c8e5..0000000 --- a/apps/projects/models.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.db import models - -# Create your models here. diff --git a/apps/projects/routes.py b/apps/projects/routes.py deleted file mode 100644 index c7f59d1..0000000 --- a/apps/projects/routes.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -generated with djinit - -API routes for projects app. -""" - -from rest_framework.routers import DefaultRouter -from . import views - -# Create router instance -router = DefaultRouter() - -# Register viewsets with router -# Example: -# router.register(r'projects', views.ProjectsViewSet) - -# Get URL patterns -urlpatterns = router.urls diff --git a/apps/projects/serializers.py b/apps/projects/serializers.py deleted file mode 100644 index e71aa58..0000000 --- a/apps/projects/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -generated with djinit -serializers for projects app. -""" - -from rest_framework import serializers - - -# Add your serializers here -# Example: -# class ProjectsSerializer(serializers.ModelSerializer): -# class Meta: -# model = YourModel -# fields = '__all__' diff --git a/apps/projects/tests.py b/apps/projects/tests.py deleted file mode 100644 index 7fdd344..0000000 --- a/apps/projects/tests.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.test import TestCase - -# Create your tests here. diff --git a/apps/projects/urls.py b/apps/projects/urls.py deleted file mode 100644 index 6493af1..0000000 --- a/apps/projects/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -"""generated with djinit""" - -from django.urls import path -from . import views - -app_name = 'projects' - -urlpatterns = [ - # Add your URL patterns here - # path('', views.index, name='index'), -] diff --git a/apps/projects/views.py b/apps/projects/views.py deleted file mode 100644 index 7df6a0d..0000000 --- a/apps/projects/views.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.shortcuts import render - -# Create your views here. diff --git a/apps/services/admin.py b/apps/services/admin.py deleted file mode 100644 index dc9bfc6..0000000 --- a/apps/services/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.contrib import admin - -# Register your models here. diff --git a/apps/services/apps.py b/apps/services/apps.py deleted file mode 100644 index 0a13a3b..0000000 --- a/apps/services/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""generated with djinit""" - -from django.apps import AppConfig - - -class ServicesConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'services' diff --git a/apps/services/models.py b/apps/services/models.py deleted file mode 100644 index 980c8e5..0000000 --- a/apps/services/models.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.db import models - -# Create your models here. diff --git a/apps/services/routes.py b/apps/services/routes.py deleted file mode 100644 index 2cf76c3..0000000 --- a/apps/services/routes.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -generated with djinit - -API routes for services app. -""" - -from rest_framework.routers import DefaultRouter -from . import views - -# Create router instance -router = DefaultRouter() - -# Register viewsets with router -# Example: -# router.register(r'services', views.ServicesViewSet) - -# Get URL patterns -urlpatterns = router.urls diff --git a/apps/services/serializers.py b/apps/services/serializers.py deleted file mode 100644 index 1444a26..0000000 --- a/apps/services/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -generated with djinit -serializers for services app. -""" - -from rest_framework import serializers - - -# Add your serializers here -# Example: -# class ServicesSerializer(serializers.ModelSerializer): -# class Meta: -# model = YourModel -# fields = '__all__' diff --git a/apps/services/tests.py b/apps/services/tests.py deleted file mode 100644 index 7fdd344..0000000 --- a/apps/services/tests.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.test import TestCase - -# Create your tests here. diff --git a/apps/services/urls.py b/apps/services/urls.py deleted file mode 100644 index 15b21ad..0000000 --- a/apps/services/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -"""generated with djinit""" - -from django.urls import path -from . import views - -app_name = 'services' - -urlpatterns = [ - # Add your URL patterns here - # path('', views.index, name='index'), -] diff --git a/apps/services/views.py b/apps/services/views.py deleted file mode 100644 index 7df6a0d..0000000 --- a/apps/services/views.py +++ /dev/null @@ -1,5 +0,0 @@ -"""generated with djinit""" - -from django.shortcuts import render - -# Create your views here. diff --git a/config/asgi.py b/config/asgi.py deleted file mode 100644 index 80dff82..0000000 --- a/config/asgi.py +++ /dev/null @@ -1,10 +0,0 @@ -"""generated with djinit""" - -import os -from dotenv import load_dotenv -from django.core.asgi import get_asgi_application - -load_dotenv() - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE", "core.settings.production")) -application = get_asgi_application() diff --git a/config/settings/production.py b/config/settings/production.py deleted file mode 100644 index 0401b78..0000000 --- a/config/settings/production.py +++ /dev/null @@ -1,57 +0,0 @@ -"""generated with djinit""" - -from .base import * -import os -import dj_database_url - -DEBUG = False -SECRET_KEY = os.getenv('SECRET_KEY') -ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',') - -# Database -# Using individual database parameters -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.getenv('DB_NAME'), - 'USER': os.getenv('DB_USER'), - 'PASSWORD': os.getenv('DB_PASSWORD'), - 'HOST': os.getenv('DB_HOST'), - 'PORT': os.getenv('DB_PORT'), - } -} - -# CORS settings for production -# Filter out empty strings from FRONTEND_URL -frontend_urls = os.getenv("FRONTEND_URL", "") -CORS_ALLOWED_ORIGINS = [ - origin.strip() for origin in frontend_urls.split(",") if origin.strip() -] -CORS_ALLOW_CREDENTIALS = True - -# Additional CORS security for production -CORS_ALLOW_PRIVATE_NETWORK = False - -# Email settings -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_HOST = os.getenv('EMAIL_HOST') -EMAIL_PORT = int(os.getenv('EMAIL_PORT', 587)) -EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', 'True').lower() == 'true' -EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') -EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') -DEFAULT_FROM_EMAIL = os.environ.get("EMAIL_HOST_USER") -SERVER_EMAIL = os.environ.get("EMAIL_HOST_USER") - -# Security settings for production -SECURE_SSL_REDIRECT = True -SECURE_HSTS_SECONDS = 31536000 -SECURE_HSTS_INCLUDE_SUBDOMAINS = True -SECURE_HSTS_PRELOAD = True -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') - -# Session settings -SESSION_COOKIE_SECURE = True -CSRF_COOKIE_SECURE = True - -# Static files -STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' diff --git a/config/wsgi.py b/config/wsgi.py deleted file mode 100644 index 8f48013..0000000 --- a/config/wsgi.py +++ /dev/null @@ -1,10 +0,0 @@ -"""generated with djinit""" - -import os -from dotenv import load_dotenv -from django.core.wsgi import get_wsgi_application - -load_dotenv() -os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE", "core.settings.production")) - -application = get_wsgi_application() diff --git a/justfile b/justfile index da6a127..64db808 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -# Justfile for config +# Justfile for . # Task runner for Django development # Install just: https://github.com/casey/just @@ -108,15 +108,15 @@ dbshell: # Start production server with gunicorn server: - uv run gunicorn config.wsgi:application --bind 0.0.0.0:8000 + uv run gunicorn ..wsgi:application --bind 0.0.0.0:8000 # Start production server with gunicorn workers server-prod WORKERS="4": - uv run gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers {{WORKERS}} + uv run gunicorn ..wsgi:application --bind 0.0.0.0:8000 --workers {{WORKERS}} # Start production server with gunicorn and reload on file changes server-watch: - uv run gunicorn config.wsgi:application --bind 0.0.0.0:8000 --reload + uv run gunicorn ..wsgi:application --bind 0.0.0.0:8000 --reload # Clean Python cache files clean: diff --git a/manage.py b/manage.py index 422a555..079cfdc 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import os import sys -from dotenv import load_dotenv +import environ def main(): @@ -25,5 +25,8 @@ def main(): if __name__ == "__main__": - load_dotenv() + env = environ.Env() + # Read .env file, if it exists + environ.Env.read_env() + main() diff --git a/pyproject.toml b/pyproject.toml index 0b874f0..861f281 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,15 +7,15 @@ requires-python = ">=3.13" license = { text = "MIT" } dependencies = [ "django", - "python-dotenv", + "django-environ", "django-jazzmin", - "djangorestframework", - "djangorestframework-simplejwt", - "drf-spectacular", - "django-cors-headers", "whitenoise", + "mysqlclient", "psycopg2-binary", "gunicorn", + "django-components>=0.143.2", + "django-htmx>=1.27.0", + "django-nepali-datetime-field>=0.8.0", ] [tool.ruff] @@ -38,4 +38,4 @@ ignore = [ ] [tool.ruff.lint.isort] -known-first-party = ["config"] +known-first-party = ["."] diff --git a/requirements.txt b/requirements.txt index 5c099f4..66d10a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,13 +3,13 @@ #--------------------------------------------------# Django -python-dotenv +django-environ django-jazzmin djangorestframework djangorestframework_simplejwt drf-spectacular django-cors-headers whitenoise -psycopg2-binary +mysqlclient gunicorn dj-database-url \ No newline at end of file diff --git a/apps/__init__.py b/src/__init__.py similarity index 100% rename from apps/__init__.py rename to src/__init__.py diff --git a/apps/clients/admin.py b/src/admin/__init__.py similarity index 100% rename from apps/clients/admin.py rename to src/admin/__init__.py diff --git a/apps/clients/__init__.py b/src/admin/base.py similarity index 100% rename from apps/clients/__init__.py rename to src/admin/base.py diff --git a/apps/clients/migrations/__init__.py b/src/admin/clients.py similarity index 100% rename from apps/clients/migrations/__init__.py rename to src/admin/clients.py diff --git a/apps/core/__init__.py b/src/admin/finance.py similarity index 100% rename from apps/core/__init__.py rename to src/admin/finance.py diff --git a/apps/core/migrations/__init__.py b/src/admin/notifications.py similarity index 100% rename from apps/core/migrations/__init__.py rename to src/admin/notifications.py diff --git a/apps/finance/__init__.py b/src/admin/productivity.py similarity index 100% rename from apps/finance/__init__.py rename to src/admin/productivity.py diff --git a/apps/finance/migrations/__init__.py b/src/admin/projects.py similarity index 100% rename from apps/finance/migrations/__init__.py rename to src/admin/projects.py diff --git a/apps/notifications/__init__.py b/src/admin/services.py similarity index 100% rename from apps/notifications/__init__.py rename to src/admin/services.py diff --git a/src/api/README.md b/src/api/README.md new file mode 100644 index 0000000..c13d73a --- /dev/null +++ b/src/api/README.md @@ -0,0 +1,76 @@ +# API Directory + +This directory contains your API endpoints organized by model/resource. + +## Structure + +For each model in your `models/` directory, create a corresponding subdirectory here with the following files: + +``` +api/ +├── __init__.py +├── urls.py # Main API URL router +└── / # e.g., user, product, order + ├── __init__.py + ├── views.py + ├── serializers.py + └── urls.py +``` + +## Example + +If you have a `User` model in `models/user.py`, create: + +### 1. `api/user/serializers.py` +```python +from rest_framework import serializers +from src.models.user import User + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = '__all__' +``` + +### 2. `api/user/views.py` +```python +from rest_framework import viewsets +from src.models.user import User +from .serializers import UserSerializer + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer +``` + +### 3. `api/user/urls.py` +```python +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import UserViewSet + +router = DefaultRouter() +router.register(r'', UserViewSet, basename='user') + +urlpatterns = [ + path('', include(router.urls)), +] +``` + +### 4. Update `api/urls.py` +```python +from django.urls import path, include + +urlpatterns = [ + path('user/', include('src.api.user.urls')), + # Add more API endpoints here +] +``` + +## Best Practices + +1. **One model per subdirectory**: Keep API endpoints for each model in its own subdirectory +2. **Consistent naming**: Use lowercase, plural names for API endpoints (e.g., `users/`, `products/`) +3. **Version your API**: Consider adding versioning (e.g., `api/v1/user/`) +4. **Use ViewSets**: Leverage DRF's ViewSets for standard CRUD operations +5. **Custom actions**: Add custom actions using `@action` decorator for non-CRUD operations diff --git a/apps/notifications/migrations/__init__.py b/src/api/__init__.py similarity index 100% rename from apps/notifications/migrations/__init__.py rename to src/api/__init__.py diff --git a/apps/productivity/__init__.py b/src/api/base/__init__.py similarity index 100% rename from apps/productivity/__init__.py rename to src/api/base/__init__.py diff --git a/apps/productivity/migrations/__init__.py b/src/api/base/urls.py similarity index 100% rename from apps/productivity/migrations/__init__.py rename to src/api/base/urls.py diff --git a/apps/projects/__init__.py b/src/api/base/views.py similarity index 100% rename from apps/projects/__init__.py rename to src/api/base/views.py diff --git a/apps/projects/migrations/__init__.py b/src/api/clients/__init__.py similarity index 100% rename from apps/projects/migrations/__init__.py rename to src/api/clients/__init__.py diff --git a/apps/services/__init__.py b/src/api/clients/urls.py similarity index 100% rename from apps/services/__init__.py rename to src/api/clients/urls.py diff --git a/apps/services/migrations/__init__.py b/src/api/clients/views.py similarity index 100% rename from apps/services/migrations/__init__.py rename to src/api/clients/views.py diff --git a/config/__init__.py b/src/api/finance/__init__.py similarity index 100% rename from config/__init__.py rename to src/api/finance/__init__.py diff --git a/config/settings/__init__.py b/src/api/finance/urls.py similarity index 100% rename from config/settings/__init__.py rename to src/api/finance/urls.py diff --git a/src/api/finance/views.py b/src/api/finance/views.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/notifications/__init__.py b/src/api/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/notifications/urls.py b/src/api/notifications/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/notifications/views.py b/src/api/notifications/views.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/productivity/__init__.py b/src/api/productivity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/productivity/urls.py b/src/api/productivity/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/productivity/views.py b/src/api/productivity/views.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/projects/__init__.py b/src/api/projects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/projects/urls.py b/src/api/projects/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/projects/views.py b/src/api/projects/views.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/services/__init__.py b/src/api/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/services/urls.py b/src/api/services/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/services/views.py b/src/api/services/views.py new file mode 100644 index 0000000..e69de29 diff --git a/src/asgi.py b/src/asgi.py new file mode 100644 index 0000000..aa7df17 --- /dev/null +++ b/src/asgi.py @@ -0,0 +1,11 @@ +"""generated with djinit""" + +import os +import environ +from django.core.asgi import get_asgi_application + +env = environ.Env() +environ.Env.read_env() + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE", default="core.settings.production")) +application = get_asgi_application() diff --git a/src/models/README.md b/src/models/README.md new file mode 100644 index 0000000..a7a241c --- /dev/null +++ b/src/models/README.md @@ -0,0 +1,119 @@ +# Models Directory + +This directory contains your Django models. + +## Structure + +Organize your models as individual Python files: + +``` +models/ +├── __init__.py +├── user.py +├── product.py +└── order.py +``` + +## Creating a Model + +### 1. Create the model file (e.g., `models/user.py`) + +```python +from django.db import models +from django.contrib.auth.models import AbstractUser + +class User(AbstractUser): + """Custom user model.""" + bio = models.TextField(blank=True) + avatar = models.ImageField(upload_to='avatars/', blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + db_table = 'users' + verbose_name = 'User' + verbose_name_plural = 'Users' + + def __str__(self): + return self.username +``` + +### 2. Import in `models/__init__.py` + +```python +from .user import User +from .product import Product +from .order import Order + +__all__ = ['User', 'Product', 'Order'] +``` + +### 3. Create corresponding API endpoints + +After creating a model, create the corresponding API structure in the `api/` directory: + +``` +api/ +└── user/ + ├── __init__.py + ├── views.py + ├── serializers.py + └── urls.py +``` + +See the `api/README.md` for detailed instructions on creating API endpoints. + +### 4. Register in admin (optional) + +Create or update `admin/__init__.py`: + +```python +from django.contrib import admin +from src.models import User + +@admin.register(User) +class UserAdmin(admin.ModelAdmin): + list_display = ['username', 'email', 'created_at'] + search_fields = ['username', 'email'] + list_filter = ['is_active', 'created_at'] +``` + +## Best Practices + +1. **One model per file**: Keep each model in its own file for better organization +2. **Use meaningful names**: Choose clear, descriptive names for models and fields +3. **Add Meta class**: Define `db_table`, `verbose_name`, and other metadata +4. **Implement __str__**: Always define a meaningful `__str__` method +5. **Use mixins**: Create reusable mixins for common fields (e.g., timestamps) +6. **Add docstrings**: Document your models with clear docstrings + +## Common Model Patterns + +### Timestamped Model Mixin + +```python +class TimestampedModel(models.Model): + """Abstract base class with timestamp fields.""" + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True +``` + +### Soft Delete Mixin + +```python +class SoftDeleteModel(models.Model): + """Abstract base class with soft delete functionality.""" + is_deleted = models.BooleanField(default=False) + deleted_at = models.DateTimeField(null=True, blank=True) + + class Meta: + abstract = True + + def delete(self, *args, **kwargs): + self.is_deleted = True + self.deleted_at = timezone.now() + self.save() +``` diff --git a/apps/clients/models.py b/src/models/__init__.py similarity index 100% rename from apps/clients/models.py rename to src/models/__init__.py diff --git a/src/models/base.py b/src/models/base.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models/clients.py b/src/models/clients.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models/finance.py b/src/models/finance.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models/notifications.py b/src/models/notifications.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models/productivity.py b/src/models/productivity.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models/projects.py b/src/models/projects.py new file mode 100644 index 0000000..e69de29 diff --git a/src/models/services.py b/src/models/services.py new file mode 100644 index 0000000..e69de29 diff --git a/src/settings/__init__.py b/src/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/settings/base.py b/src/settings/base.py similarity index 56% rename from config/settings/base.py rename to src/settings/base.py index 5af88b0..d94f298 100644 --- a/config/settings/base.py +++ b/src/settings/base.py @@ -3,13 +3,12 @@ generated with djinit Common settings shared between development and production environment """ -import os -from datetime import timedelta from pathlib import Path -from dotenv import load_dotenv +import environ -load_dotenv() +env = environ.Env() +environ.Env.read_env() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent @@ -20,22 +19,13 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent # Application definition THIRD_PARTY_APPS = [ - "rest_framework", - "rest_framework_simplejwt", - "rest_framework_simplejwt.token_blacklist", - "corsheaders", - "drf_spectacular", + "django_components", + "django_htmx", + "nepali_datetime_field", ] USER_DEFINED_APPS = [ - "apps.clients", - "apps.core", - "apps.finance", - "apps.productivity", - "apps.services", - "apps.core", - "apps.notifications", - "apps.projects", + "src", ] BUILT_IN_APPS = [ @@ -51,7 +41,6 @@ INSTALLED_APPS = BUILT_IN_APPS + THIRD_PARTY_APPS + USER_DEFINED_APPS INSTALLED_APPS.insert(0, "jazzmin") MIDDLEWARE = [ - "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", @@ -62,7 +51,7 @@ MIDDLEWARE = [ "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = "config.urls" +ROOT_URLCONF = "src.urls" TEMPLATES = [ { @@ -79,7 +68,7 @@ TEMPLATES = [ }, ] -WSGI_APPLICATION = "config.wsgi.application" +WSGI_APPLICATION = "src.wsgi.application" # Password validation @@ -119,66 +108,3 @@ MEDIA_ROOT = BASE_DIR / "media" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -# REST Framework Settings -REST_FRAMEWORK = { - "DEFAULT_RENDERER_CLASSES": [ - "rest_framework.renderers.JSONRenderer", - ], - "DEFAULT_PARSER_CLASSES": [ - "rest_framework.parsers.JSONParser", - ], - "DEFAULT_AUTHENTICATION_CLASSES": ( - "rest_framework_simplejwt.authentication.JWTAuthentication", - "rest_framework.authentication.SessionAuthentication", - ), - "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", - "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", - "PAGE_SIZE": 20, -} - -# DRF Spectacular settings -SPECTACULAR_SETTINGS = { - "TITLE": "config API", - "DESCRIPTION": "API documentation for config", - "VERSION": "1.0.0", - "SERVE_INCLUDE_SCHEMA": False, - "COMPONENT_SPLIT_REQUEST": True, - "SCHEMA_PATH_PREFIX": "/api/", -} - -# JWT Settings -SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(days=1), - "REFRESH_TOKEN_LIFETIME": timedelta(days=3), - "ROTATE_REFRESH_TOKENS": True, - "BLACKLIST_AFTER_ROTATION": True, -} - -# CORS Settings -CORS_ALLOW_METHODS = [ - "DELETE", - "GET", - "OPTIONS", - "PATCH", - "POST", - "PUT", -] - -CORS_ALLOW_HEADERS = [ - "accept", - "accept-encoding", - "authorization", - "content-type", - "dnt", - "origin", - "user-agent", - "x-csrftoken", - "x-requested-with", -] - -CORS_PREFLIGHT_MAX_AGE = 86400 # 24 hours -# Security Settings -SECURE_BROWSER_XSS_FILTER = True -SECURE_CONTENT_TYPE_NOSNIFF = True -X_FRAME_OPTIONS = "DENY" diff --git a/config/settings/development.py b/src/settings/development.py similarity index 53% rename from config/settings/development.py rename to src/settings/development.py index dc52a7d..706621b 100644 --- a/config/settings/development.py +++ b/src/settings/development.py @@ -2,7 +2,7 @@ from .base import * -SECRET_KEY = "" +SECRET_KEY = "RKOHQN9iLI(J50m4KW=ge(t#69PI&BTI!yp*iq&0wtcW350dD=" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -16,16 +16,22 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", + "OPTIONS": { + "transaction_mode": "IMMEDIATE", + "timeout": 5, # seconds + "init_command": """ + PRAGMA journal_mode=WAL; + PRAGMA synchronous=NORMAL; + PRAGMA mmap_size = 134217728; + PRAGMA journal_size_limit = 27103364; + PRAGMA cache_size=2000; + """, + }, } } # Email backend for development EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" -# CORS settings for development -# Allow all origins in development (convenient but not secure) -CORS_ALLOW_ALL_ORIGINS = True -CORS_ALLOW_CREDENTIALS = True - # Disable HTTPS redirect in development SECURE_SSL_REDIRECT = False diff --git a/src/settings/production.py b/src/settings/production.py new file mode 100644 index 0000000..e2e1e64 --- /dev/null +++ b/src/settings/production.py @@ -0,0 +1,41 @@ +"""generated with djinit""" + +from .base import * + +DEBUG = False +SECRET_KEY = env("SECRET_KEY") +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[]) + +# Database +# Using dj_database_url (recommended for production) +DATABASES = {"default": env.db("DATABASE_URL")} + +# CORS settings for production +# Filter out empty strings from FRONTEND_URL + +# Additional CORS security for production +CORS_ALLOW_PRIVATE_NETWORK = False + +# Email settings +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +EMAIL_HOST = env("EMAIL_HOST") +EMAIL_PORT = env.int("EMAIL_PORT", default=587) +EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", default=True) +EMAIL_HOST_USER = env("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") +DEFAULT_FROM_EMAIL = env("EMAIL_HOST_USER") +SERVER_EMAIL = env("EMAIL_HOST_USER") + +# Security settings for production +SECURE_SSL_REDIRECT = True +SECURE_HSTS_SECONDS = 31536000 +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = True +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + +# Session settings +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True + +# Static files +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/urls.py b/src/urls.py similarity index 70% rename from config/urls.py rename to src/urls.py index b1f191f..d1f52f9 100644 --- a/config/urls.py +++ b/src/urls.py @@ -19,15 +19,7 @@ urlpatterns = [ path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), path("token/blacklist/", TokenBlacklistView.as_view(), name="token_blacklist"), - # App URLs - path("", include("apps.clients.urls")), - path("", include("apps.core.urls")), - path("", include("apps.finance.urls")), - path("", include("apps.productivity.urls")), - path("", include("apps.services.urls")), - path("", include("apps.core.urls")), - path("", include("apps.notifications.urls")), - path("", include("apps.projects.urls")), +# path("api/", include("src.api.urls")), ] # Don't show schema in production diff --git a/src/wsgi.py b/src/wsgi.py new file mode 100644 index 0000000..22bf67e --- /dev/null +++ b/src/wsgi.py @@ -0,0 +1,12 @@ +"""generated with djinit""" + +import os +import environ +from django.core.wsgi import get_wsgi_application + +env = environ.Env() +environ.Env.read_env() + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE", default="core.settings.production")) + +application = get_wsgi_application() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..793a58b --- /dev/null +++ b/uv.lock @@ -0,0 +1,258 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "asgiref" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, +] + +[[package]] +name = "backend" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "django" }, + { name = "django-components" }, + { name = "django-environ" }, + { name = "django-htmx" }, + { name = "django-jazzmin" }, + { name = "django-nepali-datetime-field" }, + { name = "gunicorn" }, + { name = "mysqlclient" }, + { name = "psycopg2-binary" }, + { name = "whitenoise" }, +] + +[package.metadata] +requires-dist = [ + { name = "django" }, + { name = "django-components", specifier = ">=0.143.2" }, + { name = "django-environ" }, + { name = "django-htmx", specifier = ">=1.27.0" }, + { name = "django-jazzmin" }, + { name = "django-nepali-datetime-field", specifier = ">=0.8.0" }, + { name = "gunicorn" }, + { name = "mysqlclient" }, + { name = "psycopg2-binary" }, + { name = "whitenoise" }, +] + +[[package]] +name = "django" +version = "6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "sqlparse" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/75/19762bfc4ea556c303d9af8e36f0cd910ab17dff6c8774644314427a2120/django-6.0.tar.gz", hash = "sha256:7b0c1f50c0759bbe6331c6a39c89ae022a84672674aeda908784617ef47d8e26", size = 10932418, upload-time = "2025-12-03T16:26:21.878Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/ae/f19e24789a5ad852670d6885f5480f5e5895576945fcc01817dfd9bc002a/django-6.0-py3-none-any.whl", hash = "sha256:1cc2c7344303bbfb7ba5070487c17f7fc0b7174bbb0a38cebf03c675f5f19b6d", size = 8339181, upload-time = "2025-12-03T16:26:16.231Z" }, +] + +[[package]] +name = "django-components" +version = "0.143.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "djc-core-html-parser" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/37/364587a54582c24a5c2f6731812b766a7cec2ec05213f7b616feaa599ce1/django_components-0.143.2.tar.gz", hash = "sha256:32f639da82d68a794b6b3a153dc9e1ae01a8fb62d83b7e200a374199962fe664", size = 214222, upload-time = "2025-11-12T16:08:09.709Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/f7/625f4bd3f0da75a74692bdcbee9d93a09079c5985569982a811778527660/django_components-0.143.2-py3-none-any.whl", hash = "sha256:12bd066fa407e26b948e51a7c7d8d4d308e6fefb6e09e99c9147f9fc96d4a968", size = 234874, upload-time = "2025-11-12T16:08:08.158Z" }, +] + +[[package]] +name = "django-environ" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/04/65d2521842c42f4716225f20d8443a50804920606aec018188bbee30a6b0/django_environ-0.12.0.tar.gz", hash = "sha256:227dc891453dd5bde769c3449cf4a74b6f2ee8f7ab2361c93a07068f4179041a", size = 56804, upload-time = "2025-01-13T17:03:37.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957, upload-time = "2025-01-13T17:03:32.918Z" }, +] + +[[package]] +name = "django-htmx" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/f2/8c3e28a5eed8e5226835c762892bfef74eda7e8629c65b49c186098eb303/django_htmx-1.27.0.tar.gz", hash = "sha256:036e5da801bfdf5f1ca815f21592cfb9f004a898f330c842f15e55c70e301a75", size = 65362, upload-time = "2025-11-28T23:18:55.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/ac/25d28489dc43224e260f4ebee7565f7ef1efe12af0f284a89500c19f75e2/django_htmx-1.27.0-py3-none-any.whl", hash = "sha256:13e1e13b87d39b57f95aae6e4987cb3df056d0b1373a41f4a94504a00298ffd8", size = 62126, upload-time = "2025-11-28T23:18:53.57Z" }, +] + +[[package]] +name = "django-jazzmin" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/96/21b6255e90d92a3eb4e93bea9376635d54258e0353ebb913a55e40ae9254/django_jazzmin-3.0.1.tar.gz", hash = "sha256:67ae148bade41267a09ca8e4352ddefa6121795ebbac238bb9a6564ff841eb1b", size = 2053550, upload-time = "2024-10-08T17:40:59.771Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/5b/2f8c4b168e6c41bf1e4b14d787deb23d80f618f0693db913bbe208a4a907/django_jazzmin-3.0.1-py3-none-any.whl", hash = "sha256:12a0a4c1d4fd09c2eef22acf6a1f03112b515ba695c59faa8ea80efc81c1f21b", size = 2125957, upload-time = "2024-10-08T17:40:57.359Z" }, +] + +[[package]] +name = "django-nepali-datetime-field" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "nepali-datetime" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/2d/5f080338f4b5e2cddbf91c312491042a761dc62e75a39aeae1ff409f55df/django_nepali_datetime_field-0.8.0.tar.gz", hash = "sha256:00d69f5d5aa6ea64e2a9964d5ba6c489b0e51325aeb7029e1b18cc4a323894ae", size = 5735, upload-time = "2025-03-09T04:19:49.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/fc/62ff130c5414a9030410673356d21e376e59cf045ecdabdef40aaf06f833/django_nepali_datetime_field-0.8.0-py3-none-any.whl", hash = "sha256:34eaab138856f69374db7af7274bafd7621224937feaf84a610e9d9872b52b37", size = 7696, upload-time = "2025-03-09T04:19:48.252Z" }, +] + +[[package]] +name = "djc-core-html-parser" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/49/d44e902ae54815ba7c54f1437c19fd5897ba361a07653935154a30d5a6d1/djc_core_html_parser-1.0.3.tar.gz", hash = "sha256:735c556440d029739d82b6f69a4c152a2a352c9355166dd5778cc42e664d715f", size = 16928, upload-time = "2025-10-21T21:04:18.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/66/710a097f7997da588f28e658811204b8d0b9476700ee5888430bce09edf9/djc_core_html_parser-1.0.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:dc52483ff89ca8ea138b644f523093a95b0acac728492ae77784964301d84d03", size = 253627, upload-time = "2025-10-21T21:03:39.782Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ad/273e65089737d87b221161231bf4edda73d77cde1bbf5d11c6cfb94f9a5f/djc_core_html_parser-1.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:415669c20919832759b7e18dffc79916afd9a6f18b9dabe3c05ff94ffc1994b7", size = 241088, upload-time = "2025-10-21T21:03:35.085Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/5e95f7292482740658df868af59a3cd810125b49d298305229406e472444/djc_core_html_parser-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f45a8221bbf6d20707f39bd35ac1decc9bd80d3c38e3a6ed45bcb81343da9cc", size = 1017679, upload-time = "2025-10-21T21:02:37.414Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/6213f4ae8df8e4e26d02ebebdf4884206693965d00304cf605f10dae7bb3/djc_core_html_parser-1.0.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2892382cbb6a0710d1e4a53bcce53d16517ec75f36a8e64afbe4d428c735691d", size = 1036931, upload-time = "2025-10-21T21:02:47.208Z" }, + { url = "https://files.pythonhosted.org/packages/2d/6a/6608180aef40a16e63da02d982a678543ce0874694cd26a88e2aeada9524/djc_core_html_parser-1.0.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5fd86fb7fb9379ee9716381882fd0f8cfe022ed136aababcdbe35f37c691f6f", size = 1043742, upload-time = "2025-10-21T21:02:56.806Z" }, + { url = "https://files.pythonhosted.org/packages/1d/51/6631af42bfb49d49ea2f720ea886df6f8063af877cf5cc9373b894e546d3/djc_core_html_parser-1.0.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a552e76011dfccff1a0bed771fb70b0077c088128ab9786b341ce417623e9258", size = 1156896, upload-time = "2025-10-21T21:03:07.217Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d8/e6232d038add5d3b2f7651cd8c881e140e0c28019bfdaf630870c64cd8d7/djc_core_html_parser-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:467c348009ad4c065ecde94695b7df5fad8684fab05788017af803943ee84699", size = 1118553, upload-time = "2025-10-21T21:03:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/c3/4e/19b1a81d49936174ca6608c97e3f7b02b3eca5b9ef32b71dab75e9aca22b/djc_core_html_parser-1.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96d09066ab5b2b927bdcfb1f080b8fa8760588ed5bd55b6287a79fdb8e6a5bca", size = 1046462, upload-time = "2025-10-21T21:03:16.951Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ee/55cd945dc6e70884e05e8b71f7413783c49eeb5cca4de228efdbf0c379df/djc_core_html_parser-1.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:01a00fb36f0bc93c192a50b6ce9207abd384be5618e21657bc6ac05f20446fad", size = 1196148, upload-time = "2025-10-21T21:03:45.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/77/3cb362b6a92e5ceccf59aabd5e7a30cacd26065a08437c472629cb3ef599/djc_core_html_parser-1.0.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8239f507636f3d58dddb2dc6b139c9238ffb922b73e70480aa7828d3b8630731", size = 1298266, upload-time = "2025-10-21T21:03:54.76Z" }, + { url = "https://files.pythonhosted.org/packages/26/1f/8f7981f3661962194fef5860d865ccccb3560cecf9b64efc00c96ede7ec6/djc_core_html_parser-1.0.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5201233706afc996eacd9e90a1ed4d5a566866aecbb692a54d32028c8e61ed76", size = 1222693, upload-time = "2025-10-21T21:04:04.444Z" }, + { url = "https://files.pythonhosted.org/packages/a6/15/33d796ef2f0f6c837398df89e489cca500556bab672c6a4c666fd572083e/djc_core_html_parser-1.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b8989260e7b5d87d49248b2cc40ca81de960aeaa23e8edadedba535ee796a5c0", size = 1213523, upload-time = "2025-10-21T21:04:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/c9/04/ccacbd6b9b92772f8bea880dcbac1b940da90c0f0499aa28bc14469b24c3/djc_core_html_parser-1.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:7b66aaafb843a7848ce75ca0cfd025fb1c6e0f658548ee9654d919d695484c83", size = 139611, upload-time = "2025-10-21T21:04:22.254Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ea/2897aea3a488240a9bfe49fb10ec9be17487ab138faeedb7ed3e6ad0edf2/djc_core_html_parser-1.0.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5524faa9500c3286f4a2722549df48c9b76d47211e4804c79dccd2a553acb3b6", size = 1012642, upload-time = "2025-10-21T21:02:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/b1/cc6720809aeaf813317f0a0febae01c8e933b51001138db110b0847a091d/djc_core_html_parser-1.0.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7328ebabc8c596a7088dcb97ac6d94796978b7cbf6b14713ccf8ae4614aac1cb", size = 1033307, upload-time = "2025-10-21T21:02:48.244Z" }, + { url = "https://files.pythonhosted.org/packages/69/dd/06143394f22648fac420133e6df94e737e7fafabd9dbd7e762ec18c822f7/djc_core_html_parser-1.0.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8898a54528b59433f4a0fc62198da90e02842b7072f549c09c95b0d2b69f95c2", size = 1038252, upload-time = "2025-10-21T21:02:58.216Z" }, + { url = "https://files.pythonhosted.org/packages/3a/26/00cc2f7a32e9284743d8f5a5bda8a57c9ddae09033f68bb6ccd1dd938d9e/djc_core_html_parser-1.0.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbd87ae6aabfecbb9534091e75e0c6577a794f44da4a04c8b802138999f0b58", size = 1151074, upload-time = "2025-10-21T21:03:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7d/4d1efe34f95d8dd3a987effb277027b8d7495d1de8206bc993573f426f5a/djc_core_html_parser-1.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c8267af6c9804f63ce6bcb4c9bfdc32685fdbfe71ffb186b796e66ce7880e755", size = 1191226, upload-time = "2025-10-21T21:03:46.429Z" }, + { url = "https://files.pythonhosted.org/packages/55/8b/6461ceccf2c2483e4e30cc0b5f385b06b9d977fb28c5b40f5993e7a3381a/djc_core_html_parser-1.0.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d6f7e17cd71fa03f9f87d161844ca91a4d15c96991af8a0e5a6ec7cbee3e32c7", size = 1294873, upload-time = "2025-10-21T21:03:55.799Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ca/232136d192dc38feac3328de335f70b7103ca7f151c6fdba233447ff8dec/djc_core_html_parser-1.0.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f4d7dc89387c97776f2e79c7d4ee60ef271c59ef90675579ca442dde4bf8b2f8", size = 1216961, upload-time = "2025-10-21T21:04:05.58Z" }, + { url = "https://files.pythonhosted.org/packages/46/bf/8e7b9f24831b1783120c40930a71a903c9b7fbe3b2579fac726f33dc6712/djc_core_html_parser-1.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:42978d265dc8931c93e5a62d2968108fe4999b236643e516178eecf83cb1f5c7", size = 1206724, upload-time = "2025-10-21T21:04:14.583Z" }, + { url = "https://files.pythonhosted.org/packages/aa/53/99cf18a29092270fc163961adb345d1239babbb3028a4053f3eecac65d99/djc_core_html_parser-1.0.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:fc7a451cd93f41670f625fa16cabd4d6fcf139167149a957c1dcf671304ac832", size = 253081, upload-time = "2025-10-21T21:03:40.723Z" }, + { url = "https://files.pythonhosted.org/packages/06/21/70914a36ea2953ebd4cc142af4d5362e4d89c7ba76685103e26e8b36e9c6/djc_core_html_parser-1.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:99d536a8f2c75a8d25b8976f70a0dbb429983c61ff0db40b93c2c25f86ebed1d", size = 240690, upload-time = "2025-10-21T21:03:35.975Z" }, + { url = "https://files.pythonhosted.org/packages/0d/20/13964d9d7414d48779a899ca932a53822ecd6daa31a421663d45cd8a870c/djc_core_html_parser-1.0.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6578c8b7356bb6eef01a7b335e1cdedc589cabdd504ca473c178ea6866a925", size = 1116898, upload-time = "2025-10-21T21:03:28.306Z" }, + { url = "https://files.pythonhosted.org/packages/9f/58/18b0b8d9858778ea2f3891a2f96e580c8d85499bcd4469cf58f4f73888ea/djc_core_html_parser-1.0.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b2a0f574c75ac6f92dd9138e0f0464e11e02d7ce9ff136227737c78bf44a04af", size = 1044211, upload-time = "2025-10-21T21:03:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b9/2a0415a5a479d38ce0092bbc29a9177e69dfcc25af88e684b0e785e13d0d/djc_core_html_parser-1.0.3-cp314-cp314-win32.whl", hash = "sha256:3dabb5e14deb1278396744facaf2186fbbaa0770a569f0f8f2ed79b220d4df43", size = 134678, upload-time = "2025-10-21T21:04:24.932Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f6/cfaf3bb275bc207135387a4eb39f0d1a198c9127780daf01267925e3e793/djc_core_html_parser-1.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:6b01b01a6e073780a75d5bba4ae8c5fe60c66025a860c7ca17e9493b6fd18c83", size = 139356, upload-time = "2025-10-21T21:04:23.14Z" }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, +] + +[[package]] +name = "mysqlclient" +version = "2.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/68/810093cb579daae426794bbd9d88aa830fae296e85172d18cb0f0e5dd4bc/mysqlclient-2.2.7.tar.gz", hash = "sha256:24ae22b59416d5fcce7e99c9d37548350b4565baac82f95e149cac6ce4163845", size = 91383, upload-time = "2025-01-10T12:06:00.763Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/01/e80141f1cd0459e4c9a5dd309dee135bbae41d6c6c121252fdd853001a8a/mysqlclient-2.2.7-cp313-cp313-win_amd64.whl", hash = "sha256:201a6faa301011dd07bca6b651fe5aaa546d7c9a5426835a06c3172e1056a3c5", size = 208000, upload-time = "2025-01-10T11:56:32.293Z" }, +] + +[[package]] +name = "nepali-datetime" +version = "1.0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/49/ee387ca2e04fae93470baf5ae7468a05c76f662208ff9c01a30552cc77f6/nepali_datetime-1.0.8.4.tar.gz", hash = "sha256:933af0ae4545ee32bc2cdaeff40aa90c067f0f7c91908350371303c3dd976a6f", size = 20322, upload-time = "2025-05-10T20:39:05.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a9/e655df60bee3fa82c18be0ca007335690ac5058015e7a344fc8c20f20344/nepali_datetime-1.0.8.4-py3-none-any.whl", hash = "sha256:13e052447167f8836618b23cc43bb5f3b259380603defdbfe8efe125f06b8462", size = 18636, upload-time = "2025-05-10T20:39:03.955Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, + { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, + { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184, upload-time = "2025-10-30T02:55:32.483Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, + { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, + { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737, upload-time = "2025-10-30T02:55:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, + { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/67/701f86b28d63b2086de47c942eccf8ca2208b3be69715a1119a4e384415a/sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e", size = 120112, upload-time = "2025-11-28T07:10:18.377Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/70/001ee337f7aa888fb2e3f5fd7592a6afc5283adb1ed44ce8df5764070f22/sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb", size = 45933, upload-time = "2025-11-28T07:10:19.73Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "whitenoise" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/95/8c81ec6b6ebcbf8aca2de7603070ccf37dbb873b03f20708e0f7c1664bc6/whitenoise-6.11.0.tar.gz", hash = "sha256:0f5bfce6061ae6611cd9396a8231e088722e4fc67bc13a111be74c738d99375f", size = 26432, upload-time = "2025-09-18T09:16:10.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/e9/4366332f9295fe0647d7d3251ce18f5615fbcb12d02c79a26f8dba9221b3/whitenoise-6.11.0-py3-none-any.whl", hash = "sha256:b2aeb45950597236f53b5342b3121c5de69c8da0109362aee506ce88e022d258", size = 20197, upload-time = "2025-09-18T09:16:09.754Z" }, +]