-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Django Ninja templates #782
base: develop
Are you sure you want to change the base?
Changes from all commits
d671a28
ad10cd8
85b863a
5b79044
025d5c8
4648823
9febcd6
33d9803
dca0eae
4b768ef
11590ba
82b44da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand the problem is not caused by this PR but there's a typo in this file: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
ARG MNP_UI | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe there's a reason for the renaming but without any context on that my preference would be to keep the more generic name: CLOUDHARNESS_FRONTEND_BUILD instead |
||
ARG CLOUDHARNESS_DJANGO | ||
|
||
FROM $MNP_UI AS frontend | ||
|
||
ARG APP_DIR=/app | ||
|
||
WORKDIR ${APP_DIR} | ||
COPY frontend/package.json . | ||
COPY frontend/yarn.lock . | ||
RUN yarn install --timeout 60000 | ||
|
||
COPY frontend . | ||
RUN yarn build | ||
|
||
##### | ||
|
||
FROM $CLOUDHARNESS_DJANGO | ||
|
||
WORKDIR ${APP_DIR} | ||
RUN mkdir -p ${APP_DIR}/static/www | ||
|
||
COPY backend/requirements.txt ${APP_DIR} | ||
RUN --mount=type=cache,target=/root/.cache python -m pip install --upgrade pip &&\ | ||
pip3 install --no-cache-dir -r requirements.txt --prefer-binary | ||
|
||
COPY backend/requirements.txt backend/setup.py ${APP_DIR} | ||
RUN python3 -m pip install -e . | ||
|
||
COPY backend ${APP_DIR} | ||
RUN python3 manage.py collectstatic --noinput | ||
|
||
COPY --from=frontend /app/dist ${APP_DIR}/static/www | ||
|
||
ENTRYPOINT uvicorn --workers ${WORKERS} --host 0.0.0.0 --port ${PORT} django_baseapp.asgi:application |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# __APP_NAME__ | ||
|
||
Django-Ninja/React-based web application. | ||
This application is constructed to be deployed inside a cloud-harness Kubernetes. | ||
It can be also run locally for development and test purpose. | ||
|
||
The code is generated with the script `harness-application`. | ||
|
||
## Configuration | ||
|
||
### Accounts | ||
|
||
The CloudHarness Django application template comes with a configuration that can retrieve user account updates from Keycloak (accounts) | ||
To enable this feature: | ||
* log in into the accounts admin interface | ||
* select in the left sidebar Events | ||
* select the `Config` tab | ||
* enable "metacell-admin-event-listener" under the `Events Config` - `Event Listeners` | ||
|
||
An other option is to enable the "metacell-admin-event-listener" through customizing the Keycloak realm.json from the CloudHarness repository. | ||
|
||
## Develop | ||
|
||
This application is composed of a Django-Ninja backend and a React frontend. | ||
|
||
### Backend | ||
|
||
Backend code is inside the *backend* directory. | ||
See [backend/README.md#Develop] | ||
|
||
### Frontend | ||
|
||
Frontend code is inside the *frontend* directory. | ||
|
||
Frontend is by default generated as a React web application, but no constraint about this specific technology. | ||
|
||
#### Call the backend apis | ||
All the api stubs are automatically generated in the [frontend/rest](frontend/rest) directory by `harness-application` | ||
and `harness-generate`. | ||
|
||
## Local build & run | ||
|
||
### Install dependencies | ||
1 - Clone cloud-harness into your project root folder | ||
|
||
2 - Run the dev setup script | ||
``` | ||
cd applications/__APP_NAME__ | ||
bash dev-setup.sh | ||
``` | ||
|
||
### Prepare backend | ||
|
||
Create a Django local superuser account, this you only need to do on initial setup | ||
```bash | ||
cd backend | ||
python3 manage.py migrate # to sync the database with the Django models | ||
python3 manage.py collectstatic --noinput # to copy all assets to the static folder | ||
python3 manage.py createsuperuser | ||
# link the frontend dist to the django static folder, this is only needed once, frontend updates will automatically be applied | ||
cd static/www | ||
ln -s ../../../frontend/dist dist | ||
``` | ||
|
||
### Build frontend | ||
|
||
Compile the frontend | ||
```bash | ||
cd frontend | ||
npm install | ||
npm run build | ||
``` | ||
|
||
### Run backend application | ||
|
||
start the Django server | ||
```bash | ||
uvicorn --workers 2 --host 0.0.0.0 --port 8000 django_baseapp.asgi:application | ||
``` | ||
|
||
|
||
### Running local with port forwardings to a kubernetes cluster | ||
When you create port forwards to microservices in your k8s cluster you want to forced your local backend server to initialize | ||
the AuthService and EventService services. | ||
This can be done by setting the `KUBERNETES_SERVICE_HOST` environment variable to a dummy or correct k8s service host. | ||
The `KUBERNETES_SERVICE_HOST` switch will activate the creation of the keycloak client and client roles of this microservice. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import time | ||
from django.http import HttpRequest | ||
from ninja import NinjaAPI | ||
from ..exceptions import Http401, Http403 | ||
|
||
|
||
api = NinjaAPI(title='__APP_NAME__ API', version='0.1.0') | ||
|
||
|
||
@api.exception_handler(Http401) | ||
def unauthorized(request, exc): | ||
return api.create_response( | ||
request, | ||
{'message': 'Unauthorized'}, | ||
status=401, | ||
) | ||
|
||
|
||
@api.exception_handler(Http403) | ||
def forbidden(request, exc): | ||
return api.create_response( | ||
request, | ||
{'message': 'Forbidden'}, | ||
status=403, | ||
) | ||
|
||
|
||
@api.get('/ping', response={200: float}, tags=['test']) | ||
def ping(request: HttpRequest): | ||
return time.time() | ||
|
||
|
||
@api.get('/live', response={200: str}, tags=['test']) | ||
def live(request: HttpRequest): | ||
return 'OK' | ||
|
||
|
||
@api.get('/ready', response={200: str}, tags=['test']) | ||
def ready(request: HttpRequest): | ||
return 'OK' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
class Http401(Exception): | ||
pass | ||
|
||
|
||
class Http403(Exception): | ||
pass |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from ninja import Schema | ||
|
||
# Create your schema here |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
""" | ||
Django settings for the MNP Checkout project. | ||
|
||
Generated by 'django-admin startproject' using Django 3.2.12. | ||
|
||
For more information on this file, see | ||
https://docs.djangoproject.com/en/3.2/topics/settings/ | ||
|
||
For the full list of settings and their values, see | ||
https://docs.djangoproject.com/en/3.2/ref/settings/ | ||
""" | ||
|
||
import os | ||
from pathlib import Path | ||
|
||
# Build paths inside the project like this: BASE_DIR / 'subdir'. | ||
BASE_DIR = Path(__file__).resolve().parent.parent | ||
|
||
|
||
# Quick-start development settings - unsuitable for production | ||
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ | ||
|
||
# SECURITY WARNING: keep the secret key used in production secret! | ||
SECRET_KEY = "django-insecure-81kv$0=07xac7r(pgz6ndb5t0at4-z@ae6&f@u6_3jo&9d#4kl" | ||
|
||
# SECURITY WARNING: don't run with debug turned on in production! | ||
DEBUG = False if os.environ.get("PRODUCTION", None) else True | ||
|
||
ALLOWED_HOSTS = [ | ||
"*", | ||
] | ||
|
||
# Application definition | ||
|
||
INSTALLED_APPS = [ | ||
"django.contrib.admin", | ||
"django.contrib.auth", | ||
"django.contrib.contenttypes", | ||
"django.contrib.sessions", | ||
"django.contrib.messages", | ||
"django.contrib.staticfiles", | ||
] | ||
|
||
MIDDLEWARE = [ | ||
"django.middleware.security.SecurityMiddleware", | ||
"django.contrib.sessions.middleware.SessionMiddleware", | ||
"django.middleware.common.CommonMiddleware", | ||
'django.middleware.csrf.CsrfViewMiddleware', | ||
"django.contrib.auth.middleware.AuthenticationMiddleware", | ||
"django.contrib.messages.middleware.MessageMiddleware", | ||
"django.middleware.clickjacking.XFrameOptionsMiddleware", | ||
"cloudharness.middleware.django.CloudharnessMiddleware", | ||
] | ||
|
||
|
||
ROOT_URLCONF = "django_baseapp.urls" | ||
|
||
TEMPLATES = [ | ||
{ | ||
"BACKEND": "django.template.backends.django.DjangoTemplates", | ||
"DIRS": [BASE_DIR / "templates"], | ||
"APP_DIRS": True, | ||
"OPTIONS": { | ||
"context_processors": [ | ||
"django.template.context_processors.debug", | ||
"django.template.context_processors.request", | ||
"django.contrib.auth.context_processors.auth", | ||
"django.contrib.messages.context_processors.messages", | ||
], | ||
}, | ||
}, | ||
] | ||
|
||
WSGI_APPLICATION = "django_baseapp.wsgi.application" | ||
|
||
|
||
# Password validation | ||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators | ||
|
||
AUTH_PASSWORD_VALIDATORS = [ | ||
{ | ||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", | ||
}, | ||
{ | ||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", | ||
}, | ||
{ | ||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", | ||
}, | ||
{ | ||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", | ||
}, | ||
] | ||
|
||
|
||
# Internationalization | ||
# https://docs.djangoproject.com/en/3.2/topics/i18n/ | ||
|
||
LANGUAGE_CODE = "en-us" | ||
|
||
TIME_ZONE = "UTC" | ||
|
||
USE_I18N = True | ||
|
||
USE_L10N = True | ||
|
||
USE_TZ = True | ||
|
||
# Default primary key field type | ||
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field | ||
|
||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" | ||
|
||
|
||
PROJECT_NAME = "__APP_NAME__".upper() | ||
|
||
# Persistent storage | ||
PERSISTENT_ROOT = os.path.join(BASE_DIR, "persistent") | ||
|
||
# *********************************************************************** | ||
# * __APP_NAME__ settings | ||
# *********************************************************************** | ||
from cloudharness.applications import get_configuration # noqa E402 | ||
from cloudharness.utils.config import ALLVALUES_PATH, CloudharnessConfig # noqa E402 | ||
|
||
# *********************************************************************** | ||
# * import base CloudHarness Django settings | ||
# *********************************************************************** | ||
from cloudharness_django.settings import * # noqa E402 | ||
|
||
# add the local apps | ||
INSTALLED_APPS += [ | ||
"__APP_NAME__", | ||
"django_baseapp", | ||
"ninja", | ||
] | ||
|
||
# override django admin base template with a local template | ||
# to add some custom styling | ||
TEMPLATES[0]["DIRS"] = [BASE_DIR / "templates"] | ||
|
||
# Static files (CSS, JavaScript, Images) | ||
MEDIA_ROOT = PERSISTENT_ROOT | ||
STATIC_ROOT = os.path.join(BASE_DIR, "static") | ||
MEDIA_URL = "/media/" | ||
STATIC_URL = "/static/" | ||
|
||
# KC Client & roles | ||
KC_CLIENT_NAME = PROJECT_NAME.lower() | ||
|
||
# __APP_NAME__ specific roles | ||
|
||
# Default KC roles | ||
KC_ADMIN_ROLE = f"{KC_CLIENT_NAME}-administrator" # admin user | ||
KC_MANAGER_ROLE = f"{KC_CLIENT_NAME}-manager" # manager user | ||
KC_USER_ROLE = f"{KC_CLIENT_NAME}-user" # customer user | ||
KC_ALL_ROLES = [ | ||
KC_ADMIN_ROLE, | ||
KC_MANAGER_ROLE, | ||
KC_USER_ROLE, | ||
] | ||
KC_PRIVILEGED_ROLES = [ | ||
KC_ADMIN_ROLE, | ||
KC_MANAGER_ROLE, | ||
] | ||
|
||
KC_DEFAULT_USER_ROLE = None # don't add the user role to the realm default role |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"""MNP Checkout URL Configuration | ||
|
||
The `urlpatterns` list routes URLs to views. For more information please see: | ||
https://docs.djangoproject.com/en/3.2/topics/http/urls/ | ||
Examples: | ||
Function views | ||
1. Add an import: from my_app import views | ||
2. Add a URL to urlpatterns: path('', views.home, name='home') | ||
Class-based views | ||
1. Add an import: from other_app.views import Home | ||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') | ||
Including another URLconf | ||
1. Import the include() function: from django.urls import include, path | ||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) | ||
""" | ||
import re | ||
from django.conf import settings | ||
from django.views.static import serve | ||
from django.contrib import admin | ||
from django.urls import path, re_path | ||
from __APP_NAME__.api import api | ||
from django_baseapp.views import index | ||
|
||
|
||
urlpatterns = [ | ||
path("admin/", admin.site.urls), | ||
path("api/", api.urls), | ||
re_path(r"^%s(?P<path>.*)$" % re.escape(settings.MEDIA_URL.lstrip("/")), serve, kwargs=dict(document_root=settings.MEDIA_ROOT)), | ||
re_path(r"^%s(?P<path>.*)$" % re.escape(settings.STATIC_URL.lstrip("/")), serve, kwargs=dict(document_root=settings.STATIC_ROOT)), | ||
re_path(r"^(?P<path>.*)$", index, name="index"), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pydantic==2.9.2 | ||
django-ninja | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to have cloudharness and cloudharness_django as dependencies here? Since they are conceptually required to run the application (f.e. they are used in settings.py) cc @ddelpiano There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect so. I understand we might need CH repo locally if I am in need of regen the template files or the codefresh pipeline, but if I am purely working on the backend I might not need CH and such dependency should be part of the requirements.txt so that all I need to run the backend is already covered by that. Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree it's better to be explicit about all the dependencies that are imported, even if they are inherited by the base image (like this case) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same typo here:
init_listner
->init_listener