Compare commits
23 Commits
bb9ff3a86c
...
polymorphi
| Author | SHA1 | Date | |
|---|---|---|---|
| 95370ed0dc | |||
| bb634d28ed | |||
| cbdf49adfd | |||
| 7ad09e21b7 | |||
| 48ff1d799c | |||
| 02990d4b2f | |||
| bcdc885d66 | |||
| 674105600e | |||
| aa24976abb | |||
| 37b1ed4aa1 | |||
| 5f5c03e479 | |||
| 78f53cae7d | |||
| 72891440af | |||
| ae86a2e5fa | |||
| d5befdd018 | |||
| 491afb6257 | |||
| 9cae53a942 | |||
| 79d7333ca0 | |||
| edb54e9f6f | |||
| 34eabe6af7 | |||
| 71809d331f | |||
| 231a3e9861 | |||
| a91e0cd7bc |
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -8,7 +8,7 @@
|
||||
"name": "Python Debugger: Django",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/techdb/manage.py",
|
||||
"program": "${workspaceFolder}/cntmanage/manage.py",
|
||||
"args": ["runserver"],
|
||||
"console": "integratedTerminal",
|
||||
"django": true,
|
||||
|
||||
@@ -11,6 +11,7 @@ import os
|
||||
|
||||
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()
|
||||
@@ -28,7 +28,6 @@ DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
@@ -41,10 +40,20 @@ INSTALLED_APPS = [
|
||||
'nested_admin',
|
||||
'flightslot',
|
||||
'durationwidget',
|
||||
'colorfield'
|
||||
'colorfield',
|
||||
'import_export',
|
||||
'django_admin_action_forms',
|
||||
'polymorphic'
|
||||
]
|
||||
|
||||
# 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 = [
|
||||
'flightslot.middleware.RedirectNonSuperuserFromAdminMiddleware', # custom middleware to show "user" page to non superuser
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
@@ -54,7 +63,7 @@ MIDDLEWARE = [
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'techdb.urls'
|
||||
ROOT_URLCONF = 'cntmanage.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
@@ -72,7 +81,7 @@ TEMPLATES = [
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'techdb.wsgi.application'
|
||||
WSGI_APPLICATION = 'cntmanage.wsgi.application'
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
@@ -84,9 +93,9 @@ STATICFILES_DIRS = [
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'techstorage',
|
||||
'USER': 'tech',
|
||||
'PASSWORD': 'tech',
|
||||
'NAME': 'flightslot_db',
|
||||
'USER': 'flightslot',
|
||||
'PASSWORD': 'flightslot',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '5432'
|
||||
}
|
||||
@@ -119,7 +128,7 @@ LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
USE_I18N = False # Disable translation engine
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
144
cntmanage/cntmanage/settings_prod.py
Normal file
144
cntmanage/cntmanage/settings_prod.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""
|
||||
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 = [
|
||||
'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',
|
||||
]
|
||||
|
||||
# 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 = [
|
||||
'flightslot.middleware.RedirectNonSuperuserFromAdminMiddleware',
|
||||
'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',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'cntmanage.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': ['/var/www/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_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'
|
||||
11
cntmanage/cntmanage/urls.py
Normal file
11
cntmanage/cntmanage/urls.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.shortcuts import redirect
|
||||
from flightslot.admin import flightslot_user
|
||||
|
||||
urlpatterns = [
|
||||
#path('', RedirectView.as_view(url='/admin/', permanent=False)),
|
||||
path('admin/', admin.site.urls),
|
||||
path('user/', flightslot_user.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
|
||||
|
||||
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()
|
||||
52
cntmanage/docker/docker-compose.yml
Normal file
52
cntmanage/docker/docker-compose.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
# 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
|
||||
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..."
|
||||
exec "$@"
|
||||
41
cntmanage/docker/flightslot.Dockerfile
Normal file
41
cntmanage/docker/flightslot.Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
### 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"
|
||||
RUN env
|
||||
# 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
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy application custom static files
|
||||
RUN mkdir -p static
|
||||
COPY ./static/cantorair.jpg ./static
|
||||
COPY ./static/cantorair_blue.jpg ./static
|
||||
|
||||
# Copy application custom templates for admin page
|
||||
RUN mkdir -p /templates/admin
|
||||
COPY ./templates/admin/* ./templates/admin/
|
||||
|
||||
# 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 ./
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
|
||||
# Command to be executed after entry point
|
||||
CMD ["gunicorn", "cntmanage.wsgi:application", "--bind", "0.0.0.0:8000"]
|
||||
221
cntmanage/flightslot/actions/exportweek.py
Normal file
221
cntmanage/flightslot/actions/exportweek.py
Normal file
@@ -0,0 +1,221 @@
|
||||
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 datetime import date, datetime
|
||||
from typing import List
|
||||
|
||||
from ..models.weekpref import WeekPreference
|
||||
from ..models.missions import Training
|
||||
from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop, HourBuildingLegBase
|
||||
|
||||
|
||||
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='thick', 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("student__surname", "student__name", "student__course"), start=1):
|
||||
student_data: List[str]
|
||||
student_phone: str = q.student.phone if q.student.phone else ""
|
||||
student_email: str = q.student.email
|
||||
if q.student.course:
|
||||
student_data = [f"{q.student.surname} {q.student.name}", f"{q.student.course.ctype}-{q.student.course.cnumber}"]
|
||||
else:
|
||||
student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"]
|
||||
|
||||
# Fill Training mission rows
|
||||
mission_name: 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_name = f"{t.mission.mtype}-{t.mission.mnum}"
|
||||
mission_days = [
|
||||
mission_name if t.monday else "",
|
||||
mission_name if t.tuesday else "",
|
||||
mission_name if t.wednesday else "",
|
||||
mission_name if t.thursday else "",
|
||||
mission_name if t.friday else "",
|
||||
mission_name if t.saturday else "",
|
||||
mission_name if t.sunday else ""
|
||||
]
|
||||
mission_notes = t.notes if t.notes else "--"
|
||||
mission_data.append([str(q.week), *student_data, *mission_days, mission_notes, student_phone, student_email, ])
|
||||
|
||||
# Fill HourBuilding rows
|
||||
hb_name: str
|
||||
hb_days: List[str]
|
||||
hb_data: List[List[str]] = []
|
||||
for h in HourBuilding.objects.filter(weekpref = q.id):
|
||||
hb_name = f"HB - {h.aircraft}\nVedi Note ->"
|
||||
hb_days = [
|
||||
hb_name if h.monday else "",
|
||||
hb_name if h.tuesday else "",
|
||||
hb_name if h.wednesday else "",
|
||||
hb_name if h.thursday else "",
|
||||
hb_name if h.friday else "",
|
||||
hb_name if h.saturday else "",
|
||||
hb_name if h.sunday else ""
|
||||
]
|
||||
hb_notes: List[str] = [f"{h.notes}", "---"] if h.notes else []
|
||||
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_notes.append(f"{hh.departure} -> {hh.destination} [{time_str}]{f' / PAX: {hh.pax.capitalize()}' if hh.pax else ''}")
|
||||
elif isinstance(hh, HourBuildingLegStop):
|
||||
hb_notes.append(f"STOP [{time_str}] {"Refuel" if hh.refuel else ""}" )
|
||||
hb_data.append([str(q.week), *student_data, *hb_days, "\n".join(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 row_content in 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="f0f0f0")
|
||||
|
||||
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:
|
||||
for row_content in range(student_start, student_end + 1):
|
||||
ws.cell(row=row_content, column=c).border += border_right
|
||||
|
||||
# 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] = []
|
||||
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)
|
||||
col_letter: str = "A"
|
||||
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
|
||||
42
cntmanage/flightslot/admin.py
Normal file
42
cntmanage/flightslot/admin.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from django.contrib import admin
|
||||
from django.http import HttpRequest
|
||||
|
||||
from .models.courses import Course
|
||||
from .models.students import Student
|
||||
from .models.missions import MissionProfile
|
||||
from .models.weekpref import WeekPreference
|
||||
|
||||
from .admins.course_adm import CourseAdmin
|
||||
from .admins.student_adm import StudentAdmin
|
||||
from .admins.mission_adm import MissionProfileAdmin
|
||||
from .admins.weekpred_adm import WeekPreferenceAdmin
|
||||
#from .admins.hourbuilding_adm import HourBuilding, HourBuildingInLine
|
||||
|
||||
from django.contrib.admin import AdminSite
|
||||
|
||||
class FlightSlotUserSite(AdminSite):
|
||||
site_header = "Flight Scheduler 🛫"
|
||||
site_title = "Flight Scheduler 🛫"
|
||||
index_title = "Welcome to CantorAir Flight Scheduler 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
|
||||
|
||||
flightslot_user = FlightSlotUserSite(name="user_site")
|
||||
|
||||
# registra SOLO i modelli autorizzati
|
||||
flightslot_user.register(WeekPreference, WeekPreferenceAdmin)
|
||||
|
||||
admin.site.site_header = "Flight Scheduler Admin 🛫"
|
||||
admin.site.site_title = "Flight Scheduler Admin 🛫"
|
||||
admin.site.index_title = "Welcome to CantorAir Flight Scheduler Administrator Portal"
|
||||
|
||||
admin.site.register(Course, CourseAdmin)
|
||||
admin.site.register(MissionProfile, MissionProfileAdmin)
|
||||
admin.site.register(Student, StudentAdmin)
|
||||
admin.site.register(WeekPreference, WeekPreferenceAdmin)
|
||||
16
cntmanage/flightslot/admins/course_adm.py
Normal file
16
cntmanage/flightslot/admins/course_adm.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.safestring import SafeText
|
||||
|
||||
from ..models.courses import Course
|
||||
from ..custom.colortag import course_color
|
||||
|
||||
class CourseAdmin(admin.ModelAdmin):
|
||||
list_display = ("ctype", "cnumber","color_display", "year")
|
||||
list_filter = ("ctype", "year")
|
||||
|
||||
# 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)
|
||||
87
cntmanage/flightslot/admins/hourbuilding_adm.py
Normal file
87
cntmanage/flightslot/admins/hourbuilding_adm.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import nested_admin
|
||||
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django.forms import TextInput, Textarea
|
||||
from django.http import HttpRequest
|
||||
|
||||
from durationwidget.widgets import TimeDurationWidget
|
||||
|
||||
from ..models.hourbuildings import HourBuilding, HourBuildingLegBase, HourBuildingLegFlight, HourBuildingLegStop
|
||||
from ..models.weekpref import WeekPreference
|
||||
|
||||
from datetime import date
|
||||
|
||||
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", )
|
||||
hide_title = True
|
||||
|
||||
class HourBuildingLegStopInLine(nested_admin.NestedStackedPolymorphicInline.Child):
|
||||
model = HourBuildingLegStop
|
||||
form = HourBuildingLegFlightForm
|
||||
fk_name = "hourbuildinglegbase_ptr"
|
||||
fields = ("time", "refuel", )
|
||||
|
||||
child_inlines = (HourBuildingLegFlightInLine, HourBuildingLegStopInLine, )
|
||||
|
||||
# If user is a student deny edit permission for week past the current one
|
||||
def has_change_permission(self, request: HttpRequest, obj: HourBuilding | None = None):
|
||||
if hasattr(request.user, "student") and obj:
|
||||
current_week = date.today().isocalendar().week
|
||||
if not obj.DoesNotExist and current_week > obj.weekpref.week:
|
||||
return False
|
||||
return True
|
||||
|
||||
def has_delete_permission(self, request: HttpRequest, obj: HourBuilding | None = None):
|
||||
return self.has_change_permission(request=request, obj=obj)
|
||||
|
||||
|
||||
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})},
|
||||
}
|
||||
|
||||
39
cntmanage/flightslot/admins/mission_adm.py
Normal file
39
cntmanage/flightslot/admins/mission_adm.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.safestring import SafeText
|
||||
|
||||
from typing import Any
|
||||
|
||||
from import_export import fields
|
||||
from import_export.admin import ImportMixin
|
||||
from import_export.resources import ModelResource
|
||||
|
||||
from ..models.missions import MissionProfile
|
||||
|
||||
from django_admin_action_forms import AdminActionFormsMixin
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
# 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) -> None:
|
||||
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))
|
||||
return 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")
|
||||
|
||||
class MissionProfileAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||
list_display = ("mtype", "mnum", "notes")
|
||||
list_filter = ("mtype",)
|
||||
75
cntmanage/flightslot/admins/student_adm.py
Normal file
75
cntmanage/flightslot/admins/student_adm.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from django import forms
|
||||
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.resources import ModelResource
|
||||
from import_export.widgets import CharWidget
|
||||
|
||||
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
||||
|
||||
from ..models.courses import Course
|
||||
from ..models.students import Student
|
||||
|
||||
from ..custom.colortag import course_color
|
||||
|
||||
# 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(row["name"].capitalize().strip())
|
||||
row["surname"] = SafeText(row["surname"].capitalize().strip())
|
||||
row["phone"] = SafeText(row["phone"].replace(" ",""))
|
||||
row["email"] = SafeText(row["email"].lower().strip())
|
||||
return super().before_import_row(row, **kwargs)
|
||||
|
||||
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 = forms.ModelChoiceField(queryset=Course.objects.all())
|
||||
|
||||
class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||
list_display = ("surname", "name", "course", "course_color", "email", "phone", "password", "active")
|
||||
list_filter = ("course", "active")
|
||||
actions = ("change_course", "disable_students")
|
||||
resource_classes = [StudentResource]
|
||||
|
||||
@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.action(description="Deactivate Students")
|
||||
def disable_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")
|
||||
pass
|
||||
|
||||
@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}")
|
||||
37
cntmanage/flightslot/admins/training_adm.py
Normal file
37
cntmanage/flightslot/admins/training_adm.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import nested_admin
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django.forms import TextInput, Textarea
|
||||
from django.http import HttpRequest
|
||||
|
||||
from ..models.missions import Training
|
||||
from ..models.weekpref import WeekPreference
|
||||
|
||||
from datetime import date
|
||||
|
||||
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 = {
|
||||
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):
|
||||
if hasattr(request.user, 'student') and obj:
|
||||
current_week: int = date.today().isocalendar().week
|
||||
if current_week > obj.week:
|
||||
return False
|
||||
return True
|
||||
|
||||
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None):
|
||||
return self.has_change_permission(request=request, obj=obj)
|
||||
126
cntmanage/flightslot/admins/weekpred_adm.py
Normal file
126
cntmanage/flightslot/admins/weekpred_adm.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import nested_admin
|
||||
|
||||
from django import forms
|
||||
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.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 ..actions.exportweek import export_selected
|
||||
|
||||
from datetime import date
|
||||
|
||||
class WeekPreferenceAdmin(nested_admin.NestedPolymorphicModelAdmin):
|
||||
inlines = (TrainingInLIne, HourBuildingInLine, )
|
||||
list_display = ("week", "student__surname","student__name", "student__course", "course_color", "student_brief_mix",)
|
||||
list_filter = ("week", "student__course", "student",)
|
||||
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):
|
||||
list_filter = super().get_list_filter(request)
|
||||
if hasattr(request.user, 'student'):
|
||||
return []
|
||||
return list_filter
|
||||
|
||||
# If a user is registered as student do not show actions
|
||||
def get_actions(self, request):
|
||||
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):
|
||||
qs = super().get_queryset(request)
|
||||
if hasattr(request.user, 'student'):
|
||||
return qs.filter(student=request.user.student)
|
||||
# If admin show everything
|
||||
return qs
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form: forms.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 = 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, obj: WeekPreference | None = None):
|
||||
if hasattr(request.user, 'student') and obj:
|
||||
current_week = date.today().isocalendar().week
|
||||
if current_week > obj.week:
|
||||
return False
|
||||
return True
|
||||
|
||||
# If user is a student deny edit permission for week past the current one
|
||||
def has_add_permission(self, request, obj: WeekPreference | None = None):
|
||||
if hasattr(request.user, 'student') and obj:
|
||||
current_week = date.today().isocalendar().week
|
||||
if current_week > obj.week:
|
||||
return False
|
||||
return True
|
||||
|
||||
# If user is a student deny edit permission for week past the current one
|
||||
def has_delete_permission(self, request, obj: WeekPreference | None = None):
|
||||
if hasattr(request.user, 'student') and obj:
|
||||
current_week = date.today().isocalendar().week
|
||||
if current_week > obj.week:
|
||||
return False
|
||||
return True
|
||||
|
||||
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:
|
||||
current_week = date.today().isocalendar().week
|
||||
weekpref = WeekPreference.objects.get(id=object_id)
|
||||
if current_week > weekpref.week:
|
||||
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, obj, form, change):
|
||||
# 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
|
||||
|
||||
|
||||
class CatopsConfig(AppConfig):
|
||||
class FlightslotConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'catops'
|
||||
name = 'flightslot'
|
||||
18
cntmanage/flightslot/middleware.py
Normal file
18
cntmanage/flightslot/middleware.py
Normal file
@@ -0,0 +1,18 @@
|
||||
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/...
|
||||
if hasattr(request,"user"):
|
||||
if (
|
||||
request.path.startswith("/admin/") and
|
||||
hasattr(request.user, 'student')
|
||||
):
|
||||
return redirect("/user/") # redirect automatico
|
||||
|
||||
return self.get_response(request)
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -1,11 +1,11 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
from datetime import timedelta
|
||||
|
||||
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
|
||||
@@ -63,7 +63,10 @@ class HourBuilding(models.Model):
|
||||
blank=True
|
||||
)
|
||||
|
||||
class HourBuildingLeg(models.Model):
|
||||
def __str__(self):
|
||||
return f"Hour Building: {self.aircraft}"
|
||||
|
||||
class HourBuildingLegBase(PolymorphicModel):
|
||||
id = models.BigAutoField(
|
||||
primary_key=True
|
||||
)
|
||||
@@ -73,31 +76,63 @@ class HourBuildingLeg(models.Model):
|
||||
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,
|
||||
default="LILV",
|
||||
max_length=4
|
||||
)
|
||||
|
||||
destination = models.CharField(
|
||||
null=False,
|
||||
blank=False,
|
||||
default="LILV",
|
||||
max_length=4
|
||||
)
|
||||
|
||||
time = models.DurationField(
|
||||
null=False,
|
||||
default = timedelta(hours=1)
|
||||
pax = models.CharField(
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=16,
|
||||
verbose_name="Pax (optional)"
|
||||
)
|
||||
|
||||
stop = models.BooleanField(
|
||||
# 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()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.departure} -> {self.destination}"
|
||||
|
||||
class HourBuildingLegStop(HourBuildingLegBase):
|
||||
|
||||
refuel = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
|
||||
# Change displayed name in the inline form
|
||||
class Meta(HourBuildingLegBase.Meta):
|
||||
verbose_name = "Stop"
|
||||
verbose_name_plural = "Stops"
|
||||
|
||||
def __str__(self):
|
||||
if self.stop:
|
||||
return "Refuelling Stop"
|
||||
else:
|
||||
return f"Flight Leg: {self.departure} -> {self.destination}"
|
||||
return f"Refuel" if self.refuel else f"No Refuel"
|
||||
|
||||
@@ -8,6 +8,7 @@ class MissionType(models.TextChoices):
|
||||
OTHER = "OTHER", _("OTHER")
|
||||
PPL = "PPL", _("PPL")
|
||||
IR = "IR", _("IR")
|
||||
MEP = "MEP", _("MEP")
|
||||
CPL = "CPL", _("CPL")
|
||||
FI = "FI", _("FI")
|
||||
PC = "PC", _("PC")
|
||||
80
cntmanage/flightslot/models/students.py
Normal file
80
cntmanage/flightslot/models/students.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
from ..models.courses import Course
|
||||
|
||||
class Student(models.Model):
|
||||
id = models.AutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
email = models.EmailField(
|
||||
null=False,
|
||||
db_index=True
|
||||
)
|
||||
|
||||
phone = models.CharField(
|
||||
null=True,
|
||||
max_length=16
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
def default_password(self) -> str:
|
||||
return f"{self.name.lower()[0]}{self.surname.lower()}{self.id}"
|
||||
|
||||
# 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,
|
||||
last_name=self.surname,
|
||||
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}"
|
||||
@@ -6,7 +6,7 @@ import sys
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'techdb.settings')
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cntmanage.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
71
techdb/poetry.lock → cntmanage/poetry.lock
generated
71
techdb/poetry.lock → cntmanage/poetry.lock
generated
@@ -2,14 +2,14 @@
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.10.0"
|
||||
version = "3.11.0"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734"},
|
||||
{file = "asgiref-3.10.0.tar.gz", hash = "sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e"},
|
||||
{file = "asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d"},
|
||||
{file = "asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -51,6 +51,21 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
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-colorfield"
|
||||
version = "0.14.0"
|
||||
@@ -83,14 +98,14 @@ Django = ">=1.11"
|
||||
|
||||
[[package]]
|
||||
name = "django-import-export"
|
||||
version = "4.3.13"
|
||||
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.13-py3-none-any.whl", hash = "sha256:197e6a534948f3dcaeb395536e848a3fa267b9620bed5899314736d321fc6fc4"},
|
||||
{file = "django_import_export-4.3.13.tar.gz", hash = "sha256:a0ebff68b470e578d1c05da83d7cb7545be1c42e1531720f49d2d9ec9a26f5bb"},
|
||||
{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]
|
||||
@@ -128,6 +143,48 @@ python-monkey-business = ">=1.0.0"
|
||||
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-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 = "pillow"
|
||||
version = "12.0.0"
|
||||
@@ -379,4 +436,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "8d8926bda0e1b7cdb8a1e57168dd3acc0f3240e34d4e2faf613b844d989dad30"
|
||||
content-hash = "e932d0af75c888d83fecefaaad1d018c508881a3bfde2ea640a82790e3567855"
|
||||
@@ -1,10 +1,12 @@
|
||||
[tool.poetry]
|
||||
name = "techdb"
|
||||
name = "cntmanage"
|
||||
version = "0.1.0"
|
||||
description = "CantorAir Tech Organized Parts Storage"
|
||||
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"
|
||||
@@ -13,6 +15,9 @@ 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"
|
||||
|
||||
|
||||
[build-system]
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 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 |
1
cntmanage/static/password
Normal file
1
cntmanage/static/password
Normal file
@@ -0,0 +1 @@
|
||||
admin: CantorAdmin2k25
|
||||
@@ -7,12 +7,19 @@
|
||||
{% block branding %}
|
||||
|
||||
<h1 id="site-name">
|
||||
<a href="{% url 'admin:index' %}">
|
||||
<img src="{% static 'cantorair.jpg' %}" height="60px" />
|
||||
<a href="{% url 'admin:index' %}" style="color: #0b1728;">
|
||||
<img src="{% static 'cantorair_blue.jpg' %}"
|
||||
height="60px"
|
||||
style="margin-right: 20px;"/>
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
<div id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></div>
|
||||
<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 %}
|
||||
@@ -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,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
|
||||
)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.contrib import admin
|
||||
from django.db.models.functions import Now
|
||||
|
||||
class VendorAdmin(admin.ModelAdmin):
|
||||
list_display = ()
|
||||
list_filter = ()
|
||||
|
||||
class Vendor(models.Model):
|
||||
id = models.UUIDField(primary_key=True)
|
||||
|
||||
reg_date = models.DateTimeField(
|
||||
auto_created=True,
|
||||
editable=False,
|
||||
db_default=Now()
|
||||
)
|
||||
|
||||
active = models.BooleanField(
|
||||
db_default=True
|
||||
)
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
from django.db import models
|
||||
from django.contrib import admin
|
||||
from django.db.models.functions import Now
|
||||
|
||||
from .plane import Plane
|
||||
from .operator import Operator
|
||||
|
||||
class WorkorderAdmin(admin.ModelAdmin):
|
||||
list_display = ()
|
||||
list_filter = []
|
||||
|
||||
class Workorder(models.Model):
|
||||
id = models.BigAutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
reg_date = models.DateTimeField(
|
||||
auto_created=True,
|
||||
editable=False,
|
||||
db_default=Now()
|
||||
)
|
||||
|
||||
date_begin = models.DateTimeField(
|
||||
null=False,
|
||||
db_default=Now()
|
||||
)
|
||||
|
||||
date_end = models.DateTimeField(
|
||||
null=False
|
||||
)
|
||||
|
||||
external_id = models.CharField(
|
||||
max_length=32,
|
||||
db_index=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
plane = models.ForeignKey(
|
||||
Plane,
|
||||
on_delete=models.DO_NOTHING
|
||||
)
|
||||
|
||||
operator = models.ForeignKey(
|
||||
Operator,
|
||||
on_delete=models.DO_NOTHING
|
||||
)
|
||||
|
||||
properties = models.JSONField(
|
||||
null=True
|
||||
)
|
||||
|
||||
active = models.BooleanField(
|
||||
db_default=True
|
||||
)
|
||||
@@ -1,21 +0,0 @@
|
||||
# Use postgres/example user/password credentials
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
|
||||
postgresql:
|
||||
image: postgres:17.0
|
||||
container_name: tech-postgresql
|
||||
restart: always
|
||||
# set shared memory limit when using docker-compose
|
||||
shm_size: 128mb
|
||||
volumes:
|
||||
- /mnt/data/postgresql:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
POSTGRES_USER: tech
|
||||
POSTGRES_PASSWORD: tech
|
||||
POSTGRED_DB: techstorage
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
from django import forms
|
||||
from django.db.models.query import QuerySet
|
||||
from django.contrib import admin
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.safestring import SafeText
|
||||
from durationwidget.widgets import TimeDurationWidget
|
||||
from datetime import date
|
||||
|
||||
import nested_admin
|
||||
import csv
|
||||
|
||||
from .models.courses import Course
|
||||
from .models.hourbuildings import HourBuilding, HourBuildingLeg
|
||||
from .models.missions import Training, MissionProfile
|
||||
from .models.students import Student
|
||||
from .models.weekpref import WeekPreference
|
||||
|
||||
from .custom.colortag import course_color
|
||||
from .custom.defpassword import default_password
|
||||
|
||||
class TrainingForm(forms.ModelForm):
|
||||
model=Training
|
||||
|
||||
class HourBuildingLegForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = HourBuildingLeg
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'time': TimeDurationWidget(show_days=False,
|
||||
show_seconds=False
|
||||
)
|
||||
}
|
||||
|
||||
# Register your models here.
|
||||
class HourBuildingLegInline(nested_admin.NestedTabularInline):
|
||||
model = HourBuildingLeg
|
||||
form = HourBuildingLegForm
|
||||
extra = 0
|
||||
fk_name = 'hb'
|
||||
max_num = 5
|
||||
|
||||
class HourBuildingInLine(nested_admin.NestedTabularInline):
|
||||
model = HourBuilding
|
||||
extra = 0
|
||||
inlines = [HourBuildingLegInline]
|
||||
fk_name = 'weekpref'
|
||||
verbose_name_plural = "Hour Building"
|
||||
max_num = 7
|
||||
|
||||
class TrainingInLIne(nested_admin.NestedTabularInline):
|
||||
model = Training
|
||||
form = TrainingForm
|
||||
extra = 0
|
||||
fk_name = 'weekpref'
|
||||
verbose_name_plural = "Training Missions"
|
||||
max_num = 7
|
||||
|
||||
class WeekPreferenceAdmin(nested_admin.NestedModelAdmin):
|
||||
inlines = [TrainingInLIne, HourBuildingInLine]
|
||||
list_display = ("week", "student__name", "student__surname", "student__course", "course_color", "student_brief_mix")
|
||||
list_filter = ("week", "student__course", "student")
|
||||
actions = ("export_selected",)
|
||||
|
||||
@admin.action(description="Export Selected Preferences")
|
||||
def export_selected(modeladmin, request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse:
|
||||
filename = "weekpreferences_export.csv"
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
|
||||
writer = csv.writer(response)
|
||||
# intestazione — scegli i campi che vuoi esportare
|
||||
writer.writerow(['student_name', 'student_surname', 'course', 'course_color', "training", "hourbuilding"])
|
||||
|
||||
for q in queryset:
|
||||
writer.writerow([
|
||||
q.student.name,
|
||||
q.student.surname,
|
||||
q.student.course,
|
||||
q.student.course.color,
|
||||
"+".join([f"{t.mission.mtype}-{t.mission.mnum}" for t in Training.objects.filter(weekpref = q.id)]),
|
||||
"+".join([f"HB_{t.aircraft}" for t in HourBuilding.objects.filter(weekpref = q.id)]),
|
||||
])
|
||||
return response
|
||||
|
||||
@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)
|
||||
|
||||
def has_module_permission(self, request):
|
||||
if hasattr(request.user, 'student'):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
# If a user is registered as student show only their preferences
|
||||
if hasattr(request.user, 'student'):
|
||||
return qs.filter(student=request.user.student)
|
||||
# If admin show everything
|
||||
return qs
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form: forms.Form = super().get_form(request, obj, **kwargs)
|
||||
|
||||
# If student is current user making request
|
||||
if hasattr(request.user, '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
|
||||
|
||||
# If form contains the week field
|
||||
if 'week' in form.base_fields:
|
||||
# Set default value as current week
|
||||
current_week = date.today().isocalendar().week
|
||||
form.base_fields['week'].initial = current_week
|
||||
return form
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
# 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)
|
||||
|
||||
class StudentAdmin(admin.ModelAdmin):
|
||||
list_display = ("surname", "name", "course", "course_color", "email", "phone", "password", "active")
|
||||
list_filter = ("course", "active")
|
||||
actions = ("disable_students",)
|
||||
|
||||
@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(default_password(student=obj))
|
||||
|
||||
@admin.action(description="Disable Students")
|
||||
def disable_students(modeladmin, request: HttpRequest, queryset: QuerySet[Student]):
|
||||
queryset.update(active = False)
|
||||
pass
|
||||
|
||||
class CourseAdmin(admin.ModelAdmin):
|
||||
list_display = ("ctype", "cnumber","color_display", "year")
|
||||
list_filter = ("ctype", "year")
|
||||
|
||||
# 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)
|
||||
|
||||
class MissionProfileAdmin(admin.ModelAdmin):
|
||||
list_display = ("mtype", "mnum",)
|
||||
list_filter = ("mtype",)
|
||||
|
||||
admin.site.register(Course, CourseAdmin)
|
||||
admin.site.register(MissionProfile, MissionProfileAdmin)
|
||||
admin.site.register(Student, StudentAdmin)
|
||||
admin.site.register(WeekPreference, WeekPreferenceAdmin)
|
||||
@@ -1,9 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class FlightslotConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'flightslot'
|
||||
|
||||
def ready(self):
|
||||
# Import only when application is ready otherwise signals will not be called
|
||||
from . import signals
|
||||
@@ -1,4 +0,0 @@
|
||||
from ..models.students import Student
|
||||
|
||||
def default_password(student: Student) -> str:
|
||||
return f"{student.name.lower()[0]}{student.surname.lower()}{student.id}"
|
||||
@@ -1,50 +0,0 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from ..models.courses import Course
|
||||
|
||||
class Student(models.Model):
|
||||
id = models.AutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
email = models.EmailField(
|
||||
null=False,
|
||||
db_index=True
|
||||
)
|
||||
|
||||
phone = models.CharField(
|
||||
null=True,
|
||||
max_length=16
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.surname} {self.name[0]}. => {self.course}"
|
||||
@@ -1,30 +0,0 @@
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.models import Group
|
||||
from .models.students import Student
|
||||
from .custom.defpassword import default_password
|
||||
|
||||
|
||||
# Create a Django user every time a new student is created
|
||||
@receiver(post_save, sender=Student)
|
||||
def create_user_for_student(sender: Student, student: Student, created, **kwargs):
|
||||
if created and not student.user:
|
||||
username = f"{student.name.lower()}.{student.surname.lower()}"
|
||||
# Avoid username conflict with progressive number
|
||||
base_username = username
|
||||
counter = 1
|
||||
while User.objects.filter(username=username).exists():
|
||||
username = f"{base_username}{counter}"
|
||||
counter += 1
|
||||
# Create user
|
||||
user = User.objects.create_user(
|
||||
username=username,
|
||||
email=student.email,
|
||||
password=default_password(student=student)
|
||||
)
|
||||
|
||||
student_group, _ = Group.objects.get_or_create(name="StudentGroup")
|
||||
user.groups.add(student_group)
|
||||
student.user = user
|
||||
student.save()
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -1,26 +0,0 @@
|
||||
"""
|
||||
URL configuration for techdb project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.1/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
|
||||
admin.site.site_header = "CantorAir Flight Scheduler"
|
||||
admin.site.site_title = "CantorAir Flight Scheduler"
|
||||
admin.site.index_title = "Welcome to CantorAir Flight Scheduler Portal"
|
||||
Reference in New Issue
Block a user