Compare commits
70 Commits
78c2e45ca4
...
email
| Author | SHA1 | Date | |
|---|---|---|---|
| a9587776e8 | |||
| ec7ae4a48e | |||
| c93171dbc3 | |||
| 42417927c9 | |||
| 7c7d0e1e62 | |||
| e41eea8527 | |||
| 369c3b5e19 | |||
| aeb3aa30ce | |||
| 84cf41535c | |||
| cdf7e7c677 | |||
| b8f4331d3b | |||
| 303359c921 | |||
| ec8373877b | |||
| e7e47152ed | |||
| 1eb11f33fc | |||
| e417268991 | |||
| 4b5319f557 | |||
| 3ee2269d70 | |||
| 5d1686f24b | |||
| 99a8cfe482 | |||
| 2b1042d3a8 | |||
| f06f269568 | |||
| a31798d0b0 | |||
| af62bf843c | |||
| 5291956a31 | |||
| 7f908157bf | |||
| 35f773047d | |||
| 65444e786b | |||
| 7a392df8ad | |||
| de39913275 | |||
| 33c610dcbc | |||
| 1c0b287666 | |||
| cc833c475f | |||
| 09584e22fd | |||
| 97b14ae1d7 | |||
| 18d2604121 | |||
| b32d0fd032 | |||
| bf9f43eed8 | |||
| 18953e06b7 | |||
| b79f0c318a | |||
| c91f603a50 | |||
| f7030e8da1 | |||
| 95370ed0dc | |||
| bb634d28ed | |||
| cbdf49adfd | |||
| 7ad09e21b7 | |||
| 48ff1d799c | |||
| 02990d4b2f | |||
| bcdc885d66 | |||
| 674105600e | |||
| aa24976abb | |||
| 37b1ed4aa1 | |||
| 5f5c03e479 | |||
| 78f53cae7d | |||
| 72891440af | |||
| ae86a2e5fa | |||
| d5befdd018 | |||
| 491afb6257 | |||
| 9cae53a942 | |||
| 79d7333ca0 | |||
| edb54e9f6f | |||
| 34eabe6af7 | |||
| 71809d331f | |||
| 231a3e9861 | |||
| a91e0cd7bc | |||
| bb9ff3a86c | |||
| be25a07272 | |||
| ea33bef9cd | |||
| 637d109a91 | |||
| c8e99e9ab8 |
18
.vscode/launch.json
vendored
Normal file
18
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python Debugger: Django",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/cntmanage/manage.py",
|
||||||
|
"args": ["runserver"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"django": true,
|
||||||
|
"justMyCode": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import os
|
|||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'techdb.settings')
|
settings = os.environ.get("DJANGO_SETTINGS_MODULE", "cntmanage.settings")
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings)
|
||||||
|
|
||||||
application = get_asgi_application()
|
application = get_asgi_application()
|
||||||
@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.1/ref/settings/
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
@@ -27,10 +28,10 @@ DEBUG = True
|
|||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'admin_confirm',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
@@ -38,10 +39,21 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'nested_admin',
|
'nested_admin',
|
||||||
'catops',
|
'flightslot',
|
||||||
'flightslot'
|
'durationwidget',
|
||||||
|
'colorfield',
|
||||||
|
'import_export',
|
||||||
|
'django_admin_action_forms',
|
||||||
|
'polymorphic',
|
||||||
|
"phonenumber_field",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Import Export plugin settings
|
||||||
|
from import_export.formats.base_formats import CSV
|
||||||
|
IMPORT_EXPORT_USE_TRANSACTIONS = True
|
||||||
|
IMPORT_EXPORT_SKIP_ADMIN_LOG = True
|
||||||
|
IMPORT_FORMATS = [CSV]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
@@ -50,14 +62,15 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'flightslot.middleware.RedirectNonSuperuserFromAdminMiddleware', # custom middleware to show "user" page to non superuser
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'techdb.urls'
|
ROOT_URLCONF = 'cntmanage.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [os.path.join(BASE_DIR, 'templates/')],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@@ -70,8 +83,11 @@ TEMPLATES = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'techdb.wsgi.application'
|
WSGI_APPLICATION = 'cntmanage.wsgi.application'
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(BASE_DIR, "static"),
|
||||||
|
]
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||||
@@ -79,9 +95,9 @@ WSGI_APPLICATION = 'techdb.wsgi.application'
|
|||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
'NAME': 'techstorage',
|
'NAME': 'flightslot_db',
|
||||||
'USER': 'tech',
|
'USER': 'flightslot',
|
||||||
'PASSWORD': 'tech',
|
'PASSWORD': 'flightslot',
|
||||||
'HOST': 'localhost',
|
'HOST': 'localhost',
|
||||||
'PORT': '5432'
|
'PORT': '5432'
|
||||||
}
|
}
|
||||||
@@ -114,9 +130,9 @@ LANGUAGE_CODE = 'en-us'
|
|||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = False # Disable translation engine
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
@@ -128,3 +144,14 @@ STATIC_URL = 'static/'
|
|||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
#### Send Email for user password communication ####
|
||||||
|
# https://docs.djangoproject.com/en/5.2/topics/email/
|
||||||
|
EMAIL_HOST = "smtp.gmail.com"
|
||||||
|
EMAIL_HOST_USER = "ema.trabattoni@gmail.com"
|
||||||
|
EMAIL_HOST_PASSWORD = "okorjsenzptdiwcr"
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
|
||||||
|
EMAIL_FILE_PATH = "/mnt/d/Test/flightslot-mail" # change this to a proper location
|
||||||
161
cntmanage/cntmanage/settings_prod.py
Normal file
161
cntmanage/cntmanage/settings_prod.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
"""
|
||||||
|
Django settings for techdb project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.1.2.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 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/5.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-s%(9^y#!1*ge)7u%$vf3zp0lisgd%=(k@$13&ej13p5(ei71hi')
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
# Allowed hosts list is necessary for when in production
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
# Using a secure-only session cookie makes it more difficult for network traffic sniffers to hijack user sessions.
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
CSRF_TRUSTED_ORIGINS = ["http://localhost:8000", "http://127.0.0.1:8000", "http://10.0.2.249:8000", "https://*.etss.it"]
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'admin_confirm',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'nested_admin',
|
||||||
|
'flightslot',
|
||||||
|
'durationwidget',
|
||||||
|
'colorfield',
|
||||||
|
'import_export',
|
||||||
|
'django_admin_action_forms',
|
||||||
|
'polymorphic',
|
||||||
|
"phonenumber_field",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Import Export plugin settings
|
||||||
|
from import_export.formats.base_formats import CSV
|
||||||
|
IMPORT_EXPORT_USE_TRANSACTIONS = True
|
||||||
|
IMPORT_EXPORT_SKIP_ADMIN_LOG = True
|
||||||
|
IMPORT_FORMATS = [CSV]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||||
|
'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',
|
||||||
|
'flightslot.middleware.RedirectNonSuperuserFromAdminMiddleware', # custom middleware to show "user" page to non superuser
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'cntmanage.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': ['/var/www/templates', '/app/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 = 'cntmanage.wsgi.application'
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
|
'NAME': os.environ.get('DB_NAME','techstorage'),
|
||||||
|
'USER': os.environ.get('DB_USER','tech'),
|
||||||
|
'PASSWORD': os.environ.get('DB_PASSWORD','tech'),
|
||||||
|
'HOST': os.environ.get('DB_HOST','postgresql'),
|
||||||
|
'PORT': os.environ.get('DB_PORT','5432')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.1/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/5.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = False # Disable translation engine
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||||
|
STATIC_URL = "static/"
|
||||||
|
STATIC_ROOT = "/var/www/static/"
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
"/app/static/"
|
||||||
|
]
|
||||||
|
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
#### Send Email for user password communication ####
|
||||||
|
# https://docs.djangoproject.com/en/5.2/topics/email/
|
||||||
|
EMAIL_HOST = "smtp.gmail.com"
|
||||||
|
EMAIL_HOST_USER = "ema.trabattoni@gmail.com"
|
||||||
|
EMAIL_HOST_PASSWORD = "okorjsenzptdiwcr"
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
|
||||||
|
# Use dummy backed for testing
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
|
||||||
13
cntmanage/cntmanage/urls.py
Normal file
13
cntmanage/cntmanage/urls.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from flightslot.admin import flightslot_user
|
||||||
|
from flightslot.admin import flightslot_staff
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
#path('', RedirectView.as_view(url='/admin/', permanent=False)),
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('user/', flightslot_user.urls),
|
||||||
|
path('staff/', flightslot_staff.urls),
|
||||||
|
path("", lambda r: redirect("/user/")), # la root porta gli utenti nella pagina giusta
|
||||||
|
]
|
||||||
@@ -11,6 +11,7 @@ import os
|
|||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'techdb.settings')
|
settings = os.environ.get("DJANGO_SETTINGS_MODULE", "cntmanage.settings")
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings)
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
5
cntmanage/docker/build.sh
Executable file
5
cntmanage/docker/build.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
GIT_HASH=$(git rev-parse --short HEAD)
|
||||||
|
echo "Building Flightslot version ${GIT_HASH}"
|
||||||
|
docker compose build --build-arg GIT_HASH=${GIT_HASH}
|
||||||
5
cntmanage/docker/deploy.sh
Executable file
5
cntmanage/docker/deploy.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
git pull
|
||||||
|
./build
|
||||||
|
docker compose up -d
|
||||||
54
cntmanage/docker/docker-compose.yml
Normal file
54
cntmanage/docker/docker-compose.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Use postgres/example user/password credentials
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
image: postgres:17.0
|
||||||
|
container_name: tech-postgresql
|
||||||
|
restart: always
|
||||||
|
# set shared memory limit when using docker-compose
|
||||||
|
shm_size: 128mb
|
||||||
|
volumes:
|
||||||
|
- postgresql_data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
networks:
|
||||||
|
- cntnet
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: flightslot_db
|
||||||
|
POSTGRES_USER: flightslot
|
||||||
|
POSTGRES_PASSWORD: flightslot
|
||||||
|
PGDATA: /var/lib/postgresql/data
|
||||||
|
|
||||||
|
flightslot:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: ./docker/flightslot.Dockerfile
|
||||||
|
args:
|
||||||
|
GIT_HASH:
|
||||||
|
image: flightslot:latest
|
||||||
|
container_name: tech-flightslot
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
||||||
|
networks:
|
||||||
|
- cntnet
|
||||||
|
depends_on:
|
||||||
|
- postgresql
|
||||||
|
environment:
|
||||||
|
- DJANGO_SETTINGS_MODULE=cntmanage.settings_prod
|
||||||
|
- DJANGO_SUPERUSER_USERNAME=admin
|
||||||
|
- DJANGO_SUPERUSER_EMAIL=emanuele.trabattoni@gmail.com
|
||||||
|
- DJANGO_SUPERUSER_PASSWORD=CantorAir2k25
|
||||||
|
- SECRET_KEY=6WIjA!+mI+ZOWHaJm6v^8F4o,@-gliDtwkp*QFvpkFe"Oo0quq
|
||||||
|
- DB_NAME=flightslot_db
|
||||||
|
- DB_USER=flightslot
|
||||||
|
- DB_PASSWORD=flightslot
|
||||||
|
- DB_HOST=postgresql
|
||||||
|
- DB_PORT=5432
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgresql_data:
|
||||||
|
name: postgress_data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cntnet:
|
||||||
|
|
||||||
27
cntmanage/docker/entrypoint.sh
Executable file
27
cntmanage/docker/entrypoint.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "📦 Starting Django deploy script..."
|
||||||
|
|
||||||
|
echo "🔧 Running migrations..."
|
||||||
|
django-admin migrate --noinput
|
||||||
|
|
||||||
|
echo "📁 Collecting static files..."
|
||||||
|
django-admin collectstatic --noinput
|
||||||
|
|
||||||
|
echo "📁 Manually copying static files..."
|
||||||
|
cp -v /app/static/* /var/www/static/
|
||||||
|
|
||||||
|
echo "📁 Manually copying template files..."
|
||||||
|
cp -rv /app/templates/ /var/www/templates/
|
||||||
|
|
||||||
|
echo "👁️ Creating superuser..."
|
||||||
|
if django-admin createsuperuser --noinput --email ${DJANGO_SUPERUSER_EMAIL} --username ${DJANGO_SUPERUSER_USERNAME}; then
|
||||||
|
echo "👁️ Superuser ${DJANGO_SUPERUSER_USERNAME} already created ..."
|
||||||
|
else
|
||||||
|
echo "👁️ Superuser ${DJANGO_SUPERUSER_USERNAME} created successfully ..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🚀 Launching Flightslot version ${VERSION} ..."
|
||||||
|
exec "$@"
|
||||||
39
cntmanage/docker/flightslot.Dockerfile
Normal file
39
cntmanage/docker/flightslot.Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
### STAGE 1 - Builder image ###
|
||||||
|
# Builder container
|
||||||
|
FROM python:3.12 AS builder
|
||||||
|
# Install Poetry
|
||||||
|
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
ENV PATH="${PATH}:/root/.local/bin"
|
||||||
|
# Create build directory
|
||||||
|
WORKDIR /build
|
||||||
|
# Copy project files
|
||||||
|
COPY . ./
|
||||||
|
# Run poetry update to download dependencies
|
||||||
|
RUN poetry update --no-interaction --no-ansi
|
||||||
|
# Build project
|
||||||
|
RUN poetry build
|
||||||
|
|
||||||
|
### STAGE 2 — Final image ###
|
||||||
|
FROM python:3.12-slim AS deploy
|
||||||
|
# Create app run directory
|
||||||
|
WORKDIR /app
|
||||||
|
# Copy application custom static files
|
||||||
|
RUN mkdir -p static
|
||||||
|
COPY ./static/* ./static
|
||||||
|
# Copy application custom templates for admin page
|
||||||
|
RUN mkdir -p /templates/admin
|
||||||
|
RUN mkdir -p /templates/email
|
||||||
|
COPY ./templates/admin/* ./templates/admin/
|
||||||
|
COPY ./templates/email/* ./templates/email/
|
||||||
|
# Copy and install application wheel package
|
||||||
|
COPY --from=builder /build/dist/*.whl ./
|
||||||
|
RUN pip install --no-cache-dir *.whl
|
||||||
|
RUN pip install gunicorn whitenoise
|
||||||
|
# Copy entryupoint bash script
|
||||||
|
COPY ./docker/entrypoint.sh ./
|
||||||
|
# Collect build number from build arg
|
||||||
|
ARG GIT_HASH
|
||||||
|
ENV VERSION=${GIT_HASH}
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
|
# Command to be executed after entry point
|
||||||
|
CMD ["gunicorn", "cntmanage.wsgi:application", "--bind", "0.0.0.0:8000", "--timeout", "600"]
|
||||||
23
cntmanage/flightslot/actions/assign_aircraft.py
Normal file
23
cntmanage/flightslot/actions/assign_aircraft.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from django.db.models.query import QuerySet, Q
|
||||||
|
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
from ..models.students import Student
|
||||||
|
from ..models.missions import MissionProfile
|
||||||
|
from ..models.aircrafts import Aircraft, AircraftTypes
|
||||||
|
|
||||||
|
from typing import List, Dict, Tuple
|
||||||
|
|
||||||
|
def assign_aircraft(queryset: QuerySet[Student] | QuerySet[MissionProfile] | QuerySet[Instructor], data: Dict[str, List[AircraftTypes]]) -> Tuple[int, List[str]]:
|
||||||
|
i: int = 0
|
||||||
|
ac_types: List[AircraftTypes] = data["aircrafts"]
|
||||||
|
ac_query: Q = Q() # Build an or query to select all aircrafts of the specified types
|
||||||
|
for a in ac_types:
|
||||||
|
ac_query |= Q(type=a)
|
||||||
|
aircrafts: QuerySet[Aircraft] = Aircraft.objects.filter(ac_query).all() # Execute query
|
||||||
|
for obj in queryset:
|
||||||
|
obj.aircrafts.clear()
|
||||||
|
for ac in aircrafts:
|
||||||
|
obj.aircrafts.add(ac)
|
||||||
|
obj.save()
|
||||||
|
i += 1
|
||||||
|
return i, [a for a in ac_types]
|
||||||
20
cntmanage/flightslot/actions/assign_profile.py
Normal file
20
cntmanage/flightslot/actions/assign_profile.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from django.db.models.query import QuerySet, Q
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
from ..models.missions import MissionProfile, MissionTypes
|
||||||
|
|
||||||
|
from typing import List, Dict, Tuple
|
||||||
|
|
||||||
|
def assign_profile(queryset: QuerySet[Instructor], data: Dict[str, List[MissionTypes]]) -> Tuple[int, List[str]]:
|
||||||
|
i: int = 0
|
||||||
|
mix_types: List[MissionTypes] = data["mission_profiles"]
|
||||||
|
mix_query: Q = Q() # Build an or query to select all aircrafts of the specified types
|
||||||
|
for m in mix_types:
|
||||||
|
mix_query |= Q(mtype=m)
|
||||||
|
profiles: QuerySet[MissionProfile] = MissionProfile.objects.filter(mix_query).all() # Execute query
|
||||||
|
for obj in queryset:
|
||||||
|
obj.missions.clear()
|
||||||
|
for ac in profiles:
|
||||||
|
obj.missions.add(ac)
|
||||||
|
obj.save()
|
||||||
|
i += 1
|
||||||
|
return i, [m for m in mix_types]
|
||||||
245
cntmanage/flightslot/actions/exportweek.py
Normal file
245
cntmanage/flightslot/actions/exportweek.py
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
|
||||||
|
from ..models.courses import CourseTypes
|
||||||
|
from ..models.missions import Training
|
||||||
|
from ..models.weekpref import WeekPreference
|
||||||
|
from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop, HourBuildingLegBase
|
||||||
|
|
||||||
|
from datetime import date, datetime
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# Enable cell merging for equal mission
|
||||||
|
MERGE: bool = False
|
||||||
|
|
||||||
|
PALETTE : List[str] = [
|
||||||
|
"#E6F2FF", # azzurro chiarissimo
|
||||||
|
"#E5FBF8", # verde acqua molto chiaro
|
||||||
|
"#ECFBE1", # verde chiarissimo
|
||||||
|
"#FFFBD1", # giallo molto chiaro
|
||||||
|
"#FFF1D6", # giallo-arancio molto chiaro
|
||||||
|
"#FFE3DD", # rosa pesca molto chiaro
|
||||||
|
"#F3E6FA", # lilla chiarissimo
|
||||||
|
]
|
||||||
|
|
||||||
|
def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse:
|
||||||
|
|
||||||
|
if not queryset.first():
|
||||||
|
raise Exception("Empty queryset")
|
||||||
|
|
||||||
|
# Init Variables
|
||||||
|
year = date.today().year
|
||||||
|
week = queryset.first().week if queryset.first() else date.today().isocalendar().week
|
||||||
|
weeks = queryset.order_by("week").distinct("week").all()
|
||||||
|
|
||||||
|
# Prepare export filename and http content
|
||||||
|
filename = f"{year}_week{'+'.join([str(w.week) for w in weeks])}_export.xlsx"
|
||||||
|
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||||
|
|
||||||
|
# Create workbook and sheet
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
if not ws:
|
||||||
|
raise Exception("Export: cannot select active workbook")
|
||||||
|
ws.title = f"Week Preferences"
|
||||||
|
ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE
|
||||||
|
ws.page_setup.paperSize = ws.PAPERSIZE_A3
|
||||||
|
ws.page_setup.fitToHeight = 0
|
||||||
|
ws.page_setup.fitToWidth = 1
|
||||||
|
|
||||||
|
# Header titles
|
||||||
|
days = [f"{datetime.strptime(f"{year} {week} {x}", "%G %V %u").strftime("%A")} {datetime.strptime(f"{year} {week} {x}", "%G %V %u").day}" for x in range(1,8)]
|
||||||
|
headers = ["Week", "Student", "Course", *days, "Notes", "Cell.", "Mail"]
|
||||||
|
|
||||||
|
# Header fields positions
|
||||||
|
week_index: int = headers.index("Week") + 1
|
||||||
|
student_index: int = headers.index("Student") + 1
|
||||||
|
course_index: int = headers.index("Course") + 1
|
||||||
|
cell_index: int = headers.index("Cell.") + 1
|
||||||
|
mail_index: int = headers.index("Mail") + 1
|
||||||
|
note_index: int = headers.index("Notes") + 1
|
||||||
|
|
||||||
|
# Stile header
|
||||||
|
header_fill = PatternFill("solid", fgColor="0e005c")
|
||||||
|
bold_white = Font(color="FFFFFF", bold=True)
|
||||||
|
bold_black = Font(color="000000", bold=True)
|
||||||
|
center = Alignment(horizontal="center", vertical="center", wrapText=True)
|
||||||
|
|
||||||
|
# Cell styles
|
||||||
|
border_thick: Side = Side(style='medium', color='000000')
|
||||||
|
border_thin: Side = Side(style='thin', color='000000', border_style='dashed')
|
||||||
|
border_bottom: Border = Border(bottom=border_thick)
|
||||||
|
border_bottom_thin: Border = Border(bottom=border_thin)
|
||||||
|
border_left: Border = Border(left=border_thick)
|
||||||
|
border_right: Border = Border(right=border_thick)
|
||||||
|
border_right_thin: Border = Border(right=border_thin)
|
||||||
|
border_all: Border = Border(bottom=border_thick, top=border_thick, left=border_thick, right=None)
|
||||||
|
|
||||||
|
# Scrittura header
|
||||||
|
for col, h in enumerate(headers, start=1):
|
||||||
|
cell = ws.cell(row=1, column=col, value=h)
|
||||||
|
cell.fill = header_fill
|
||||||
|
cell.font = bold_white
|
||||||
|
cell.alignment = center
|
||||||
|
|
||||||
|
### Start of Student Loop ###
|
||||||
|
# Fill worksheet with EVERY training and hb for every student
|
||||||
|
# Each of this iterations fills the table for a student
|
||||||
|
row: int = 2
|
||||||
|
row_offset: int = 0
|
||||||
|
for i, q in enumerate(queryset.order_by("-week", "student__surname", "student__name", "student__course"), start=1):
|
||||||
|
student_data: List[str]
|
||||||
|
student_phone: str = str(q.student.phone) if q.student.phone else ""
|
||||||
|
student_email: str = q.student.email
|
||||||
|
student_course_type: str
|
||||||
|
student_course_number: str
|
||||||
|
student_course_ac: str = f"({'/'.join(t.type for t in q.student.aircrafts.distinct("type").all())})"
|
||||||
|
if q.student.course:
|
||||||
|
student_course_type = q.student.course.ctype
|
||||||
|
student_course_number = str(q.student.course.cnumber)
|
||||||
|
student_data = [
|
||||||
|
"\n".join([f"{q.student.surname} {q.student.name}", student_course_ac]),
|
||||||
|
f"{student_course_type}-{student_course_number}"
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"]
|
||||||
|
|
||||||
|
# Fill Training mission rows
|
||||||
|
mission_name: List[str]
|
||||||
|
mission_days: List[str]
|
||||||
|
mission_notes: str
|
||||||
|
mission_data: List[List[str]] = []
|
||||||
|
for t in Training.objects.filter(weekpref = q.id):
|
||||||
|
if not t.mission:
|
||||||
|
raise Exception("No Training Mission Assigned")
|
||||||
|
mission_notes = t.notes.strip().capitalize() if t.notes else ""
|
||||||
|
mission_name = [f"{t.mission.mtype}-{t.mission.mnum}"]
|
||||||
|
if q.student.course and q.student.course.ctype == CourseTypes.PPL: # add course aircraft only for PPL students
|
||||||
|
mission_name.append(student_course_ac)
|
||||||
|
mission_name_joined: str = "\n".join(mission_name)
|
||||||
|
mission_days = [
|
||||||
|
mission_name_joined if t.monday else "",
|
||||||
|
mission_name_joined if t.tuesday else "",
|
||||||
|
mission_name_joined if t.wednesday else "",
|
||||||
|
mission_name_joined if t.thursday else "",
|
||||||
|
mission_name_joined if t.friday else "",
|
||||||
|
mission_name_joined if t.saturday else "",
|
||||||
|
mission_name_joined if t.sunday else ""
|
||||||
|
]
|
||||||
|
mission_data.append([str(q.week), *student_data, *mission_days, mission_notes, student_phone, student_email, ])
|
||||||
|
|
||||||
|
# Fill HourBuilding rows
|
||||||
|
hb_name: List[str]
|
||||||
|
hb_days: List[str]
|
||||||
|
hb_data: List[List[str]] = []
|
||||||
|
for h in HourBuilding.objects.filter(weekpref = q.id):
|
||||||
|
hb_name = ["HB", f"({h.aircraft})"]
|
||||||
|
hb_legs_all = HourBuildingLegBase.objects.filter(hb_id = h.id)
|
||||||
|
for hh in hb_legs_all:
|
||||||
|
time_str: str = ':'.join(str(hh.time).split(':')[:2]) # keep only hours and minutes
|
||||||
|
if isinstance(hh, HourBuildingLegFlight):
|
||||||
|
hb_pax: str | None = " ".join(x.capitalize() for x in hh.pax.split()) if hh.pax else None
|
||||||
|
hb_name.append(f"{hh.departure} -> {hh.destination} [{time_str}]{f' / Pax: {hb_pax}' if hb_pax else ''}")
|
||||||
|
elif isinstance(hh, HourBuildingLegStop):
|
||||||
|
hb_name.append(f"STOP [{time_str}] {"Refuel" if hh.refuel else ""}" )
|
||||||
|
hb_name_joined: str = "\n".join(hb_name)
|
||||||
|
hb_days = [
|
||||||
|
hb_name_joined if h.monday else "",
|
||||||
|
hb_name_joined if h.tuesday else "",
|
||||||
|
hb_name_joined if h.wednesday else "",
|
||||||
|
hb_name_joined if h.thursday else "",
|
||||||
|
hb_name_joined if h.friday else "",
|
||||||
|
hb_name_joined if h.saturday else "",
|
||||||
|
hb_name_joined if h.sunday else ""
|
||||||
|
]
|
||||||
|
hb_notes: str = h.notes.strip().capitalize() if h.notes else ""
|
||||||
|
hb_data.append([str(q.week), *student_data, *hb_days, hb_notes, str(q.student.phone), q.student.email])
|
||||||
|
|
||||||
|
# Build rows for table
|
||||||
|
all_data: List[List[str]] = mission_data + hb_data
|
||||||
|
student_start: int = row + row_offset
|
||||||
|
for ri, row_content in enumerate(all_data):
|
||||||
|
for c, cell_content in enumerate(row_content, start=1):
|
||||||
|
cell = ws.cell(row = row + row_offset, column = c, value = cell_content)
|
||||||
|
cell.alignment = center
|
||||||
|
# Format Student Name
|
||||||
|
if c == student_index:
|
||||||
|
cell.font = bold_black
|
||||||
|
# Format Course Column with color
|
||||||
|
elif c == course_index and q.student.course:
|
||||||
|
cell.font = bold_black
|
||||||
|
cell.fill = PatternFill("solid", fgColor=str(q.student.course.color).lstrip('#').lower())
|
||||||
|
# Add internal borders between mix cells and notes
|
||||||
|
elif c > course_index and c <= note_index:
|
||||||
|
cell.border = border_bottom_thin + border_right_thin
|
||||||
|
# Fill mix cells if the cell is not empty
|
||||||
|
if c > course_index and c <= note_index:
|
||||||
|
if len(cell_content):
|
||||||
|
cell.fill = PatternFill('solid', fgColor=PALETTE[ri % len(PALETTE)].lstrip("#").lower())
|
||||||
|
if MERGE:
|
||||||
|
prev_cell_val: str = row_content[0]
|
||||||
|
merge_start: bool = False
|
||||||
|
merge_col_start: int = 1
|
||||||
|
for c, cell_content in enumerate(row_content, start=1):
|
||||||
|
# Merge cells in the row
|
||||||
|
if cell_content == prev_cell_val and not merge_start:
|
||||||
|
merge_start = True
|
||||||
|
merge_col_start = c-1 # start merge from previous column
|
||||||
|
elif cell_content != prev_cell_val and merge_start:
|
||||||
|
merge_start = False
|
||||||
|
ws.merge_cells(start_row=row+row_offset,
|
||||||
|
end_row=row+row_offset,
|
||||||
|
start_column=max(merge_col_start,1),
|
||||||
|
end_column=max(c-1,1)) # end merge to previous column
|
||||||
|
prev_cell_val = cell_content
|
||||||
|
|
||||||
|
# Incement row counter
|
||||||
|
row_offset += 1
|
||||||
|
|
||||||
|
# End week preferences for this student
|
||||||
|
student_end: int = row + row_offset - 1
|
||||||
|
|
||||||
|
# Add thick border to the last cell row of this student
|
||||||
|
for c in range(course_index, mail_index + 1):
|
||||||
|
ws.cell(row=student_end, column=c).border = Border(bottom=border_thick, right=border_thin)
|
||||||
|
# And for last column also a vertical border all student high
|
||||||
|
if c == mail_index:
|
||||||
|
ws.cell(row=student_end, column=c).border = Border(bottom=border_thick, right=border_thick)
|
||||||
|
|
||||||
|
# Merge Week, thick border
|
||||||
|
ws.cell(row=student_start, column=week_index).border = border_all
|
||||||
|
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=week_index, end_column=week_index)
|
||||||
|
# Merge Name, thick border
|
||||||
|
ws.cell(row=student_start, column=student_index).border = border_all
|
||||||
|
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=student_index, end_column=student_index)
|
||||||
|
# Merge Course
|
||||||
|
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=course_index, end_column=course_index)
|
||||||
|
# Merge Cell
|
||||||
|
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=cell_index, end_column=cell_index)
|
||||||
|
# Merge Mail
|
||||||
|
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=mail_index, end_column=mail_index)
|
||||||
|
|
||||||
|
# Keep the largest column
|
||||||
|
max_len: List[int] = []
|
||||||
|
col_letter: str = "A"
|
||||||
|
for column_cells in ws.columns:
|
||||||
|
for cell in column_cells:
|
||||||
|
cell_lines = str(cell.value).splitlines()
|
||||||
|
if len(cell_lines) == 0:
|
||||||
|
continue
|
||||||
|
max_len.append(max([len(ll) for ll in cell_lines]))
|
||||||
|
length: int = max(max_len)
|
||||||
|
if column_cells[0].column:
|
||||||
|
col_letter = get_column_letter(column_cells[0].column)
|
||||||
|
ws.column_dimensions[col_letter].width = length + 2
|
||||||
|
### End of Student Loop ###
|
||||||
|
|
||||||
|
# Save document in HttpResponse
|
||||||
|
wb.save(response)
|
||||||
|
|
||||||
|
return response
|
||||||
73
cntmanage/flightslot/actions/send_email.py
Normal file
73
cntmanage/flightslot/actions/send_email.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
from django.contrib.staticfiles import finders
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from ..models.students import Student
|
||||||
|
|
||||||
|
from smtplib import SMTPException
|
||||||
|
from email.mime.image import MIMEImage
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def send_mail_password(request: HttpRequest, queryset: QuerySet[Student]) -> None:
|
||||||
|
img: MIMEImage | None = None
|
||||||
|
filename: str
|
||||||
|
candidates = finders.find("cantorair.png")
|
||||||
|
if not candidates:
|
||||||
|
messages.error(request=request, message="Cannot Load CantorAir Logo")
|
||||||
|
return
|
||||||
|
elif isinstance(candidates, list):
|
||||||
|
filename = candidates.pop()
|
||||||
|
else:
|
||||||
|
filename = candidates
|
||||||
|
|
||||||
|
with open(filename, "rb") as f:
|
||||||
|
img = MIMEImage(f.read())
|
||||||
|
img.add_header("Content-ID", "logo_image")
|
||||||
|
img.add_header("Content-Disposition", "inline", filename="cantorair.png")
|
||||||
|
|
||||||
|
# build mail list filling template
|
||||||
|
mails: List[EmailMultiAlternatives] = []
|
||||||
|
for student in queryset:
|
||||||
|
if not student.user or not student.email: # skip student if has not an associated user
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
username: str = student.user.username
|
||||||
|
password: str = student.default_password()
|
||||||
|
address: str = student.email
|
||||||
|
|
||||||
|
text_message: str = f"Cantor Air Flight Scheduler\nUsername:{username}\nPassword:{password}\n"
|
||||||
|
|
||||||
|
html_message: SafeText = render_to_string(
|
||||||
|
template_name="email/mail.html",
|
||||||
|
context={"username": username, "password": password}
|
||||||
|
)
|
||||||
|
|
||||||
|
mail: EmailMultiAlternatives = EmailMultiAlternatives(
|
||||||
|
subject="CantorAir Flight Scheduler 🛫",
|
||||||
|
from_email="ema.trabattoni@gmail.com",
|
||||||
|
body=text_message,
|
||||||
|
to = [ address ]
|
||||||
|
)
|
||||||
|
mail.attach(filename=img)
|
||||||
|
mail.attach_alternative(content=html_message, mimetype="text/html")
|
||||||
|
mails.append(mail)
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request=request, message=f"General Error: {e}")
|
||||||
|
|
||||||
|
# Open only one conenction and send mass email
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
conn.send_messages(mails)
|
||||||
|
except SMTPException as e:
|
||||||
|
messages.error(request=request, message=f"Send Mail SMTP error: {e.strerror}")
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request=request, message=f"Send Mail General error: {e}")
|
||||||
|
else:
|
||||||
|
messages.success(request=request, message=f"Successfully sent {len(mails)} messages")
|
||||||
|
|
||||||
|
return
|
||||||
79
cntmanage/flightslot/admin.py
Normal file
79
cntmanage/flightslot/admin.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
from .models.aircrafts import Aircraft
|
||||||
|
from .models.courses import Course
|
||||||
|
from .models.students import Student
|
||||||
|
from .models.missions import MissionProfile
|
||||||
|
from .models.weekpref import WeekPreference
|
||||||
|
from .models.instructors import Instructor
|
||||||
|
from .models.availabilities import Availability
|
||||||
|
|
||||||
|
from .admins.aircraft_adm import AircraftAdmin
|
||||||
|
from .admins.course_adm import CourseAdmin
|
||||||
|
from .admins.student_adm import StudentAdmin
|
||||||
|
from .admins.mission_adm import MissionProfileAdmin
|
||||||
|
from .admins.weekpref_adm import WeekPreferenceAdmin
|
||||||
|
from .admins.instructor_admin import InstructorAdmin
|
||||||
|
from .admins.availability_adm import AvailabilityAdmin
|
||||||
|
|
||||||
|
from django.contrib.admin import AdminSite
|
||||||
|
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# User website under /user/ URL #
|
||||||
|
##################################
|
||||||
|
class FlightSlotUserSite(AdminSite):
|
||||||
|
site_header = "Flight Scheduler 🛫"
|
||||||
|
site_title = "Flight Scheduler 🛫"
|
||||||
|
index_title = "Welcome to CantorAir Flight Scheduler Student Portal"
|
||||||
|
|
||||||
|
def get_app_list(self, request: HttpRequest, *args, **kwargs):
|
||||||
|
app_list = super().get_app_list(request)
|
||||||
|
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
self.enable_nav_sidebar = False
|
||||||
|
|
||||||
|
return app_list
|
||||||
|
|
||||||
|
# Register only user visible models
|
||||||
|
flightslot_user = FlightSlotUserSite(name="user_site")
|
||||||
|
flightslot_user.register(WeekPreference, WeekPreferenceAdmin)
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# User website under /staff/ URL #
|
||||||
|
##################################
|
||||||
|
class FlightSlotStaffSite(AdminSite):
|
||||||
|
site_header = "Flight Scheduler Staff 🛫"
|
||||||
|
site_title = "Flight Scheduler Staff 🛫"
|
||||||
|
index_title = "Welcome to CantorAir Flight Scheduler Staff Portal"
|
||||||
|
|
||||||
|
def get_app_list(self, request: HttpRequest, *args, **kwargs):
|
||||||
|
app_list = super().get_app_list(request)
|
||||||
|
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
self.enable_nav_sidebar = False
|
||||||
|
|
||||||
|
return app_list
|
||||||
|
|
||||||
|
# Register only user visible models
|
||||||
|
flightslot_staff = FlightSlotUserSite(name="staff_site")
|
||||||
|
flightslot_staff.register(MissionProfile, MissionProfileAdmin)
|
||||||
|
flightslot_staff.register(Availability, AvailabilityAdmin)
|
||||||
|
flightslot_staff.register(Instructor, InstructorAdmin)
|
||||||
|
|
||||||
|
# Get version for debug purposes
|
||||||
|
ver: str = environ.get("VERSION", "dev")
|
||||||
|
# Register all visible models
|
||||||
|
admin.site.site_header = f"Flight Scheduler Admin 🛫 - ver.{ver}"
|
||||||
|
admin.site.site_title = f"Flight Scheduler Admin 🛫 - ver.{ver}"
|
||||||
|
admin.site.index_title = "Welcome to CantorAir Flight Scheduler Administrator Portal"
|
||||||
|
|
||||||
|
admin.site.register(Aircraft, AircraftAdmin)
|
||||||
|
admin.site.register(Course, CourseAdmin)
|
||||||
|
admin.site.register(MissionProfile, MissionProfileAdmin)
|
||||||
|
admin.site.register(Student, StudentAdmin)
|
||||||
|
admin.site.register(WeekPreference, WeekPreferenceAdmin)
|
||||||
|
#admin.site.register(Instructor, InstructorAdmin)
|
||||||
|
#admin.site.register(Availability, AvailabilityAdmin)
|
||||||
17
cntmanage/flightslot/admins/aircraft_adm.py
Normal file
17
cntmanage/flightslot/admins/aircraft_adm.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
from ..models.aircrafts import Aircraft
|
||||||
|
|
||||||
|
class AircraftAdmin(admin.ModelAdmin):
|
||||||
|
model = Aircraft
|
||||||
|
list_display = ("type", "markings", "avail_hours", "complex", )
|
||||||
|
list_filter = ("type", )
|
||||||
|
actions = ("reset_maint")
|
||||||
|
|
||||||
|
def get_queryset(self, request: HttpRequest) -> QuerySet[Aircraft]:
|
||||||
|
qs: QuerySet[Aircraft] = super().get_queryset(request)
|
||||||
|
qs.order_by("type", "markings")
|
||||||
|
return qs
|
||||||
|
|
||||||
66
cntmanage/flightslot/admins/availability_adm.py
Normal file
66
cntmanage/flightslot/admins/availability_adm.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
from ..models.availabilities import Availability
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
class AvailabilityForm(forms.ModelForm):
|
||||||
|
model=Availability
|
||||||
|
|
||||||
|
class AvailabilityAdmin(admin.ModelAdmin):
|
||||||
|
model = Availability
|
||||||
|
list_display = ("week", "instructor__surname", "instructor__name", "days_available", "hours")
|
||||||
|
list_filter = ("week", )
|
||||||
|
search_fields = ("instructor__surname","instructor__name", )
|
||||||
|
#actions = ("export", )
|
||||||
|
|
||||||
|
@admin.display(description="Days Available")
|
||||||
|
def days_available(self, obj: Availability) -> SafeText:
|
||||||
|
if not obj:
|
||||||
|
return SafeText("")
|
||||||
|
days: List[str | None] = [
|
||||||
|
"Mon" if obj.monday else None,
|
||||||
|
"Tue" if obj.tuesday else None,
|
||||||
|
"Wed" if obj.wednesday else None,
|
||||||
|
"Thu" if obj.thursday else None,
|
||||||
|
"Fri" if obj.friday else None,
|
||||||
|
"Sat" if obj.saturday else None,
|
||||||
|
"Sun" if obj.sunday else None,
|
||||||
|
]
|
||||||
|
return SafeText("/".join(d if d else "" for d in days))
|
||||||
|
|
||||||
|
def get_queryset(self, request: HttpRequest) -> QuerySet:
|
||||||
|
return super().get_queryset(request).order_by("-week", "instructor__surname", "instructor__name")
|
||||||
|
|
||||||
|
def get_form(self, request: HttpRequest, obj: Availability | None = None, change: bool = False, **kwargs: Any) -> AvailabilityForm:
|
||||||
|
form: AvailabilityForm = super().get_form(request, obj, change, **kwargs)
|
||||||
|
|
||||||
|
if change: # if is only a form change do not set default values and return form
|
||||||
|
return form
|
||||||
|
|
||||||
|
# If form contains the week field
|
||||||
|
current_week = date.today().isocalendar().week
|
||||||
|
if "week" in form.base_fields:
|
||||||
|
# Set default value as current week
|
||||||
|
form.base_fields["week"].initial = current_week
|
||||||
|
|
||||||
|
# If student is current user making request
|
||||||
|
if hasattr(request.user, "instructor"):
|
||||||
|
instructor: Instructor = request.user.instructor
|
||||||
|
if "instructor" in form.base_fields:
|
||||||
|
form.base_fields["instructor"].initial = instructor
|
||||||
|
form.base_fields["instructor"].disabled = True
|
||||||
|
return form
|
||||||
|
|
||||||
|
# Imposta automaticamente l'istruttore se non è già valorizzato
|
||||||
|
def save_model(self, request: HttpRequest, obj: Availability, form: AvailabilityForm, change: bool):
|
||||||
|
if hasattr(request.user, "instructor") and not obj.instructor_id:
|
||||||
|
obj.instructor = request.user.instructor
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
28
cntmanage/flightslot/admins/course_adm.py
Normal file
28
cntmanage/flightslot/admins/course_adm.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from ..models.students import Student
|
||||||
|
from ..models.courses import Course
|
||||||
|
from ..custom.colortag import course_color
|
||||||
|
|
||||||
|
class CourseAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("ctype", "cnumber","color_display", "course_students", "year")
|
||||||
|
list_filter = ("ctype", "year")
|
||||||
|
|
||||||
|
def get_queryset(self, request: HttpRequest) -> QuerySet:
|
||||||
|
return super().get_queryset(request).order_by("ctype", "cnumber")
|
||||||
|
|
||||||
|
@admin.display(description="Student Number")
|
||||||
|
def course_students(self, obj: Course) -> SafeText:
|
||||||
|
if not obj.pk:
|
||||||
|
return SafeText("")
|
||||||
|
return SafeText(f"{Student.objects.filter(course = obj.id).count()}")
|
||||||
|
|
||||||
|
# Dinamically add color_display property to show a colored dot
|
||||||
|
@admin.display(description="Color")
|
||||||
|
def color_display(self, obj: Course) -> SafeText:
|
||||||
|
if not obj.pk:
|
||||||
|
return SafeText("")
|
||||||
|
return course_color(obj.color)
|
||||||
83
cntmanage/flightslot/admins/hourbuilding_adm.py
Normal file
83
cntmanage/flightslot/admins/hourbuilding_adm.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import nested_admin
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.db import models
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.forms import TextInput, Textarea
|
||||||
|
|
||||||
|
from durationwidget.widgets import TimeDurationWidget
|
||||||
|
|
||||||
|
from ..models.hourbuildings import HourBuilding, HourBuildingLegBase, HourBuildingLegFlight, HourBuildingLegStop
|
||||||
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
|
from ..custom.student_permissions import has_edit_permission
|
||||||
|
|
||||||
|
class HourBuildingLegFlightForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = HourBuildingLegFlight
|
||||||
|
fields = "__all__"
|
||||||
|
widgets = {
|
||||||
|
"time": TimeDurationWidget(show_days=False,
|
||||||
|
show_seconds=False,
|
||||||
|
attrs={
|
||||||
|
"style": (
|
||||||
|
"margin-right:5px; margin-left:5px; width:40px; min:0; max:5"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class HourBuildingLegStopForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = HourBuildingLegStop
|
||||||
|
fields = "__all__"
|
||||||
|
widgets = {
|
||||||
|
"time": TimeDurationWidget(show_days=False,
|
||||||
|
show_seconds=False,
|
||||||
|
attrs={
|
||||||
|
"style": (
|
||||||
|
"margin-right:5px; margin-left:5px; width:40px;"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
class HourBuildingLegBaseInLine(nested_admin.NestedStackedPolymorphicInline):
|
||||||
|
model = HourBuildingLegBase
|
||||||
|
fk_name = "hb"
|
||||||
|
verbose_name_plural = "Hour Building Legs"
|
||||||
|
|
||||||
|
class HourBuildingLegFlightInLine(nested_admin.NestedStackedPolymorphicInline.Child):
|
||||||
|
model = HourBuildingLegFlight
|
||||||
|
form = HourBuildingLegFlightForm
|
||||||
|
fk_name = "hourbuildinglegbase_ptr"
|
||||||
|
fields = ("departure", "time", "destination", "pax", )
|
||||||
|
|
||||||
|
class HourBuildingLegStopInLine(nested_admin.NestedStackedPolymorphicInline.Child):
|
||||||
|
model = HourBuildingLegStop
|
||||||
|
form = HourBuildingLegFlightForm
|
||||||
|
fk_name = "hourbuildinglegbase_ptr"
|
||||||
|
fields = ("time", "refuel", )
|
||||||
|
|
||||||
|
child_inlines = (HourBuildingLegFlightInLine, HourBuildingLegStopInLine, )
|
||||||
|
|
||||||
|
class HourBuildingInLine(nested_admin.NestedTabularInline):
|
||||||
|
model = HourBuilding
|
||||||
|
inlines = (HourBuildingLegBaseInLine,)
|
||||||
|
extra = 0
|
||||||
|
max_num = 7
|
||||||
|
fk_name = "weekpref"
|
||||||
|
verbose_name_plural = "Hour Buildings"
|
||||||
|
formfield_overrides = {
|
||||||
|
models.CharField: {"widget": TextInput(attrs={"size":"20"})},
|
||||||
|
models.TextField: {"widget": Textarea(attrs={"rows":4, "cols":35})},
|
||||||
|
}
|
||||||
|
|
||||||
|
# If user is a student deny edit permission for week past the current one
|
||||||
|
def has_change_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
return has_edit_permission(request=request, obj=obj)
|
||||||
|
|
||||||
|
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
return self.has_change_permission(request=request, obj=obj)
|
||||||
|
|
||||||
|
def has_add_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
return self.has_change_permission(request=request, obj=obj)
|
||||||
95
cntmanage/flightslot/admins/instructor_admin.py
Normal file
95
cntmanage/flightslot/admins/instructor_admin.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
from django.forms import TypedMultipleChoiceField
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.contrib import admin, messages
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
||||||
|
|
||||||
|
from import_export import fields
|
||||||
|
from import_export.admin import ImportMixin
|
||||||
|
from import_export.tmp_storages import CacheStorage
|
||||||
|
from import_export.resources import ModelResource
|
||||||
|
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
from ..models.aircrafts import AircraftTypes
|
||||||
|
from ..models.missions import MissionTypes
|
||||||
|
|
||||||
|
from ..actions.assign_profile import assign_profile
|
||||||
|
from ..actions.assign_aircraft import assign_aircraft
|
||||||
|
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
# Resource Class for Instructor data import
|
||||||
|
class InstructorResource(ModelResource):
|
||||||
|
surname = fields.Field(attribute="surname", column_name="surname")
|
||||||
|
name = fields.Field(attribute="name", column_name="name")
|
||||||
|
email = fields.Field(attribute="email", column_name="email")
|
||||||
|
phone = fields.Field(attribute="phone", column_name="phone")
|
||||||
|
|
||||||
|
# Cleanup fields before entering
|
||||||
|
def before_import_row(self, row: Dict[str, str], **kwargs) -> None:
|
||||||
|
row["name"] = SafeText("-".join(c.capitalize() for c in row["name"].split(" ")).strip())
|
||||||
|
row["surname"] = SafeText("-".join(c.capitalize() for c in row["surname"].split(" ")).strip())
|
||||||
|
row["phone"] = SafeText(row["phone"].replace(" ",""))
|
||||||
|
row["email"] = SafeText(row["email"].lower().strip())
|
||||||
|
return super().before_import_row(row, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Instructor
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = True
|
||||||
|
fields = ("surname", "name", "email", "phone", )
|
||||||
|
import_id_fields = ("surname", "name", )
|
||||||
|
|
||||||
|
# Form class to assing aircrafts to instructors
|
||||||
|
class AssignMissionForm(AdminActionForm):
|
||||||
|
mission_profiles = TypedMultipleChoiceField(choices=MissionTypes)
|
||||||
|
|
||||||
|
# Form class to assing aircrafts to instructors
|
||||||
|
class AssignAircraftForm(AdminActionForm):
|
||||||
|
aircrafts = TypedMultipleChoiceField(choices=AircraftTypes)
|
||||||
|
|
||||||
|
class InstructorAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||||
|
model = Instructor
|
||||||
|
list_display = ("surname", "name", "email", "phone", "assigned_profiles", "assigned_aircrafts", "active", )
|
||||||
|
search_fields = ("surname", "name", "phone", "email", )
|
||||||
|
readonly_fields = ("username", "password", )
|
||||||
|
actions = ("assign_aircraft", "assign_profile", )
|
||||||
|
resource_classes = [InstructorResource]
|
||||||
|
tmp_storage_class = CacheStorage
|
||||||
|
skip_admin_log = True
|
||||||
|
|
||||||
|
@admin.display(description="Password")
|
||||||
|
def password(self, obj: Instructor) -> SafeText:
|
||||||
|
return SafeText(obj.default_password())
|
||||||
|
|
||||||
|
@admin.display(description="Username")
|
||||||
|
def username(self, obj: Instructor) -> SafeText:
|
||||||
|
return SafeText(obj.default_username())
|
||||||
|
|
||||||
|
@admin.display(description="Assigned Profiles")
|
||||||
|
def assigned_profiles(self, obj: Instructor) -> SafeText:
|
||||||
|
if not obj.aircrafts:
|
||||||
|
return SafeText("")
|
||||||
|
return SafeText("/".join(mix.mtype for mix in obj.missions.distinct("mtype").order_by("mtype").all()))
|
||||||
|
|
||||||
|
@admin.display(description="Assigned Aircrafts")
|
||||||
|
def assigned_aircrafts(self, obj: Instructor) -> SafeText:
|
||||||
|
if not obj.aircrafts:
|
||||||
|
return SafeText("")
|
||||||
|
return SafeText("/".join(ac.type for ac in obj.aircrafts.distinct("type").order_by("type").all()))
|
||||||
|
|
||||||
|
@action_with_form(AssignAircraftForm, description="Assign Aircraft Type")
|
||||||
|
def assign_aircraft(self, request: HttpRequest, queryset: QuerySet[Instructor], data: Dict[str, List[AircraftTypes]]):
|
||||||
|
i: int
|
||||||
|
ac_types: List[str]
|
||||||
|
i, ac_types = assign_aircraft(queryset=queryset, data=data)
|
||||||
|
messages.success(request, f"{i} Instructors updated to {ac_types}")
|
||||||
|
|
||||||
|
@action_with_form(AssignMissionForm, description="Assign Mission Type")
|
||||||
|
def assign_profile(self, request: HttpRequest, queryset: QuerySet[Instructor], data: Dict[str, List[MissionTypes]]):
|
||||||
|
i: int
|
||||||
|
mix_types: List[str]
|
||||||
|
i, mix_types = assign_profile(queryset=queryset, data=data)
|
||||||
|
messages.success(request, f"{i} Instructors updated to {mix_types}")
|
||||||
70
cntmanage/flightslot/admins/mission_adm.py
Normal file
70
cntmanage/flightslot/admins/mission_adm.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from django.forms import TypedMultipleChoiceField
|
||||||
|
from django.contrib import admin, messages
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
||||||
|
|
||||||
|
from import_export import fields
|
||||||
|
from import_export.admin import ImportMixin
|
||||||
|
from import_export.tmp_storages import CacheStorage
|
||||||
|
from import_export.resources import ModelResource
|
||||||
|
|
||||||
|
from ..models.aircrafts import AircraftTypes
|
||||||
|
from ..models.missions import MissionProfile
|
||||||
|
|
||||||
|
from ..actions.assign_aircraft import assign_aircraft
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
# Resource Class for Student data import
|
||||||
|
class MissionProfileResource(ModelResource):
|
||||||
|
mtype = fields.Field(attribute="mtype", column_name="mtype")
|
||||||
|
mnum = fields.Field(attribute="mnum", column_name="mnum")
|
||||||
|
duration = fields.Field(attribute="duration", column_name="duration")
|
||||||
|
|
||||||
|
# Cleanup fields before entering
|
||||||
|
def before_import_row(self, row: dict[str, str | Any], **kwargs):
|
||||||
|
row["mtype"] = SafeText(row["mtype"].upper().strip())
|
||||||
|
row["mnum"] = SafeText(row["mnum"].upper().strip())
|
||||||
|
h, m, _ = row["duration"].split(":")
|
||||||
|
row["duration"] = timedelta(hours=float(h), minutes=float(m))
|
||||||
|
super().before_import_row(row, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = MissionProfile
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = True
|
||||||
|
fields = ("mtype", "mnum", "duration",)
|
||||||
|
import_id_fields = ("mtype", "mnum",)
|
||||||
|
|
||||||
|
# Form class to assing aircrafts to students
|
||||||
|
class ChangeAircraftForm(AdminActionForm):
|
||||||
|
aircrafts = TypedMultipleChoiceField(choices=AircraftTypes)
|
||||||
|
|
||||||
|
class MissionProfileAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||||
|
list_display = ("mtype", "mnum", "assigned_aircrafts", "duration", "notes", )
|
||||||
|
list_filter = ("mtype", )
|
||||||
|
actions = ("assign_aircraft", )
|
||||||
|
resource_classes = [MissionProfileResource]
|
||||||
|
tmp_storage_class = CacheStorage
|
||||||
|
skip_admin_log = True
|
||||||
|
|
||||||
|
def get_queryset(self, request: HttpRequest) -> QuerySet[MissionProfile]:
|
||||||
|
return super().get_queryset(request).order_by("mtype", "mnum")
|
||||||
|
|
||||||
|
@action_with_form(ChangeAircraftForm, description="Assign Aircraft Type")
|
||||||
|
def assign_aircraft(self, request: HttpRequest, queryset: QuerySet[MissionProfile], data: Dict[str, List[AircraftTypes]]):
|
||||||
|
i: int
|
||||||
|
ac_types: List[str]
|
||||||
|
i, ac_types = assign_aircraft(queryset=queryset, data=data)
|
||||||
|
messages.success(request, f"{i} Missions updated to {ac_types}")
|
||||||
|
|
||||||
|
@admin.display(description="Assigned Aircrafts")
|
||||||
|
def assigned_aircrafts(self, obj: MissionProfile) -> SafeText:
|
||||||
|
if not obj.aircrafts:
|
||||||
|
return SafeText("")
|
||||||
|
return SafeText("/".join(ac.markings for ac in obj.aircrafts.all()))
|
||||||
|
|
||||||
136
cntmanage/flightslot/admins/student_adm.py
Normal file
136
cntmanage/flightslot/admins/student_adm.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
from django.forms import ModelChoiceField, TypedMultipleChoiceField
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.contrib import admin, messages
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from import_export import fields
|
||||||
|
from import_export.admin import ImportMixin
|
||||||
|
from import_export.tmp_storages import CacheStorage
|
||||||
|
from import_export.resources import ModelResource
|
||||||
|
from import_export.forms import ConfirmImportForm, ImportForm
|
||||||
|
|
||||||
|
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, ActionForm, action_with_form
|
||||||
|
from admin_confirm import AdminConfirmMixin, confirm_action
|
||||||
|
|
||||||
|
from ..models.aircrafts import AircraftTypes
|
||||||
|
from ..models.courses import Course
|
||||||
|
from ..models.students import Student
|
||||||
|
|
||||||
|
from ..actions.assign_aircraft import assign_aircraft
|
||||||
|
from ..actions.send_email import send_mail_password
|
||||||
|
|
||||||
|
from ..custom.colortag import course_color
|
||||||
|
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
# Custom import form to select a course for student input
|
||||||
|
class StudentCustomConfirmImportForm(ConfirmImportForm):
|
||||||
|
course = ModelChoiceField(
|
||||||
|
queryset=Course.objects.all(),
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
# Resource Class for Student data import
|
||||||
|
class StudentResource(ModelResource):
|
||||||
|
surname = fields.Field(attribute="surname", column_name="surname")
|
||||||
|
name = fields.Field(attribute="name", column_name="name")
|
||||||
|
email = fields.Field(attribute="email", column_name="email")
|
||||||
|
phone = fields.Field(attribute="phone", column_name="phone")
|
||||||
|
|
||||||
|
# Cleanup fields before entering
|
||||||
|
def before_import_row(self, row: Dict[str, str], **kwargs) -> None:
|
||||||
|
row["name"] = SafeText("-".join(c.capitalize() for c in row["name"].split(" ")).strip())
|
||||||
|
row["surname"] = SafeText("-".join(c.capitalize() for c in row["surname"].split(" ")).strip())
|
||||||
|
row["phone"] = SafeText(row["phone"].replace(" ",""))
|
||||||
|
row["email"] = SafeText(row["email"].lower().strip())
|
||||||
|
return super().before_import_row(row, **kwargs)
|
||||||
|
|
||||||
|
# If course was addedd as a form kwasrg add it to the student after creation
|
||||||
|
def after_init_instance(self, instance: Student, new: bool, row: Dict[str, str], **kwargs: Dict[str, Any | Course]):
|
||||||
|
course = kwargs.get("course", None)
|
||||||
|
if course and isinstance(course, Course):
|
||||||
|
instance.course = course
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Student
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = True
|
||||||
|
fields = ("surname", "name", "email", "phone",)
|
||||||
|
import_id_fields = ("email", "phone",)
|
||||||
|
|
||||||
|
# Form Class for Student course change
|
||||||
|
class ChangeCourseForm(AdminActionForm):
|
||||||
|
course = ModelChoiceField(queryset=Course.objects.all().order_by("ctype", "-cnumber"))
|
||||||
|
|
||||||
|
# Form class to assing aircrafts to students
|
||||||
|
class ChangeAircraftForm(AdminActionForm):
|
||||||
|
aircrafts = TypedMultipleChoiceField(choices=AircraftTypes)
|
||||||
|
|
||||||
|
|
||||||
|
class StudentAdmin(ImportMixin, AdminConfirmMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||||
|
model = Student
|
||||||
|
list_display = ("surname", "name", "course", "course_color", "email", "phone", "username", "password", "active", )
|
||||||
|
list_filter = ("course", "active", )
|
||||||
|
search_fields = ("surname", "name", "phone", "email", )
|
||||||
|
actions = ("change_course", "deactivate_students", "change_aircraft", "send_mail", )
|
||||||
|
resource_classes = [StudentResource]
|
||||||
|
confirm_form_class = StudentCustomConfirmImportForm
|
||||||
|
tmp_storage_class = CacheStorage
|
||||||
|
skip_admin_log = True
|
||||||
|
|
||||||
|
|
||||||
|
@admin.display(description="Color")
|
||||||
|
def course_color(self, obj: Student) -> SafeText:
|
||||||
|
if not obj.course:
|
||||||
|
return SafeText("")
|
||||||
|
return course_color(obj.course.color)
|
||||||
|
|
||||||
|
@admin.display(description="Password")
|
||||||
|
def password(self, obj: Student) -> SafeText:
|
||||||
|
return SafeText(obj.default_password())
|
||||||
|
|
||||||
|
@admin.display(description="Username")
|
||||||
|
def username(self, obj: Student) -> SafeText:
|
||||||
|
return SafeText(obj.default_username())
|
||||||
|
|
||||||
|
@admin.action(description="Deactivate Students")
|
||||||
|
def deactivate_students(self, request: HttpRequest, queryset: QuerySet[Student]):
|
||||||
|
for q in queryset.all():
|
||||||
|
if q.user:
|
||||||
|
q.user.is_staff = False
|
||||||
|
q.user.save()
|
||||||
|
count: int = queryset.update(active = False)
|
||||||
|
messages.success(request, f"{count} students deactivated")
|
||||||
|
|
||||||
|
@action_with_form(ChangeCourseForm, description="Change Student Course")
|
||||||
|
def change_course(self, request: HttpRequest, queryset: QuerySet[Student], data):
|
||||||
|
course = data["course"]
|
||||||
|
count: int = queryset.update(course=course)
|
||||||
|
messages.success(request, f"{count} students updated to {course}")
|
||||||
|
|
||||||
|
@action_with_form(ChangeAircraftForm, description="Assign Aircraft Type")
|
||||||
|
def change_aircraft(self, request: HttpRequest, queryset: QuerySet[Student], data: Dict[str, List[AircraftTypes]]):
|
||||||
|
i: int
|
||||||
|
ac_types: List[str]
|
||||||
|
i, ac_types = assign_aircraft(queryset=queryset, data=data)
|
||||||
|
messages.success(request, f"{i} Students updated to {ac_types}")
|
||||||
|
|
||||||
|
@confirm_action
|
||||||
|
@admin.action(description="Send Access Credentials e-mail")
|
||||||
|
def send_mail(self, request: HttpRequest, queryset: QuerySet[Student], *args: Any) -> None:
|
||||||
|
send_mail_password(request=request, queryset=queryset)
|
||||||
|
|
||||||
|
# Return the initial form for import confirmations, request course to user
|
||||||
|
def get_confirm_form_initial(self, request: HttpRequest, import_form) -> Dict[str, Any]:
|
||||||
|
initial: Dict[str, Any] = super().get_confirm_form_initial(request, import_form)
|
||||||
|
if import_form and hasattr(import_form.cleaned_data, "course"):
|
||||||
|
course: Course = import_form.cleaned_data["course"]
|
||||||
|
initial["course"] = course.id
|
||||||
|
return initial
|
||||||
|
|
||||||
|
# Add course to import form kwargs to be used by resource to associate course with all imported students
|
||||||
|
def get_import_data_kwargs(self, request: HttpRequest, *args, **kwargs) -> Dict[str, Any]:
|
||||||
|
form: ImportForm | None = kwargs.get("form", None)
|
||||||
|
if form and hasattr(form, "cleaned_data"):
|
||||||
|
kwargs["course"] = form.cleaned_data.get("course", None)
|
||||||
|
return kwargs
|
||||||
62
cntmanage/flightslot/admins/training_adm.py
Normal file
62
cntmanage/flightslot/admins/training_adm.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import nested_admin
|
||||||
|
from django import forms
|
||||||
|
from django.db.models import CharField, TextField
|
||||||
|
from django.db.models.query_utils import Q
|
||||||
|
from django.db.models.fields.related import ForeignKey
|
||||||
|
from django.forms import TextInput, Textarea
|
||||||
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
from ..models.courses import Course, CourseTypes
|
||||||
|
from ..models.students import Student
|
||||||
|
from ..models.missions import Training, MissionTypes, MissionProfile
|
||||||
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
|
from ..custom.student_permissions import has_edit_permission
|
||||||
|
|
||||||
|
class TrainingForm(forms.ModelForm):
|
||||||
|
model=Training
|
||||||
|
|
||||||
|
class TrainingInLIne(nested_admin.NestedTabularInline):
|
||||||
|
model = Training
|
||||||
|
form = TrainingForm
|
||||||
|
extra = 0
|
||||||
|
fk_name = 'weekpref'
|
||||||
|
verbose_name_plural = "Training Missions"
|
||||||
|
max_num = 7
|
||||||
|
|
||||||
|
formfield_overrides = {
|
||||||
|
CharField: {'widget': TextInput(attrs={'size':'20'})},
|
||||||
|
TextField: {'widget': Textarea(attrs={'rows':4, 'cols':35})},
|
||||||
|
}
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field: ForeignKey, request: HttpRequest, **kwargs):
|
||||||
|
# modify entries for "mission" field, show only types compatible with student course
|
||||||
|
if not hasattr(request.user, "student") or not hasattr(request.user.student, "course"):
|
||||||
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
course: Course = request.user.student.course
|
||||||
|
if db_field.name == "mission":
|
||||||
|
match course.ctype:
|
||||||
|
case CourseTypes.PPL:
|
||||||
|
kwargs["queryset"] = MissionProfile.objects.filter(mtype=MissionTypes.PPL)
|
||||||
|
case CourseTypes.ATPL:
|
||||||
|
q: Q = Q(mtype=MissionTypes.IR) | \
|
||||||
|
Q(mtype=MissionTypes.MEP) | \
|
||||||
|
Q(mtype=MissionTypes.MEP_IR) | \
|
||||||
|
Q(mtype=MissionTypes.CPL) | \
|
||||||
|
Q(mtype=MissionTypes.CHK)
|
||||||
|
kwargs["queryset"] = MissionProfile.objects.filter(q).order_by("id")
|
||||||
|
case CourseTypes.FI:
|
||||||
|
kwargs["queryset"] = MissionProfile.objects.filter(mtype=MissionTypes.FI).order_by("mnum")
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
|
# If user is a student deny edit permission for week past the current one
|
||||||
|
def has_change_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
return has_edit_permission(request=request, obj=obj)
|
||||||
|
|
||||||
|
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
return self.has_change_permission(request=request, obj=obj)
|
||||||
|
|
||||||
|
def has_add_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
return self.has_change_permission(request=request, obj=obj)
|
||||||
135
cntmanage/flightslot/admins/weekpref_adm.py
Normal file
135
cntmanage/flightslot/admins/weekpref_adm.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import nested_admin
|
||||||
|
|
||||||
|
from django.forms import Form
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.contrib import admin, messages
|
||||||
|
from django.utils.translation import ngettext
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from ..models.courses import CourseTypes
|
||||||
|
from ..models.students import Student
|
||||||
|
from ..models.missions import Training
|
||||||
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
|
from .training_adm import TrainingInLIne
|
||||||
|
from .hourbuilding_adm import HourBuildingInLine
|
||||||
|
|
||||||
|
from ..custom.colortag import course_color
|
||||||
|
from ..custom.student_permissions import has_edit_permission, has_week_add_permission
|
||||||
|
from ..actions.exportweek import export_selected
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
|
class WeekPreferenceAdmin(nested_admin.NestedPolymorphicModelAdmin):
|
||||||
|
list_display = ("week", "student__surname", "student__name", "student__course", "course_color", "student_brief_mix", )
|
||||||
|
list_filter = ("week", "student__course", )
|
||||||
|
search_fields = ("student__surname","student__name", )
|
||||||
|
actions = ("export", )
|
||||||
|
|
||||||
|
@admin.action(description="Export Selected Preferences")
|
||||||
|
def export(self, request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse | None:
|
||||||
|
if queryset.count() == 0:
|
||||||
|
return None
|
||||||
|
self.message_user(request, ngettext("Exporting %d row", "Exporting %d rows", queryset.count()) % queryset.count(), messages.SUCCESS)
|
||||||
|
return export_selected(request=request, queryset=queryset)
|
||||||
|
|
||||||
|
@admin.display(description="Mission Count")
|
||||||
|
def student_brief_mix(self, obj: WeekPreference) -> SafeText:
|
||||||
|
if not obj.student.course:
|
||||||
|
return SafeText("")
|
||||||
|
return SafeText(f"{Training.objects.filter(weekpref = obj.id).count()}")
|
||||||
|
|
||||||
|
@admin.display(description="Color")
|
||||||
|
def course_color(self, obj: WeekPreference) -> SafeText:
|
||||||
|
if not obj.student.course:
|
||||||
|
return SafeText("")
|
||||||
|
return course_color(obj.student.course.color)
|
||||||
|
|
||||||
|
# If a user is registered as student hide filters
|
||||||
|
def get_list_filter(self, request: HttpRequest) -> List[str]:
|
||||||
|
list_filter = super().get_list_filter(request)
|
||||||
|
if hasattr(request.user, "student"):
|
||||||
|
return []
|
||||||
|
return list_filter
|
||||||
|
|
||||||
|
# Get available mission or HB depending on student course
|
||||||
|
def get_inline_instances(self, request: HttpRequest, obj: WeekPreference | None = None):
|
||||||
|
if hasattr(request.user, "student"):
|
||||||
|
student: Student = request.user.student
|
||||||
|
# Only ATPL students are able to book HourBuilding Missions
|
||||||
|
if student.course and student.course.ctype in (CourseTypes.ATPL, CourseTypes.DISTANCE):
|
||||||
|
return (
|
||||||
|
TrainingInLIne(self.model, self.admin_site),
|
||||||
|
HourBuildingInLine(self.model, self.admin_site),
|
||||||
|
)
|
||||||
|
# All other courses have only training
|
||||||
|
return (TrainingInLIne(self.model, self.admin_site), )
|
||||||
|
else:
|
||||||
|
return (
|
||||||
|
TrainingInLIne(self.model, self.admin_site),
|
||||||
|
HourBuildingInLine(self.model, self.admin_site),
|
||||||
|
)
|
||||||
|
|
||||||
|
# If a user is registered as student do not show actions
|
||||||
|
def get_actions(self, request: HttpRequest) -> Dict[str, Any]:
|
||||||
|
actions = super().get_actions(request)
|
||||||
|
if hasattr(request.user, "student"):
|
||||||
|
return {}
|
||||||
|
return actions
|
||||||
|
|
||||||
|
# If a user is registered as student show only their preferences
|
||||||
|
def get_queryset(self, request: HttpRequest) -> QuerySet[WeekPreference]:
|
||||||
|
qs = super().get_queryset(request).order_by("-week", "-student__course", "student__surname", "student__name")
|
||||||
|
if hasattr(request.user, "student"):
|
||||||
|
return qs.filter(student=request.user.student)
|
||||||
|
# If admin show everything
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def get_form(self, request: HttpRequest, obj: WeekPreference | None = None, **kwargs: Dict[str, Any]) -> Form:
|
||||||
|
form: Form = super().get_form(request, obj, **kwargs)
|
||||||
|
current_week = date.today().isocalendar().week
|
||||||
|
|
||||||
|
# If form contains the week field
|
||||||
|
if "week" in form.base_fields:
|
||||||
|
# Set default value as current week
|
||||||
|
form.base_fields["week"].initial = current_week
|
||||||
|
|
||||||
|
# If student is current user making request
|
||||||
|
if hasattr(request.user, "student"):
|
||||||
|
student: Student = request.user.student
|
||||||
|
if "student" in form.base_fields:
|
||||||
|
form.base_fields["student"].initial = student
|
||||||
|
form.base_fields["student"].disabled = True
|
||||||
|
form.base_fields["week"].disabled = True # student cannot change week
|
||||||
|
return form
|
||||||
|
|
||||||
|
# If user is a student deny edit permission for week past the current one
|
||||||
|
def has_change_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
return has_edit_permission(request=request, obj=obj)
|
||||||
|
|
||||||
|
# If user is a student deny edit permission for week past the current one
|
||||||
|
def has_add_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
return has_week_add_permission(request=request) and has_edit_permission(request=request, obj=obj)
|
||||||
|
|
||||||
|
# If user is a student deny edit permission for week past the current one
|
||||||
|
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None)-> bool:
|
||||||
|
return self.has_change_permission(request, obj)
|
||||||
|
|
||||||
|
def changeform_view(self, request: HttpRequest, object_id: int | None = None, form_url: str = "", extra_context = None):
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
if hasattr(request.user, "student") and object_id:
|
||||||
|
weekpref = WeekPreference.objects.get(id=object_id)
|
||||||
|
if not has_edit_permission(request=request, obj=weekpref):
|
||||||
|
extra_context["show_save"] = False
|
||||||
|
extra_context["show_save_and_continue"] = False
|
||||||
|
extra_context["show_save_and_add_another"] = False
|
||||||
|
extra_context["show_delete"] = False
|
||||||
|
return super().changeform_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
def save_model(self, request: HttpRequest, obj: WeekPreference, form: Form, change: bool):
|
||||||
|
# Imposta automaticamente lo studente se non è già valorizzato
|
||||||
|
if hasattr(request.user, "student") and not obj.student_id:
|
||||||
|
obj.student = request.user.student
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class FlightslotConfig(AppConfig):
|
class FlightslotConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'flightslot'
|
name = 'flightslot'
|
||||||
18
cntmanage/flightslot/custom/colortag.py
Normal file
18
cntmanage/flightslot/custom/colortag.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from colorfield.fields import ColorField
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
def course_color(color: ColorField | None) -> SafeText:
|
||||||
|
if not color:
|
||||||
|
return format_html("")
|
||||||
|
return format_html(
|
||||||
|
'<div style="background-color: {}; \
|
||||||
|
width: 20px; height: 20px; \
|
||||||
|
border-radius: 25%; \
|
||||||
|
border-color: gray; \
|
||||||
|
border-style: solid; \
|
||||||
|
border-width: 1px; \
|
||||||
|
display: inline-block; \
|
||||||
|
"></div>',
|
||||||
|
color
|
||||||
|
)
|
||||||
21
cntmanage/flightslot/custom/student_permissions.py
Normal file
21
cntmanage/flightslot/custom/student_permissions.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from ..models.students import Student
|
||||||
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
def has_week_add_permission(request: HttpRequest):
|
||||||
|
if hasattr(request.user, 'student'):
|
||||||
|
student: Student = request.user.student
|
||||||
|
current_week: int = date.today().isocalendar().week
|
||||||
|
return student.active and not WeekPreference.objects.filter(student_id=student.id, week=current_week).count()
|
||||||
|
return True
|
||||||
|
|
||||||
|
# allow add, modify, delete depending on a set of requirements
|
||||||
|
def has_edit_permission(request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
if hasattr(request.user, 'student'):
|
||||||
|
current_week: int = date.today().isocalendar().week
|
||||||
|
if obj and current_week > obj.week:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
16
cntmanage/flightslot/middleware.py
Normal file
16
cntmanage/flightslot/middleware.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
class RedirectNonSuperuserFromAdminMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request: HttpRequest):
|
||||||
|
# Se l'utente è loggato, non è superuser e prova ad andare in /admin/... o qualsiasi altro path
|
||||||
|
if hasattr(request, "user") and not request.user.is_superuser:
|
||||||
|
if hasattr(request.user, "student") and not "/user/" in request.path:
|
||||||
|
return redirect("/user/") # redirect automatico
|
||||||
|
elif hasattr(request.user, "instructor") and not "/staff/" in request.path:
|
||||||
|
return redirect("/staff/") # redirect automatico
|
||||||
|
return self.get_response(request)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-10-20 09:27
|
||||||
|
|
||||||
|
import django.db.models.functions.datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0006_course_student_active_student_course'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='course',
|
||||||
|
name='year',
|
||||||
|
field=models.PositiveSmallIntegerField(db_default=django.db.models.functions.datetime.ExtractYear(django.db.models.functions.datetime.Now()), editable=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuilding',
|
||||||
|
name='notes',
|
||||||
|
field=models.TextField(blank=True, max_length=140, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='missionprofile',
|
||||||
|
name='notes',
|
||||||
|
field=models.TextField(blank=True, max_length=140, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='training',
|
||||||
|
name='notes',
|
||||||
|
field=models.TextField(blank=True, max_length=140, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-10-20 10:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0007_course_year_alter_hourbuilding_notes_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='hourbuilding',
|
||||||
|
name='friday',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='training',
|
||||||
|
name='friday',
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-11 17:45
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.db.models.expressions
|
||||||
|
import django.db.models.functions.datetime
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0008_hourbuilding_friday_training_friday'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='missionprofile',
|
||||||
|
name='mnum',
|
||||||
|
field=models.PositiveSmallIntegerField(default=0, null=True, verbose_name='Mission Number'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='missionprofile',
|
||||||
|
name='mtype',
|
||||||
|
field=models.CharField(choices=[('OTHER', 'OTHER'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='PPL', verbose_name='Mission Type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='student',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.student', verbose_name='Student Selection'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='week',
|
||||||
|
field=models.PositiveSmallIntegerField(auto_created=True, db_index=True, default=django.db.models.expressions.CombinedExpression(django.db.models.functions.datetime.ExtractWeek(django.db.models.functions.datetime.Now()), '+', models.Value(1)), verbose_name='Week Number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-11 20:31
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0009_student_user_alter_missionprofile_mnum_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuildingleg',
|
||||||
|
name='time',
|
||||||
|
field=models.DurationField(default=models.DurationField(default=datetime.timedelta(0))),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-11 20:33
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0010_alter_hourbuildingleg_time'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuildingleg',
|
||||||
|
name='time',
|
||||||
|
field=models.DurationField(default=datetime.timedelta(seconds=3600)),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-12 09:47
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0011_alter_hourbuildingleg_time'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='cnumber',
|
||||||
|
field=models.PositiveSmallIntegerField(default=2025),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='year',
|
||||||
|
field=models.PositiveSmallIntegerField(db_default=2025, editable=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='missionprofile',
|
||||||
|
name='duration',
|
||||||
|
field=models.DurationField(default=datetime.timedelta(seconds=3600)),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='week',
|
||||||
|
field=models.PositiveSmallIntegerField(auto_created=True, db_default=46, db_index=True, verbose_name='Week Number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-12 10:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0012_alter_course_cnumber_alter_course_year_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='course',
|
||||||
|
name='color',
|
||||||
|
field=models.CharField(choices=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')], default='#ffffff'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='ctype',
|
||||||
|
field=models.CharField(choices=[('FI', 'FI'), ('PPL', 'PPL'), ('ATPL', 'ATPL'), ('DL', 'DISTANCE'), ('OTHER', 'OTHER')]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='year',
|
||||||
|
field=models.PositiveSmallIntegerField(default=2025),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
cntmanage/flightslot/migrations/0014_student_phone.py
Normal file
18
cntmanage/flightslot/migrations/0014_student_phone.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-12 10:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0013_course_color_alter_course_ctype_alter_course_year'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(max_length=16, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
cntmanage/flightslot/migrations/0015_alter_course_color.py
Normal file
19
cntmanage/flightslot/migrations/0015_alter_course_color.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-12 10:31
|
||||||
|
|
||||||
|
import colorfield.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0014_student_phone'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='color',
|
||||||
|
field=colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')]),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-14 14:42
|
||||||
|
|
||||||
|
import colorfield.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0015_alter_course_color'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='cnumber',
|
||||||
|
field=models.PositiveSmallIntegerField(default=2025, verbose_name='Course Number'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='color',
|
||||||
|
field=colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')], verbose_name='Binder Color'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='ctype',
|
||||||
|
field=models.CharField(choices=[('FI', 'FI'), ('PPL', 'PPL'), ('ATPL', 'ATPL'), ('DL', 'DISTANCE'), ('OTHER', 'OTHER')], verbose_name='Course Type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='year',
|
||||||
|
field=models.PositiveSmallIntegerField(default=2025, verbose_name='Year'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuilding',
|
||||||
|
name='aircraft',
|
||||||
|
field=models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010')]),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-18 21:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0016_alter_course_cnumber_alter_course_color_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='missionprofile',
|
||||||
|
name='mtype',
|
||||||
|
field=models.CharField(choices=[('OTHER', 'OTHER'), ('PPL', 'PPL'), ('IR', 'IR'), ('MEP', 'MEP'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='PPL', verbose_name='Mission Type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='week',
|
||||||
|
field=models.PositiveSmallIntegerField(auto_created=True, db_default=47, db_index=True, verbose_name='Week Number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-21 11:20
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('flightslot', '0017_alter_missionprofile_mtype_alter_weekpreference_week'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HourBuildingLegBase',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('time', models.DurationField(default=datetime.timedelta(seconds=3600))),
|
||||||
|
('hb', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.hourbuilding')),
|
||||||
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HourBuildingLegFlight',
|
||||||
|
fields=[
|
||||||
|
('hourbuildinglegbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='flightslot.hourbuildinglegbase')),
|
||||||
|
('departure', models.CharField(default='LILV', max_length=4)),
|
||||||
|
('destination', models.CharField(default='LILV', max_length=4)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('flightslot.hourbuildinglegbase',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HourBuildingLegStop',
|
||||||
|
fields=[
|
||||||
|
('hourbuildinglegbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='flightslot.hourbuildinglegbase')),
|
||||||
|
('location', models.CharField(default='LILV', max_length=4)),
|
||||||
|
('refuel', models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
'base_manager_name': 'objects',
|
||||||
|
},
|
||||||
|
bases=('flightslot.hourbuildinglegbase',),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='HourBuildingLeg',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-21 16:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0018_hourbuildinglegbase_hourbuildinglegflight_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='hourbuildinglegstop',
|
||||||
|
name='location',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='hourbuildinglegflight',
|
||||||
|
name='pax',
|
||||||
|
field=models.CharField(max_length=16, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuildinglegbase',
|
||||||
|
name='time',
|
||||||
|
field=models.DurationField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuildinglegflight',
|
||||||
|
name='departure',
|
||||||
|
field=models.CharField(max_length=4),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuildinglegflight',
|
||||||
|
name='destination',
|
||||||
|
field=models.CharField(max_length=4),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-25 11:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0019_remove_hourbuildinglegstop_location_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='hourbuildinglegbase',
|
||||||
|
options={'base_manager_name': 'objects', 'verbose_name': 'Flight Leg or Stop', 'verbose_name_plural': 'Flight Legs or Stops'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='hourbuildinglegflight',
|
||||||
|
options={'base_manager_name': 'objects', 'verbose_name': 'Flight leg', 'verbose_name_plural': 'Flight legs'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='hourbuildinglegstop',
|
||||||
|
options={'base_manager_name': 'objects', 'verbose_name': 'Stop', 'verbose_name_plural': 'Stops'},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuildinglegflight',
|
||||||
|
name='pax',
|
||||||
|
field=models.CharField(blank=True, max_length=16, null=True, verbose_name='Pax (optional)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='week',
|
||||||
|
field=models.PositiveSmallIntegerField(auto_created=True, db_default=48, db_index=True, verbose_name='Week Number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-27 10:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0020_alter_hourbuildinglegbase_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuildinglegstop',
|
||||||
|
name='refuel',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Stop for Refuelling'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-27 13:34
|
||||||
|
|
||||||
|
import colorfield.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0021_alter_hourbuildinglegstop_refuel'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Aircraft',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('type', models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('PA34', 'Piper PA34'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010'), ('FSTD', 'Alsim ALX40')], max_length=4)),
|
||||||
|
('markings', models.CharField(max_length=6)),
|
||||||
|
('complex', models.BooleanField(default=False)),
|
||||||
|
('avail_hours', models.DurationField(null=True, verbose_name='Time until maintenance')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='color',
|
||||||
|
field=colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=[('#bfbfbf', 'GREY'), ('#ff0000', 'RED'), ('#ffc000', 'ORANGE'), ('#ffff00', 'YELLOW'), ('#92d050', 'GREEN'), ('#00b0f0', 'CYAN'), ('#b1a0c7', 'MAGENTA'), ('#fabcfb', 'PINK'), ('#f27ae4', 'VIOLET')], verbose_name='Binder Color'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuilding',
|
||||||
|
name='aircraft',
|
||||||
|
field=models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('PA34', 'Piper PA34'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010'), ('FSTD', 'Alsim ALX40')]),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='student',
|
||||||
|
name='aircrafts',
|
||||||
|
field=models.ManyToManyField(to='flightslot.aircraft'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-28 09:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0022_aircraft_alter_course_color_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='missionprofile',
|
||||||
|
name='aircrafts',
|
||||||
|
field=models.ManyToManyField(to='flightslot.aircraft'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-11-28 10:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0023_missionprofile_aircrafts'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='missionprofile',
|
||||||
|
name='mtype',
|
||||||
|
field=models.CharField(choices=[('OTHER', 'OTHER'), ('CHK', 'CHK_6M'), ('PPL', 'PPL'), ('IR', 'IR'), ('MEP', 'MEP'), ('MEP_IR', 'MEP_IR'), ('CPL', 'CPL'), ('UPRT', 'UPRT'), ('FI', 'FI'), ('PC', 'PC')], default='PPL', verbose_name='Mission Type'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-01 12:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0024_alter_missionprofile_mtype'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='aircraft',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('PA34', 'Piper PA34'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010'), ('CP10', 'Cap 10'), ('FSTD', 'Alsim ALX40')], max_length=4),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuilding',
|
||||||
|
name='aircraft',
|
||||||
|
field=models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('PA34', 'Piper PA34'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010'), ('CP10', 'Cap 10'), ('FSTD', 'Alsim ALX40')]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='week',
|
||||||
|
field=models.PositiveSmallIntegerField(auto_created=True, db_default=49, db_index=True, verbose_name='Week Number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-01 17:27
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0025_alter_aircraft_type_alter_hourbuilding_aircraft_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='student',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(db_index=True, max_length=254, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='student',
|
||||||
|
name='phone',
|
||||||
|
field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None, unique=True),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Instructor',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('email', models.EmailField(db_index=True, max_length=254)),
|
||||||
|
('phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None, unique=True)),
|
||||||
|
('name', models.CharField(max_length=32)),
|
||||||
|
('surname', models.CharField(max_length=32)),
|
||||||
|
('active', models.BooleanField(default=True)),
|
||||||
|
('aircrafts', models.ManyToManyField(to='flightslot.aircraft')),
|
||||||
|
('missions', models.ManyToManyField(to='flightslot.missionprofile')),
|
||||||
|
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-02 10:02
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0026_alter_student_email_alter_student_phone_instructor'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='student',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.student', verbose_name='Student Selection'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Availability',
|
||||||
|
fields=[
|
||||||
|
('week', models.PositiveSmallIntegerField(auto_created=True, db_default=49, db_index=True, verbose_name='Week Number')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('monday', models.BooleanField(default=True)),
|
||||||
|
('tuesday', models.BooleanField(default=True)),
|
||||||
|
('wednesday', models.BooleanField(default=True)),
|
||||||
|
('thursday', models.BooleanField(default=True)),
|
||||||
|
('friday', models.BooleanField(default=True)),
|
||||||
|
('saturday', models.BooleanField(default=True)),
|
||||||
|
('sunday', models.BooleanField(default=True)),
|
||||||
|
('hours', models.DurationField(null=True, verbose_name='Available hours')),
|
||||||
|
('notes', models.TextField(blank=True, max_length=140, null=True)),
|
||||||
|
('instructor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.instructor', verbose_name='Instructor Selection')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-02 10:59
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0027_alter_weekpreference_student_availability'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='availability',
|
||||||
|
options={'verbose_name': 'Instructor Availability', 'verbose_name_plural': 'Instructor Availabilities'},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
cntmanage/flightslot/migrations/0029_alter_course_ctype.py
Normal file
18
cntmanage/flightslot/migrations/0029_alter_course_ctype.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-05 16:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0028_alter_availability_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='course',
|
||||||
|
name='ctype',
|
||||||
|
field=models.CharField(choices=[('FI', 'FI'), ('PPL', 'PPL'), ('ATPL', 'ATPL'), ('DL', 'DISTANCE'), ('DL_VOLO', 'DISTANCE_VOLO'), ('OTHER', 'OTHER')], verbose_name='Course Type'),
|
||||||
|
),
|
||||||
|
]
|
||||||
50
cntmanage/flightslot/models/aircrafts.py
Normal file
50
cntmanage/flightslot/models/aircrafts.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class AircraftTypes(models.TextChoices):
|
||||||
|
C152 = "C152", _("Cessna 152")
|
||||||
|
P208 = "P208", _("Tecnam P2008")
|
||||||
|
PA28 = "PA28", _("Piper PA28R")
|
||||||
|
PA34 = "PA34", _("Piper PA34")
|
||||||
|
C182 = "C182", _("Cessna 182Q")
|
||||||
|
P210 = "TWEN", _("Tecnam P2010")
|
||||||
|
CP10 = "CP10", _("Cap 10")
|
||||||
|
ALX40 = "FSTD", _("Alsim ALX40")
|
||||||
|
|
||||||
|
class Aircraft(models.Model):
|
||||||
|
id = models.AutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
type = models.CharField(
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
max_length=4, # ICAO naming of aircraft,
|
||||||
|
choices=AircraftTypes
|
||||||
|
)
|
||||||
|
|
||||||
|
markings = models.CharField(
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
max_length=6
|
||||||
|
)
|
||||||
|
|
||||||
|
complex = models.BooleanField(
|
||||||
|
null=False,
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
avail_hours = models.DurationField(
|
||||||
|
null=True,
|
||||||
|
verbose_name=_("Time until maintenance")
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.type} ({self.markings})"
|
||||||
|
|
||||||
|
# Insert dash between first and rest, I-OASM
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.markings = self.markings.upper()
|
||||||
|
if not "-" in self.markings:
|
||||||
|
self.markings = self.markings[0] + "-" + self.markings[1:]
|
||||||
|
super().save(*args, **kwargs)
|
||||||
79
cntmanage/flightslot/models/availabilities.py
Normal file
79
cntmanage/flightslot/models/availabilities.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from django.db import models
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
|
||||||
|
class Availability(models.Model):
|
||||||
|
id = models.BigAutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
week = models.PositiveSmallIntegerField(
|
||||||
|
null=False,
|
||||||
|
db_index=True,
|
||||||
|
db_default=date.today().isocalendar().week,
|
||||||
|
auto_created=True,
|
||||||
|
verbose_name="Week Number"
|
||||||
|
)
|
||||||
|
|
||||||
|
instructor = models.ForeignKey(
|
||||||
|
Instructor,
|
||||||
|
null=False,
|
||||||
|
db_index=True,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name="Instructor Selection"
|
||||||
|
)
|
||||||
|
|
||||||
|
monday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
tuesday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
wednesday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
thursday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
friday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
saturday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
sunday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
hours = models.DurationField(
|
||||||
|
null=True,
|
||||||
|
verbose_name="Available hours"
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = models.TextField(
|
||||||
|
max_length=140,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta():
|
||||||
|
verbose_name = "Instructor Availability"
|
||||||
|
verbose_name_plural = "Instructor Availabilities"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Week {self.week} - {self.instructor.surname} {self.instructor.name[0]}."
|
||||||
|
|
||||||
57
cntmanage/flightslot/models/courses.py
Normal file
57
cntmanage/flightslot/models/courses.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import models
|
||||||
|
from datetime import date
|
||||||
|
from colorfield.fields import ColorField
|
||||||
|
|
||||||
|
class CourseTypes(models.TextChoices):
|
||||||
|
FI = "FI", _("FI")
|
||||||
|
PPL = "PPL", _("PPL")
|
||||||
|
ATPL = "ATPL", _("ATPL")
|
||||||
|
DISTANCE = "DL", _("DISTANCE")
|
||||||
|
DISTANCE_VOLO = "DL_VOLO", _("DISTANCE_VOLO")
|
||||||
|
OTHER = "OTHER",_("OTHER")
|
||||||
|
|
||||||
|
class Course(models.Model):
|
||||||
|
# Add colors according to table from Alessia
|
||||||
|
COLOR_PALETTE = [
|
||||||
|
("#bfbfbf","GREY"),
|
||||||
|
("#ff0000", "RED"),
|
||||||
|
("#ffc000", "ORANGE"),
|
||||||
|
("#ffff00", "YELLOW"),
|
||||||
|
("#92d050", "GREEN"),
|
||||||
|
("#00b0f0", "CYAN"),
|
||||||
|
("#b1a0c7", "MAGENTA"),
|
||||||
|
("#fabcfb", "PINK"),
|
||||||
|
("#f27ae4", "VIOLET"),
|
||||||
|
]
|
||||||
|
|
||||||
|
id = models.AutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
ctype = models.CharField(
|
||||||
|
null=False,
|
||||||
|
choices=CourseTypes,
|
||||||
|
verbose_name=_("Course Type")
|
||||||
|
)
|
||||||
|
|
||||||
|
cnumber = models.PositiveSmallIntegerField(
|
||||||
|
null=False,
|
||||||
|
default=date.today().year,
|
||||||
|
verbose_name=_("Course Number")
|
||||||
|
)
|
||||||
|
|
||||||
|
year = models.PositiveSmallIntegerField(
|
||||||
|
null=False,
|
||||||
|
default=date.today().year,
|
||||||
|
verbose_name=_("Year")
|
||||||
|
)
|
||||||
|
|
||||||
|
color = ColorField (
|
||||||
|
verbose_name=_("Binder Color"),
|
||||||
|
samples=COLOR_PALETTE
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.ctype}-{self.cnumber}"
|
||||||
|
|
||||||
142
cntmanage/flightslot/models/hourbuildings.py
Normal file
142
cntmanage/flightslot/models/hourbuildings.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
|
from ..models.weekpref import WeekPreference
|
||||||
|
from ..models.aircrafts import AircraftTypes
|
||||||
|
|
||||||
|
class HourBuilding(models.Model):
|
||||||
|
id = models.BigAutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
weekpref = models.ForeignKey(
|
||||||
|
WeekPreference,
|
||||||
|
null=False,
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
aircraft = models.CharField(
|
||||||
|
null=False,
|
||||||
|
choices=AircraftTypes
|
||||||
|
)
|
||||||
|
|
||||||
|
monday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
tuesday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
wednesday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
thursday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
friday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
saturday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
sunday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = models.TextField(
|
||||||
|
max_length=140,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Hour Building: {self.aircraft}"
|
||||||
|
|
||||||
|
class HourBuildingLegBase(PolymorphicModel):
|
||||||
|
id = models.BigAutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
hb = models.ForeignKey(
|
||||||
|
HourBuilding,
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
time = models.DurationField(
|
||||||
|
null=False,
|
||||||
|
blank=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Change displayed name in the inline form
|
||||||
|
class Meta(PolymorphicModel.Meta):
|
||||||
|
verbose_name = "Flight Leg or Stop"
|
||||||
|
verbose_name_plural = "Flight Legs or Stops"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Hour Building Leg"
|
||||||
|
|
||||||
|
class HourBuildingLegFlight(HourBuildingLegBase):
|
||||||
|
departure = models.CharField(
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
max_length=4
|
||||||
|
)
|
||||||
|
|
||||||
|
destination = models.CharField(
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
max_length=4
|
||||||
|
)
|
||||||
|
|
||||||
|
pax = models.CharField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
max_length=16,
|
||||||
|
verbose_name="Pax (optional)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Change displayed name in the inline form
|
||||||
|
class Meta(HourBuildingLegBase.Meta):
|
||||||
|
verbose_name = "Flight leg"
|
||||||
|
verbose_name_plural = "Flight legs"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.departure = self.departure.upper().strip()
|
||||||
|
self.destination = self.destination.upper().strip()
|
||||||
|
if self.pax:
|
||||||
|
self.pax = " ".join(c.capitalize() for c in self.pax.split())
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.departure} -> {self.destination}"
|
||||||
|
|
||||||
|
class HourBuildingLegStop(HourBuildingLegBase):
|
||||||
|
|
||||||
|
refuel = models.BooleanField (
|
||||||
|
null=False,
|
||||||
|
default=False,
|
||||||
|
verbose_name="Stop for Refuelling"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Change displayed name in the inline form
|
||||||
|
class Meta(HourBuildingLegBase.Meta):
|
||||||
|
verbose_name = "Stop"
|
||||||
|
verbose_name_plural = "Stops"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Refuel" if self.refuel else f"No Refuel"
|
||||||
|
|
||||||
99
cntmanage/flightslot/models/instructors.py
Normal file
99
cntmanage/flightslot/models/instructors.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
|
from phonenumber_field import modelfields
|
||||||
|
|
||||||
|
from ..models.missions import MissionProfile
|
||||||
|
from ..models.aircrafts import Aircraft
|
||||||
|
|
||||||
|
class Instructor(models.Model):
|
||||||
|
id = models.AutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
email = models.EmailField(
|
||||||
|
null=False,
|
||||||
|
db_index=True
|
||||||
|
)
|
||||||
|
|
||||||
|
phone = modelfields.PhoneNumberField(
|
||||||
|
null=True,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
max_length=32
|
||||||
|
)
|
||||||
|
|
||||||
|
surname = models.CharField(
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
max_length=32
|
||||||
|
)
|
||||||
|
|
||||||
|
active = models.BooleanField(
|
||||||
|
null=False,
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
aircrafts = models.ManyToManyField(
|
||||||
|
Aircraft
|
||||||
|
)
|
||||||
|
|
||||||
|
missions = models.ManyToManyField(
|
||||||
|
MissionProfile
|
||||||
|
)
|
||||||
|
|
||||||
|
user = models.OneToOneField(
|
||||||
|
User,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def default_password(self) -> str: # Maximum 4 digits for passowrd
|
||||||
|
if self.pk:
|
||||||
|
return f"{self.name.lower()[0]}{self.surname.lower()}{self.id % 10000}"
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def default_username(self) -> str:
|
||||||
|
if self.pk and self.user:
|
||||||
|
return self.user.username
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Override save method to add user for login upon Student creation
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
creating: bool = self.pk is None
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
if creating and not self.user:
|
||||||
|
username: str = f"{self.name.lower()}.{self.surname.lower()}"
|
||||||
|
# Avoid username conflict with progressive number
|
||||||
|
base_username = username
|
||||||
|
counter: int = 1
|
||||||
|
while User.objects.filter(username=username).exists():
|
||||||
|
username = f"{base_username}{counter}"
|
||||||
|
counter += 1
|
||||||
|
# Create user
|
||||||
|
user: User = User.objects.create_user(
|
||||||
|
first_name=self.name.capitalize(),
|
||||||
|
last_name=self.surname.capitalize(),
|
||||||
|
username=username,
|
||||||
|
email=self.email,
|
||||||
|
password=self.default_password(),
|
||||||
|
is_staff=True # allows access to admin page
|
||||||
|
)
|
||||||
|
|
||||||
|
instructor_group, _ = Group.objects.get_or_create(name="InstructorGroup")
|
||||||
|
user.groups.add(instructor_group)
|
||||||
|
self.user = user
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.pk:
|
||||||
|
return f"{self.surname} {self.name[0]}."
|
||||||
|
else:
|
||||||
|
return "New Instructor"
|
||||||
116
cntmanage/flightslot/models/missions.py
Normal file
116
cntmanage/flightslot/models/missions.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import models
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from ..models.weekpref import WeekPreference
|
||||||
|
from ..models.aircrafts import Aircraft
|
||||||
|
|
||||||
|
class MissionTypes(models.TextChoices):
|
||||||
|
OTHER = "OTHER", _("OTHER")
|
||||||
|
CHK = "CHK", _("CHK_6M")
|
||||||
|
PPL = "PPL", _("PPL")
|
||||||
|
IR = "IR", _("IR")
|
||||||
|
MEP = "MEP", _("MEP")
|
||||||
|
MEP_IR = "MEP_IR", _("MEP_IR")
|
||||||
|
CPL = "CPL", _("CPL")
|
||||||
|
UPRT = "UPRT", _("UPRT")
|
||||||
|
FI = "FI", _("FI")
|
||||||
|
PC = "PC", _("PC")
|
||||||
|
|
||||||
|
class MissionProfile(models.Model):
|
||||||
|
id = models.AutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
mtype = models.CharField(
|
||||||
|
null=False,
|
||||||
|
default=MissionTypes.PPL,
|
||||||
|
choices=MissionTypes,
|
||||||
|
verbose_name="Mission Type"
|
||||||
|
)
|
||||||
|
|
||||||
|
mnum = models.PositiveSmallIntegerField(
|
||||||
|
null=True,
|
||||||
|
default=0,
|
||||||
|
verbose_name="Mission Number"
|
||||||
|
)
|
||||||
|
|
||||||
|
duration = models.DurationField(
|
||||||
|
null=False,
|
||||||
|
default=timedelta(hours=1)
|
||||||
|
)
|
||||||
|
|
||||||
|
aircrafts = models.ManyToManyField(
|
||||||
|
Aircraft
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = models.TextField(
|
||||||
|
max_length=140,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.mtype} {self.mnum}"
|
||||||
|
|
||||||
|
class Training(models.Model):
|
||||||
|
id = models.BigAutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
weekpref = models.ForeignKey(
|
||||||
|
WeekPreference,
|
||||||
|
null=False,
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
mission = models.ForeignKey(
|
||||||
|
MissionProfile,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
monday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
tuesday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
wednesday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
thursday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
friday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
saturday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
sunday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = models.TextField(
|
||||||
|
max_length=140,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.mission}"
|
||||||
|
|
||||||
97
cntmanage/flightslot/models/students.py
Normal file
97
cntmanage/flightslot/models/students.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
|
from phonenumber_field import modelfields
|
||||||
|
|
||||||
|
from ..models.courses import Course
|
||||||
|
from ..models.aircrafts import Aircraft
|
||||||
|
|
||||||
|
class Student(models.Model):
|
||||||
|
id = models.AutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
email = models.EmailField(
|
||||||
|
null=False,
|
||||||
|
db_index=True,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
|
||||||
|
phone = modelfields.PhoneNumberField(
|
||||||
|
null=True,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
null=False,
|
||||||
|
max_length=32
|
||||||
|
)
|
||||||
|
|
||||||
|
surname = models.CharField(
|
||||||
|
null=False,
|
||||||
|
max_length=32
|
||||||
|
)
|
||||||
|
|
||||||
|
course = models.ForeignKey(
|
||||||
|
Course,
|
||||||
|
on_delete=models.DO_NOTHING,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
active = models.BooleanField(
|
||||||
|
null=False,
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
user = models.OneToOneField(
|
||||||
|
User,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
aircrafts = models.ManyToManyField(
|
||||||
|
Aircraft
|
||||||
|
)
|
||||||
|
|
||||||
|
def default_password(self) -> str: # Maximum 4 digits for passowrd
|
||||||
|
if self.pk:
|
||||||
|
return f"{self.name.lower()[0]}{self.surname.lower()}{self.id % 10000}"
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def default_username(self) -> str:
|
||||||
|
if self.pk and self.user:
|
||||||
|
return self.user.username
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Override save method to add user for login upon Student creation
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
creating: bool = self.pk is None
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
if creating and not self.user:
|
||||||
|
username: str = f"{self.name.lower()}.{self.surname.lower()}"
|
||||||
|
# Avoid username conflict with progressive number
|
||||||
|
base_username = username
|
||||||
|
counter: int = 1
|
||||||
|
while User.objects.filter(username=username).exists():
|
||||||
|
username = f"{base_username}{counter}"
|
||||||
|
counter += 1
|
||||||
|
# Create user
|
||||||
|
user: User = User.objects.create_user(
|
||||||
|
first_name=self.name.capitalize(),
|
||||||
|
last_name=self.surname.capitalize(),
|
||||||
|
username=username,
|
||||||
|
email=self.email,
|
||||||
|
password=self.default_password(),
|
||||||
|
is_staff=True # allows access to admin page
|
||||||
|
)
|
||||||
|
|
||||||
|
student_group, _ = Group.objects.get_or_create(name="StudentGroup")
|
||||||
|
user.groups.add(student_group)
|
||||||
|
self.user = user
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.surname} {self.name[0]}. => {self.course}"
|
||||||
28
cntmanage/flightslot/models/weekpref.py
Normal file
28
cntmanage/flightslot/models/weekpref.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from django.db import models
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from ..models.students import Student
|
||||||
|
|
||||||
|
class WeekPreference(models.Model):
|
||||||
|
id = models.BigAutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
week = models.PositiveSmallIntegerField(
|
||||||
|
null=False,
|
||||||
|
db_index=True,
|
||||||
|
db_default=date.today().isocalendar().week,
|
||||||
|
auto_created=True,
|
||||||
|
verbose_name="Week Number"
|
||||||
|
)
|
||||||
|
|
||||||
|
student = models.ForeignKey(
|
||||||
|
Student,
|
||||||
|
null=False,
|
||||||
|
db_index=True,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name="Student Selection"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Week {self.week} - {self.student.surname} {self.student.name[0]}."
|
||||||
@@ -6,7 +6,7 @@ import sys
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Run administrative tasks."""
|
"""Run administrative tasks."""
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'techdb.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cntmanage.settings')
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
486
cntmanage/poetry.lock
generated
Normal file
486
cntmanage/poetry.lock
generated
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asgiref"
|
||||||
|
version = "3.11.0"
|
||||||
|
description = "ASGI specs, helper code, and adapters"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d"},
|
||||||
|
{file = "asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff-match-patch"
|
||||||
|
version = "20241021"
|
||||||
|
description = "Repackaging of Google's Diff Match and Patch libraries."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "diff_match_patch-20241021-py3-none-any.whl", hash = "sha256:93cea333fb8b2bc0d181b0de5e16df50dd344ce64828226bda07728818936782"},
|
||||||
|
{file = "diff_match_patch-20241021.tar.gz", hash = "sha256:beae57a99fa48084532935ee2968b8661db861862ec82c6f21f4acdd6d835073"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["attribution (==1.8.0)", "black (==24.8.0)", "build (>=1)", "flit (==3.9.0)", "mypy (==1.12.1)", "ufmt (==2.7.3)", "usort (==1.0.8.post1)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django"
|
||||||
|
version = "5.2.8"
|
||||||
|
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django-5.2.8-py3-none-any.whl", hash = "sha256:37e687f7bd73ddf043e2b6b97cfe02fcbb11f2dbb3adccc6a2b18c6daa054d7f"},
|
||||||
|
{file = "django-5.2.8.tar.gz", hash = "sha256:23254866a5bb9a2cfa6004e8b809ec6246eba4b58a7589bc2772f1bcc8456c7f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
asgiref = ">=3.8.1"
|
||||||
|
sqlparse = ">=0.3.1"
|
||||||
|
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||||
|
bcrypt = ["bcrypt"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-admin-action-forms"
|
||||||
|
version = "2.2.1"
|
||||||
|
description = "Extension for the Django admin panel that allows passing additional parameters to actions by creating intermediate pages with forms."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django_admin_action_forms-2.2.1-py3-none-any.whl", hash = "sha256:597d20d36fcb6cfbb0b5e0ed83df0c9dbd5af6b225f7af24f5b96a2ed84d4d35"},
|
||||||
|
{file = "django_admin_action_forms-2.2.1.tar.gz", hash = "sha256:94ff59964ece5d6b8d2c9c307f22837863be173e3fb64fdcd64d6f301e1d0c9d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
django = ">=3.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-admin-confirm"
|
||||||
|
version = "1.0.1"
|
||||||
|
description = "Adds confirmation to Django Admin changes, additions and actions"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django-admin-confirm-1.0.1.tar.gz", hash = "sha256:fb6a4b7cb9fc6ccd97f92f88275ee8e3912f3ee0ce82da962ad0a2b1b17cd6a0"},
|
||||||
|
{file = "django_admin_confirm-1.0.1-py3-none-any.whl", hash = "sha256:271a7135e8e5f0cce94a6c06f708dec794d3538ada37e111c1f8d7c8f762b012"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=3.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-colorfield"
|
||||||
|
version = "0.14.0"
|
||||||
|
description = "color field for django models with a nice color-picker in the admin."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django_colorfield-0.14.0-py3-none-any.whl", hash = "sha256:f169c4e7ad8f336e51d4ea81f866346e7d4f336f3766e54e144cd16ea7d84a0e"},
|
||||||
|
{file = "django_colorfield-0.14.0.tar.gz", hash = "sha256:478dbd3975a88f2ea2a9afc325faaca05c54ebf04ec985ce130f6dea39dfb899"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Pillow = ">=9.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-durationwidget"
|
||||||
|
version = "1.0.5"
|
||||||
|
description = "Django Duration field widget to handle duration field in the form"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django-durationwidget-1.0.5.tar.gz", hash = "sha256:a77bba14c173cbfe50071ae08759ca37218acb8567639687b0119647cb78c53f"},
|
||||||
|
{file = "django_durationwidget-1.0.5-py3-none-any.whl", hash = "sha256:7d14d5dfcf46c444fb203fe911cd058f78ba482c81ea2f0665db08369a1eff47"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=1.11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-import-export"
|
||||||
|
version = "4.3.14"
|
||||||
|
description = "Django application and library for importing and exporting data with included admin integration."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django_import_export-4.3.14-py3-none-any.whl", hash = "sha256:ce6484fa082a1cdb2bf4e0b60276d3e2a7f39f74c20ae663b2f8eebb54141a58"},
|
||||||
|
{file = "django_import_export-4.3.14.tar.gz", hash = "sha256:224c7d909fec607378bc58271db38b9c6065982306aa644d26a529fcde64869e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
diff-match-patch = "20241021"
|
||||||
|
Django = ">=4.2"
|
||||||
|
tablib = ">=3.7.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["tablib[all]"]
|
||||||
|
cli = ["tablib[cli]"]
|
||||||
|
docs = ["openpyxl (==3.1.5)", "psycopg[binary] (>=3.2.9)", "sphinx (==8.1.3)", "sphinx-rtd-theme (==3.0.1)"]
|
||||||
|
ods = ["tablib[ods]"]
|
||||||
|
pandas = ["tablib[pandas]"]
|
||||||
|
tests = ["chardet (==5.2.0)", "coverage (==7.6.4)", "django-extensions (==3.2.3)", "memory-profiler (==0.61.0)", "mysqlclient (==2.2.5)", "psycopg[binary] (>=3.2.9)", "pytz (==2024.2)", "setuptools-scm (==8.1.0)", "tablib[all] (>=3.7.0)"]
|
||||||
|
xls = ["tablib[xls]"]
|
||||||
|
xlsx = ["tablib[xlsx]"]
|
||||||
|
yaml = ["tablib[yaml]"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-nested-admin"
|
||||||
|
version = "4.1.6"
|
||||||
|
description = "Django admin classes that allow for nested inlines"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django_nested_admin-4.1.6-py3-none-any.whl", hash = "sha256:ae3e1d910631e6cc3b5815d80129f18c6c081b7dd7f775bf392d9bb1ac88ef93"},
|
||||||
|
{file = "django_nested_admin-4.1.6.tar.gz", hash = "sha256:0222475cc343e7b8813d7a5db583cddef15f5c0f44dbe17b91b4d682db7bd73a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
python-monkey-business = ">=1.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["Pillow", "black", "dj-database-url", "django-selenosis", "flake8", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "selenium"]
|
||||||
|
test = ["Pillow", "dj-database-url", "django-selenosis", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "selenium"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-phonenumber-field"
|
||||||
|
version = "8.4.0"
|
||||||
|
description = "An international phone number field for django models."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django_phonenumber_field-8.4.0-py3-none-any.whl", hash = "sha256:7a1cb3a6456edb54d879f11ffa0acb227ded08c93b587035d0f28093f0e46511"},
|
||||||
|
{file = "django_phonenumber_field-8.4.0.tar.gz", hash = "sha256:2b83e843dac35eec6a69880a166487235b737a71a1e38c9a52e5ad67d6996083"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=4.2"
|
||||||
|
phonenumberslite = {version = ">=7.0.2", optional = true, markers = "extra == \"phonenumberslite\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
phonenumbers = ["phonenumbers (>=7.0.2)"]
|
||||||
|
phonenumberslite = ["phonenumberslite (>=7.0.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-polymorphic"
|
||||||
|
version = "4.1.0"
|
||||||
|
description = "Seamless polymorphic inheritance for Django models"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django_polymorphic-4.1.0-py3-none-any.whl", hash = "sha256:0ce3984999e103a0d1a434a5c5617f2c7f990dc3d5fb3585ce0fadadf9ff90ea"},
|
||||||
|
{file = "django_polymorphic-4.1.0.tar.gz", hash = "sha256:4438d95a0aef6c4307cd6c83ead387e1142ce80b65188a931ec2f0dbdd9bfc51"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=3.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "et-xmlfile"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "An implementation of lxml.xmlfile for the standard library"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"},
|
||||||
|
{file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openpyxl"
|
||||||
|
version = "3.1.5"
|
||||||
|
description = "A Python library to read/write Excel 2010 xlsx/xlsm files"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"},
|
||||||
|
{file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
et-xmlfile = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phonenumberslite"
|
||||||
|
version = "9.0.19"
|
||||||
|
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "phonenumberslite-9.0.19-py2.py3-none-any.whl", hash = "sha256:92a2426808e7d40b4acf36c97dcc436747807419c5dbc035330df28c13d41c0f"},
|
||||||
|
{file = "phonenumberslite-9.0.19.tar.gz", hash = "sha256:3794fcec9d2a6510a806187de750853c73ea5dabaac4ecd7fa36e79f869b3c2e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "12.0.0"
|
||||||
|
description = "Python Imaging Library (fork)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d"},
|
||||||
|
{file = "pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e"},
|
||||||
|
{file = "pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a"},
|
||||||
|
{file = "pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b"},
|
||||||
|
{file = "pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a"},
|
||||||
|
{file = "pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7"},
|
||||||
|
{file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8"},
|
||||||
|
{file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a"},
|
||||||
|
{file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197"},
|
||||||
|
{file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c"},
|
||||||
|
{file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e"},
|
||||||
|
{file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76"},
|
||||||
|
{file = "pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5"},
|
||||||
|
{file = "pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||||
|
fpx = ["olefile"]
|
||||||
|
mic = ["olefile"]
|
||||||
|
test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"]
|
||||||
|
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
|
||||||
|
xmp = ["defusedxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psycopg2-binary"
|
||||||
|
version = "2.9.11"
|
||||||
|
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6fe6b47d0b42ce1c9f1fa3e35bb365011ca22e39db37074458f27921dca40f2"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c0e4262e089516603a09474ee13eabf09cb65c332277e39af68f6233911087"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c47676e5b485393f069b4d7a811267d3168ce46f988fa602658b8bb901e9e64d"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a28d8c01a7b27a1e3265b11250ba7557e5f72b5ee9e5f3a2fa8d2949c29bf5d2"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f2732cf504a1aa9e9609d02f79bea1067d99edf844ab92c247bbca143303b"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:865f9945ed1b3950d968ec4690ce68c55019d79e4497366d36e090327ce7db14"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91537a8df2bde69b1c1db01d6d944c831ca793952e4f57892600e96cee95f2cd"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dca1f356a67ecb68c81a7bc7809f1569ad9e152ce7fd02c2f2036862ca9f66b"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0da4de5c1ac69d94ed4364b6cbe7190c1a70d325f112ba783d83f8440285f152"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37d8412565a7267f7d79e29ab66876e55cb5e8e7b3bbf94f8206f6795f8f7e7e"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:c665f01ec8ab273a61c62beeb8cce3014c214429ced8a308ca1fc410ecac3a39"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a311f1edc9967723d3511ea7d2708e2c3592e3405677bf53d5c7246753591fbb"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20e7fb94e20b03dcc783f76c0865f9da39559dcc0c28dd1a3fce0d01902a6b9c"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bdab48575b6f870f465b397c38f1b415520e9879fdf10a53ee4f49dcbdf8a21"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9d3a9edcfbe77a3ed4bc72836d466dfce4174beb79eda79ea155cc77237ed9e8"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:44fc5c2b8fa871ce7f0023f619f1349a0aa03a0857f2c96fbc01c657dcbbdb49"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9c55460033867b4622cda1b6872edf445809535144152e5d14941ef591980edf"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2d11098a83cca92deaeaed3d58cfd150d49b3b06ee0d0852be466bf87596899e"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:691c807d94aecfbc76a14e1408847d59ff5b5906a04a23e12a89007672b9e819"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b81627b691f29c4c30a8f322546ad039c40c328373b11dff7490a3e1b517855"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:b637d6d941209e8d96a072d7977238eea128046effbf37d1d8b2c0764750017d"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:41360b01c140c2a03d346cec3280cf8a71aa07d94f3b1509fa0161c366af66b4"},
|
||||||
|
{file = "psycopg2_binary-2.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:875039274f8a2361e5207857899706da840768e2a775bf8c65e82f60b197df02"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-monkey-business"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "Utility functions for monkey-patching python code"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "python-monkey-business-1.1.0.tar.gz", hash = "sha256:8393839cc741415ed5ddc2bd58e2d4ce07f966a7d26b7aebff19dcec64818edc"},
|
||||||
|
{file = "python_monkey_business-1.1.0-py2.py3-none-any.whl", hash = "sha256:15b4f603c749ba9a7b4f1acd36af023a6c5ba0f7e591c945f8253f0ef44bf389"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlparse"
|
||||||
|
version = "0.5.4"
|
||||||
|
description = "A non-validating SQL parser."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb"},
|
||||||
|
{file = "sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["build"]
|
||||||
|
doc = ["sphinx"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tablib"
|
||||||
|
version = "3.9.0"
|
||||||
|
description = "Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "tablib-3.9.0-py3-none-any.whl", hash = "sha256:eda17cd0d4dda614efc0e710227654c60ddbeb1ca92cdcfc5c3bd1fc5f5a6e4a"},
|
||||||
|
{file = "tablib-3.9.0.tar.gz", hash = "sha256:1b6abd8edb0f35601e04c6161d79660fdcde4abb4a54f66cc9f9054bd55d5fe2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["odfpy", "openpyxl (>=2.6.0)", "pandas", "pyyaml", "tabulate", "xlrd", "xlwt"]
|
||||||
|
cli = ["tabulate"]
|
||||||
|
ods = ["odfpy"]
|
||||||
|
pandas = ["pandas"]
|
||||||
|
xls = ["xlrd", "xlwt"]
|
||||||
|
xlsx = ["openpyxl (>=2.6.0)"]
|
||||||
|
yaml = ["pyyaml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2025.2"
|
||||||
|
description = "Provider of IANA time zone data"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "sys_platform == \"win32\""
|
||||||
|
files = [
|
||||||
|
{file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
|
||||||
|
{file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.1"
|
||||||
|
python-versions = "^3.12"
|
||||||
|
content-hash = "b45301c627836abac1ef9628e67fc63189b03e7857a7a003854aa1fb30f2a4a3"
|
||||||
27
cntmanage/pyproject.toml
Normal file
27
cntmanage/pyproject.toml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "cntmanage"
|
||||||
|
version = "0.2.0"
|
||||||
|
packages = [{include = "flightslot"}, {include = "cntmanage"}]
|
||||||
|
description = "CantorAir Flight Scheduler"
|
||||||
|
authors = ["Emanuele <ema.trabattoni@gmail.com>"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.12"
|
||||||
|
django = "^5.1.2"
|
||||||
|
psycopg2-binary = "^2.9.10"
|
||||||
|
django-nested-admin = "^4.1.1"
|
||||||
|
django-durationwidget = "^1.0.5"
|
||||||
|
django-import-export = "^4.3.13"
|
||||||
|
django-colorfield = "^0.14.0"
|
||||||
|
openpyxl = "^3.1.5"
|
||||||
|
django-admin-action-forms = "^2.2.1"
|
||||||
|
django-polymorphic = "^4.1.0"
|
||||||
|
django-phonenumber-field = {extras = ["phonenumberslite"], version = "^8.4.0"}
|
||||||
|
django-admin-confirm = "^1.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
BIN
cntmanage/static/cantorair.jpg
Normal file
BIN
cntmanage/static/cantorair.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
cntmanage/static/cantorair.png
Normal file
BIN
cntmanage/static/cantorair.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
BIN
cntmanage/static/cantorair_blue.jpg
Normal file
BIN
cntmanage/static/cantorair_blue.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
29
cntmanage/templates/admin/base_site.html
Normal file
29
cntmanage/templates/admin/base_site.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block branding %}
|
||||||
|
|
||||||
|
<h1 id="site-name">
|
||||||
|
<a href="{% url 'admin:index' %}" style="color: #0b1728;">
|
||||||
|
<img src="{% static 'cantorair.png' %}"
|
||||||
|
height="70px"
|
||||||
|
style="margin-right: 20px;"/>
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div id="site-name" style="align-self: center; text-align: center;">
|
||||||
|
<a href="{% url 'admin:index' %}" style="color: #0b1728;">
|
||||||
|
{{ site_header | default:_('Django administration') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if user.is_anonymous %}
|
||||||
|
{% include "admin/color_theme_toggle.html" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block nav-global %}{% endblock %}
|
||||||
408
cntmanage/templates/email/mail.html
Normal file
408
cntmanage/templates/email/mail.html
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html
|
||||||
|
xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG />
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="x-apple-disable-message-reformatting" />
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<!--<![endif]-->
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
@media only screen and (min-width: 520px) {
|
||||||
|
.u-row {
|
||||||
|
width: 500px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row .u-col {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row .u-col-100 {
|
||||||
|
width: 500px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 520px) {
|
||||||
|
.u-row-container {
|
||||||
|
max-width: 100% !important;
|
||||||
|
padding-left: 0px !important;
|
||||||
|
padding-right: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row .u-col {
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 320px !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row .u-col > div {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
table,
|
||||||
|
td,
|
||||||
|
tr {
|
||||||
|
border-collapse: collapse;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.ie-container table,
|
||||||
|
.mso-container table {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
a[x-apple-data-detectors="true"] {
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table,
|
||||||
|
td {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body
|
||||||
|
class="clean-body u_body"
|
||||||
|
style="
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
background-color: #f7f8f9;
|
||||||
|
color: #000000;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if IE]><div class="ie-container"><![endif]-->
|
||||||
|
<!--[if mso]><div class="mso-container"><![endif]-->
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
id="u_body"
|
||||||
|
style="
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
border-spacing: 0;
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
vertical-align: top;
|
||||||
|
min-width: 320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #f7f8f9;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
vertical-align: top;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if (mso)|(IE)]><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color: #F7F8F9;"><![endif]-->
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="u-row-container"
|
||||||
|
style="padding: 0px; background-color: transparent"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="u-row"
|
||||||
|
style="
|
||||||
|
margin: 0 auto;
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 500px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
background-color: transparent;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
border-collapse: collapse;
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if (mso)|(IE)]><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:500px;"><tr style="background-color: transparent;"><![endif]-->
|
||||||
|
|
||||||
|
<!--[if (mso)|(IE)]><td align="center" width="500" style="width: 500px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;border-radius: 0px;-webkit-border-radius: 0px; -moz-border-radius: 0px;" valign="top"><![endif]-->
|
||||||
|
<div
|
||||||
|
class="u-col u-col-100"
|
||||||
|
style="
|
||||||
|
max-width: 320px;
|
||||||
|
min-width: 500px;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: top;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
height: 100%;
|
||||||
|
width: 100% !important;
|
||||||
|
border-radius: 0px;
|
||||||
|
-webkit-border-radius: 0px;
|
||||||
|
-moz-border-radius: 0px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if (!mso)&(!IE)]><!--><div
|
||||||
|
style="
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0px;
|
||||||
|
border-top: 0px solid transparent;
|
||||||
|
border-left: 0px solid transparent;
|
||||||
|
border-right: 0px solid transparent;
|
||||||
|
border-bottom: 0px solid transparent;
|
||||||
|
border-radius: 0px;
|
||||||
|
-webkit-border-radius: 0px;
|
||||||
|
-moz-border-radius: 0px;
|
||||||
|
"
|
||||||
|
><!--<![endif]-->
|
||||||
|
<table
|
||||||
|
style="font-family: arial, helvetica, sans-serif"
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: arial, helvetica, sans-serif;
|
||||||
|
"
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="100%"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
padding-right: 0px;
|
||||||
|
padding-left: 0px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://www.cantorair.it"
|
||||||
|
target="_blank"
|
||||||
|
style="
|
||||||
|
color: rgb(0, 0, 238);
|
||||||
|
text-decoration: underline;
|
||||||
|
line-height: inherit;
|
||||||
|
"
|
||||||
|
><img
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
src="cid:logo_image"
|
||||||
|
alt="Cantor Air Logo"
|
||||||
|
title=""
|
||||||
|
style="
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
clear: both;
|
||||||
|
display: inline-block !important;
|
||||||
|
border: none;
|
||||||
|
height: auto;
|
||||||
|
float: none;
|
||||||
|
width: 50%;
|
||||||
|
max-width: 240px;
|
||||||
|
"
|
||||||
|
width="240"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
style="font-family: arial, helvetica, sans-serif"
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: arial, helvetica, sans-serif;
|
||||||
|
"
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
<!--[if mso]><table role="presentation" width="100%"><tr><td><![endif]-->
|
||||||
|
<h1
|
||||||
|
style="
|
||||||
|
margin: 0px;
|
||||||
|
line-height: 140%;
|
||||||
|
text-align: center;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 400;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>✈️ Welcome to CantorAir Flight Scheduler
|
||||||
|
✈️</span
|
||||||
|
>
|
||||||
|
</h1>
|
||||||
|
<!--[if mso]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
style="font-family: arial, helvetica, sans-serif"
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: arial, helvetica, sans-serif;
|
||||||
|
"
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 140%;
|
||||||
|
text-align: left;
|
||||||
|
word-wrap: break-word;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style="
|
||||||
|
text-align: center;
|
||||||
|
line-height: inherit;
|
||||||
|
margin: 0px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>You can access the flight scheduler
|
||||||
|
website with this link: </span
|
||||||
|
><br /><a href="https://cms.etss.it/user"
|
||||||
|
><span>https://cms.etss.it</span></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p style="text-align: center"><br /></p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<span style="font-weight: bold"
|
||||||
|
>Your access credentials:</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<br /><span>Username: {{username}}</span
|
||||||
|
><br /><span>Password: {{password}}</span>
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<br />
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<span
|
||||||
|
>Flight Scheduling is available from </span
|
||||||
|
><br /><span style="font-weight: bold"
|
||||||
|
>Monday to Tuesday until 15.00</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p style="text-align: center">
|
||||||
|
<br /><span>_______________________</span>
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<span style="font-style: italic">Team</span
|
||||||
|
><br /><span style="font-weight: bold"
|
||||||
|
>CANTOR AIR IT.ATO.0004</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<span> Email: </span
|
||||||
|
><a
|
||||||
|
href="mailto:info@cantorair.it"
|
||||||
|
target="_blank"
|
||||||
|
style="line-height: 115%"
|
||||||
|
><span>info@cantorair.it</span></a
|
||||||
|
><br /><span>Phone: +39 035 520035</span
|
||||||
|
><br /><span>_______________________</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!--[if (!mso)&(!IE)]><!-->
|
||||||
|
</div>
|
||||||
|
<!--<![endif]-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||||
|
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso]></div><![endif]-->
|
||||||
|
<!--[if IE]></div><![endif]-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
note.txt
Normal file
8
note.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
OK leg delle hb all'interno del riquadro per i giorni
|
||||||
|
OK aereo assegnato allo studente di fianco al numero della missione per PPL, invece per CPL e IR e HB il tipo va di fiaco al nome dello studente
|
||||||
|
OK le missioni ripetute su piu' giorni hanno una cella per giorno (non unite)
|
||||||
|
OK lo studente vede solo le missioni della sua fase PPL->PPL ATPL-> tutto
|
||||||
|
OK ogni richiesta ha un colore diverso che cicla con delle tinte pastello
|
||||||
|
|
||||||
|
|
||||||
|
password: CantorAir2k25
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from .models.box import Box, BoxAdmin
|
|
||||||
from .models.part import Part, PartAdmin
|
|
||||||
from .models.vendor import Vendor, VendorAdmin
|
|
||||||
from .models.formone import FormOne,FormOneAdmin
|
|
||||||
from .models.customer import Customer, CustomerAdmin
|
|
||||||
from .models.plane import Plane, PlaneAdmin
|
|
||||||
from .models.workorder import Workorder, WorkorderAdmin
|
|
||||||
from .models.operator import Operator, OperatorAdmin
|
|
||||||
from .models.movimag import Movimag, MovimagAdmin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
admin.site.register(Box, BoxAdmin)
|
|
||||||
admin.site.register(Part, PartAdmin)
|
|
||||||
admin.site.register(Vendor, VendorAdmin)
|
|
||||||
admin.site.register(FormOne,FormOneAdmin)
|
|
||||||
admin.site.register(Customer, CustomerAdmin)
|
|
||||||
admin.site.register(Plane, PlaneAdmin)
|
|
||||||
admin.site.register(Workorder, WorkorderAdmin)
|
|
||||||
admin.site.register(Operator, OperatorAdmin)
|
|
||||||
admin.site.register(Movimag, MovimagAdmin)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class CatopsConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'catops'
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Generated by Django 5.1.2 on 2024-10-18 09:46
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Parts',
|
|
||||||
fields=[
|
|
||||||
('id', models.UUIDField(primary_key=True, serialize=False)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.1.2 on 2024-10-18 09:55
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('catops', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameModel(
|
|
||||||
old_name='Parts',
|
|
||||||
new_name='Part',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
# Generated by Django 5.1.2 on 2024-10-18 15:45
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.db.models.functions.datetime
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('catops', '0002_rename_parts_part'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='FormOne',
|
|
||||||
fields=[
|
|
||||||
('reg_date', models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False)),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('external_id', models.CharField(db_index=True, null=True)),
|
|
||||||
('doc_path', models.FilePathField(null=True)),
|
|
||||||
('active', models.BooleanField(db_default=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='active',
|
|
||||||
field=models.BooleanField(db_default=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='expiry_time',
|
|
||||||
field=models.DurationField(null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='last_stock',
|
|
||||||
field=models.DateField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='min_reorder',
|
|
||||||
field=models.PositiveIntegerField(null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='part_number',
|
|
||||||
field=models.CharField(db_index=True, default='aaa', max_length=64, unique=True),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='properties',
|
|
||||||
field=models.JSONField(null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='quantity',
|
|
||||||
field=models.PositiveIntegerField(default=0),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='reg_date',
|
|
||||||
field=models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='serial_number',
|
|
||||||
field=models.CharField(db_index=True, max_length=64, null=True, unique=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='unit',
|
|
||||||
field=models.CharField(choices=[('QTY', 'Quantity'), ('LT', 'Liters'), ('USG', 'US Gallons'), ('QTS', 'US Quarters'), ('KG', 'Kilograms'), ('LBS', 'Pounds')], default='QTY'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='part',
|
|
||||||
name='id',
|
|
||||||
field=models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Box',
|
|
||||||
fields=[
|
|
||||||
('reg_date', models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False)),
|
|
||||||
('id', models.UUIDField(db_index=True, default=uuid.uuid1, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('loc_room', models.CharField(choices=[('ST1', 'Magazzino'), ('ST2', 'Deposito Esterno')], default='ST1', max_length=3)),
|
|
||||||
('loc_x', models.CharField(max_length=4)),
|
|
||||||
('loc_y', models.CharField(max_length=4)),
|
|
||||||
('loc_z', models.CharField(max_length=4)),
|
|
||||||
('active', models.BooleanField(db_default=True)),
|
|
||||||
('label_printed', models.BooleanField(db_default=False)),
|
|
||||||
('part', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='catops.part')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='part',
|
|
||||||
name='form_one',
|
|
||||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='catops.formone'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 5.1.2 on 2024-10-18 15:45
|
|
||||||
|
|
||||||
import django.db.models.functions.datetime
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('catops', '0003_formone_part_active_part_expiry_time_part_last_stock_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Vendor',
|
|
||||||
fields=[
|
|
||||||
('reg_date', models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False)),
|
|
||||||
('id', models.UUIDField(primary_key=True, serialize=False)),
|
|
||||||
('active', models.BooleanField(db_default=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Generated by Django 5.1.2 on 2024-10-18 15:45
|
|
||||||
|
|
||||||
import django.db.models.functions.datetime
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('catops', '0004_vendor'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Customer',
|
|
||||||
fields=[
|
|
||||||
('reg_date', models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False)),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=32)),
|
|
||||||
('surname', models.CharField(max_length=32)),
|
|
||||||
('external_id', models.CharField(db_index=True, null=True)),
|
|
||||||
('properties', models.JSONField(null=True)),
|
|
||||||
('active', models.BooleanField(db_default=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Generated by Django 5.1.2 on 2024-10-18 15:46
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.db.models.functions.datetime
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('catops', '0005_customer'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Plane',
|
|
||||||
fields=[
|
|
||||||
('reg_date', models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False)),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('tail', models.CharField(max_length=6)),
|
|
||||||
('manufacturer', models.CharField(max_length=32)),
|
|
||||||
('model', models.CharField(max_length=32)),
|
|
||||||
('chassis_num', models.CharField(max_length=32)),
|
|
||||||
('external_id', models.CharField(db_index=True, null=True)),
|
|
||||||
('properties', models.JSONField(null=True)),
|
|
||||||
('active', models.BooleanField(db_default=True)),
|
|
||||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='catops.customer')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Generated by Django 5.1.2 on 2024-10-18 15:47
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.db.models.functions.datetime
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('catops', '0006_plane'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Operator',
|
|
||||||
fields=[
|
|
||||||
('reg_date', models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False)),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=32)),
|
|
||||||
('surname', models.CharField(max_length=32)),
|
|
||||||
('external_id', models.CharField(db_index=True, null=True)),
|
|
||||||
('properties', models.JSONField(null=True)),
|
|
||||||
('active', models.BooleanField(db_default=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Workorder',
|
|
||||||
fields=[
|
|
||||||
('reg_date', models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False)),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('date_begin', models.DateTimeField(db_default=django.db.models.functions.datetime.Now())),
|
|
||||||
('date_end', models.DateTimeField()),
|
|
||||||
('external_id', models.CharField(db_index=True, max_length=32, null=True)),
|
|
||||||
('properties', models.JSONField(null=True)),
|
|
||||||
('active', models.BooleanField(db_default=True)),
|
|
||||||
('operator', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='catops.operator')),
|
|
||||||
('plane', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='catops.plane')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Generated by Django 5.1.2 on 2024-10-18 15:47
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.db.models.functions.datetime
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('catops', '0007_operator_workorder'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Movimag',
|
|
||||||
fields=[
|
|
||||||
('mov_datetime', models.DateTimeField(auto_created=True, db_default=django.db.models.functions.datetime.Now(), editable=False)),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('direction', models.CharField(choices=[('LOAD', 'Load'), ('UNLOAD', 'UnLoad')])),
|
|
||||||
('operator', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='catops.operator')),
|
|
||||||
('part', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='catops.part')),
|
|
||||||
('workorder', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='catops.workorder')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
class RoomLocation(models.TextChoices):
|
|
||||||
STORAGE_1 = "ST1", _("Magazzino")
|
|
||||||
STORAGE_2 = "ST2", _("Deposito Esterno")
|
|
||||||
|
|
||||||
class Units(models.TextChoices):
|
|
||||||
NUM = "QTY", _("Quantity")
|
|
||||||
LT = "LT", _("Liters")
|
|
||||||
USG = "USG", _("US Gallons")
|
|
||||||
QTS = "QTS", _("US Quarters")
|
|
||||||
KG = "KG", _("Kilograms")
|
|
||||||
LBS = "LBS", _("Pounds")
|
|
||||||
|
|
||||||
class MoviDirection(models.TextChoices):
|
|
||||||
LOAD = "LOAD", _("Load")
|
|
||||||
UNLOAD = "UNLOAD", _("UnLoad")
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models.functions import Now
|
|
||||||
from .part import Part
|
|
||||||
from ..misc.units import RoomLocation
|
|
||||||
|
|
||||||
# Box identifies a part storage location, it can be a drawer or a bag or a carbord box.
|
|
||||||
# It has a location and one or multiple parts inside it.
|
|
||||||
|
|
||||||
# Properties to visualize the Box model in the Django admin view
|
|
||||||
class BoxAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("id", "loc_room", "loc_x", "loc_y", "loc_z", "reg_date", "active")
|
|
||||||
list_filter = ["loc_room"]
|
|
||||||
|
|
||||||
class Box(models.Model):
|
|
||||||
id = models.UUIDField(
|
|
||||||
primary_key=True,
|
|
||||||
default=uuid.uuid1,
|
|
||||||
db_index=True,
|
|
||||||
editable=False,
|
|
||||||
serialize=str,
|
|
||||||
)
|
|
||||||
|
|
||||||
reg_date = models.DateTimeField(
|
|
||||||
auto_created=True,
|
|
||||||
editable=False,
|
|
||||||
db_default=Now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
loc_room = models.CharField(
|
|
||||||
max_length=3,
|
|
||||||
choices=RoomLocation,
|
|
||||||
default=RoomLocation.STORAGE_1,
|
|
||||||
null=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
loc_x = models.CharField(
|
|
||||||
max_length=4,
|
|
||||||
)
|
|
||||||
|
|
||||||
loc_y = models.CharField(
|
|
||||||
max_length=4,
|
|
||||||
)
|
|
||||||
|
|
||||||
loc_z = models.CharField(
|
|
||||||
max_length=4,
|
|
||||||
)
|
|
||||||
|
|
||||||
active = models.BooleanField(
|
|
||||||
db_default=True
|
|
||||||
)
|
|
||||||
|
|
||||||
label_printed = models.BooleanField(
|
|
||||||
db_default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
part = models.OneToOneField(
|
|
||||||
Part,
|
|
||||||
unique=True,
|
|
||||||
db_index=True,
|
|
||||||
swappable=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{str(self.id)}"
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models.functions import Now
|
|
||||||
|
|
||||||
class CustomerAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ()
|
|
||||||
list_filter = []
|
|
||||||
|
|
||||||
class Customer(models.Model):
|
|
||||||
id = models.BigAutoField(
|
|
||||||
primary_key=True
|
|
||||||
)
|
|
||||||
|
|
||||||
reg_date = models.DateTimeField(
|
|
||||||
auto_created=True,
|
|
||||||
editable=False,
|
|
||||||
db_default=Now()
|
|
||||||
)
|
|
||||||
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=32
|
|
||||||
)
|
|
||||||
|
|
||||||
surname = models.CharField(
|
|
||||||
max_length=32
|
|
||||||
)
|
|
||||||
|
|
||||||
external_id = models.CharField(
|
|
||||||
db_index=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
properties = models.JSONField(
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
active = models.BooleanField(
|
|
||||||
db_default=True
|
|
||||||
)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models.functions import Now
|
|
||||||
|
|
||||||
class FormOneAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ()
|
|
||||||
list_filter = []
|
|
||||||
|
|
||||||
class FormOne(models.Model):
|
|
||||||
id = models.BigAutoField(
|
|
||||||
primary_key=True
|
|
||||||
)
|
|
||||||
|
|
||||||
reg_date = models.DateTimeField(
|
|
||||||
auto_created=True,
|
|
||||||
editable=False,
|
|
||||||
db_default=Now()
|
|
||||||
)
|
|
||||||
|
|
||||||
external_id = models.CharField(
|
|
||||||
db_index=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
doc_path = models.FilePathField(
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
active = models.BooleanField(
|
|
||||||
db_default=True
|
|
||||||
)
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models.functions import Now
|
|
||||||
|
|
||||||
from .part import Part
|
|
||||||
from .operator import Operator
|
|
||||||
from .workorder import Workorder
|
|
||||||
from ..misc import units
|
|
||||||
|
|
||||||
class MovimagAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ()
|
|
||||||
list_filter = []
|
|
||||||
|
|
||||||
class Movimag(models.Model):
|
|
||||||
id = models.BigAutoField(
|
|
||||||
primary_key=True
|
|
||||||
)
|
|
||||||
|
|
||||||
mov_datetime = models.DateTimeField(
|
|
||||||
auto_created=True,
|
|
||||||
editable=False,
|
|
||||||
db_default=Now()
|
|
||||||
)
|
|
||||||
|
|
||||||
part = models.ForeignKey(
|
|
||||||
Part,
|
|
||||||
on_delete=models.DO_NOTHING
|
|
||||||
)
|
|
||||||
|
|
||||||
workorder = models.ForeignKey(
|
|
||||||
Workorder,
|
|
||||||
on_delete=models.DO_NOTHING
|
|
||||||
)
|
|
||||||
|
|
||||||
operator = models.ForeignKey(
|
|
||||||
Operator,
|
|
||||||
on_delete=models.DO_NOTHING
|
|
||||||
)
|
|
||||||
|
|
||||||
direction = models.CharField(
|
|
||||||
null=False,
|
|
||||||
choices=units.MoviDirection
|
|
||||||
)
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models.functions import Now
|
|
||||||
|
|
||||||
class OperatorAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ()
|
|
||||||
list_filter = ()
|
|
||||||
|
|
||||||
class Operator(models.Model):
|
|
||||||
id = models.BigAutoField(
|
|
||||||
primary_key=True
|
|
||||||
)
|
|
||||||
|
|
||||||
reg_date = models.DateTimeField(
|
|
||||||
auto_created=True,
|
|
||||||
editable=False,
|
|
||||||
db_default=Now()
|
|
||||||
)
|
|
||||||
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=32
|
|
||||||
)
|
|
||||||
|
|
||||||
surname = models.CharField(
|
|
||||||
max_length=32
|
|
||||||
)
|
|
||||||
|
|
||||||
external_id = models.CharField(
|
|
||||||
db_index=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
properties = models.JSONField(
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
active = models.BooleanField(
|
|
||||||
db_default=True
|
|
||||||
)
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models.functions import Now
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from ..misc.units import Units
|
|
||||||
from .formone import FormOne
|
|
||||||
|
|
||||||
# Part identifies a part with its part number and associated properties:
|
|
||||||
# such as torage quantity, min reorder quantities and expiry date.
|
|
||||||
|
|
||||||
class PartAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ()
|
|
||||||
list_filter = ()
|
|
||||||
|
|
||||||
class Part(models.Model):
|
|
||||||
id = models.UUIDField(
|
|
||||||
primary_key=True,
|
|
||||||
default=uuid.uuid4,
|
|
||||||
db_index=True,
|
|
||||||
editable=False,
|
|
||||||
serialize=str
|
|
||||||
)
|
|
||||||
|
|
||||||
reg_date = models.DateTimeField(
|
|
||||||
auto_created=True,
|
|
||||||
editable=False,
|
|
||||||
db_default=Now()
|
|
||||||
)
|
|
||||||
|
|
||||||
part_number = models.CharField(
|
|
||||||
db_index=True,
|
|
||||||
max_length=64,
|
|
||||||
null=False,
|
|
||||||
unique=True
|
|
||||||
)
|
|
||||||
|
|
||||||
serial_number = models.CharField(
|
|
||||||
db_index=True,
|
|
||||||
max_length=64,
|
|
||||||
null=True,
|
|
||||||
unique=True
|
|
||||||
)
|
|
||||||
|
|
||||||
quantity = models.PositiveIntegerField(
|
|
||||||
null=False,
|
|
||||||
default=0
|
|
||||||
)
|
|
||||||
|
|
||||||
unit = models.CharField(
|
|
||||||
choices=Units,
|
|
||||||
default=Units.NUM,
|
|
||||||
null=False
|
|
||||||
)
|
|
||||||
|
|
||||||
last_stock = models.DateField(
|
|
||||||
null=False,
|
|
||||||
auto_created=True,
|
|
||||||
editable=False,
|
|
||||||
db_default=Now()
|
|
||||||
)
|
|
||||||
|
|
||||||
expiry_time = models.DurationField(
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
min_reorder = models.PositiveIntegerField(
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
properties = models.JSONField(
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
form_one = models.OneToOneField(
|
|
||||||
FormOne,
|
|
||||||
on_delete=models.DO_NOTHING,
|
|
||||||
db_index=True,
|
|
||||||
unique=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
active = models.BooleanField(
|
|
||||||
db_default=True
|
|
||||||
)
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models.functions import Now
|
|
||||||
|
|
||||||
from .customer import Customer
|
|
||||||
|
|
||||||
class PlaneAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ()
|
|
||||||
list_filter = []
|
|
||||||
|
|
||||||
class Plane(models.Model):
|
|
||||||
id = models.BigAutoField(
|
|
||||||
primary_key=True
|
|
||||||
)
|
|
||||||
|
|
||||||
reg_date = models.DateTimeField(
|
|
||||||
auto_created=True,
|
|
||||||
editable=False,
|
|
||||||
db_default=Now()
|
|
||||||
)
|
|
||||||
|
|
||||||
tail = models.CharField(
|
|
||||||
max_length=6,
|
|
||||||
null=False
|
|
||||||
)
|
|
||||||
|
|
||||||
manufacturer = models.CharField(
|
|
||||||
max_length=32,
|
|
||||||
null=False
|
|
||||||
)
|
|
||||||
|
|
||||||
model = models.CharField(
|
|
||||||
max_length=32,
|
|
||||||
null=False
|
|
||||||
)
|
|
||||||
|
|
||||||
chassis_num = models.CharField(
|
|
||||||
max_length=32,
|
|
||||||
null=False
|
|
||||||
)
|
|
||||||
|
|
||||||
external_id = models.CharField(
|
|
||||||
db_index=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
customer = models.ForeignKey(
|
|
||||||
Customer,
|
|
||||||
on_delete=models.DO_NOTHING
|
|
||||||
)
|
|
||||||
|
|
||||||
properties = models.JSONField(
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
active = models.BooleanField(
|
|
||||||
db_default=True
|
|
||||||
)
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user