31 Commits

Author SHA1 Message Date
95370ed0dc Improved excel formatting 2025-11-24 12:20:42 +01:00
bb634d28ed improved excel formatting 2025-11-23 12:32:12 +01:00
cbdf49adfd Improved class naming for admin 2025-11-21 19:11:21 +01:00
7ad09e21b7 Improved time widget visualization 2025-11-21 18:57:44 +01:00
48ff1d799c Polymorphic model correctly saves, fixed formatting of admin 2025-11-21 17:49:51 +01:00
02990d4b2f Nested polymorphic hour building legs and stops added, needs restyling 2025-11-21 15:25:29 +01:00
bcdc885d66 fix titles for user 2025-11-19 22:07:41 +01:00
674105600e Improved visuals of admin page login 2025-11-19 21:53:40 +01:00
aa24976abb fix hb description 2025-11-19 19:06:30 +01:00
37b1ed4aa1 added custom hide sidebar middleware to prod 2025-11-19 18:55:32 +01:00
5f5c03e479 Autobuild docker imahe for application 2025-11-19 18:48:21 +01:00
78f53cae7d Disable sidebar for students 2025-11-19 18:20:16 +01:00
72891440af improved compose and entry point files 2025-11-19 16:40:42 +01:00
ae86a2e5fa Deploy application in container using compose to bring up db 2025-11-19 13:37:34 +01:00
d5befdd018 Changed settings project name, removed tech db stuff 2025-11-18 23:34:17 +01:00
491afb6257 Renamed Project to cntmanage 2025-11-18 23:28:50 +01:00
9cae53a942 Import mission profiles and better format text fields 2025-11-18 23:24:38 +01:00
79d7333ca0 Admin files refactoring 2025-11-18 22:44:16 +01:00
edb54e9f6f Bulk import students, bulk change course 2025-11-18 22:18:12 +01:00
34eabe6af7 Fixed permission on add, change, delete based on week number for students 2025-11-18 16:34:50 +01:00
71809d331f Fixed sheet formatting 2025-11-18 14:52:05 +01:00
231a3e9861 Cell formatting OK, notes with hb details 2025-11-17 19:24:51 +01:00
a91e0cd7bc Exporter first version, merged cells broken 2025-11-17 15:19:20 +01:00
bb9ff3a86c Demo export action and added color badges where needed 2025-11-14 15:47:18 +01:00
be25a07272 Refactored model files 2025-11-14 12:01:14 +01:00
ea33bef9cd Improved Model Admin views, student adds user for login 2025-11-11 22:18:39 +01:00
637d109a91 Templates and Admin personalization 2024-10-28 11:18:20 +01:00
c8e99e9ab8 Fixed Interface to add weekpreference 2024-10-20 17:08:40 +02:00
78c2e45ca4 Better inLine version with nested_admin 2024-10-20 11:24:24 +02:00
eb9018928f Nested WeekPreference view in admin with nested_inline 2024-10-20 10:46:19 +02:00
adf388f7ae First version of flight preference forms 2024-10-19 19:56:44 +02:00
84 changed files with 2534 additions and 984 deletions

18
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Django",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/cntmanage/manage.py",
"args": ["runserver"],
"console": "integratedTerminal",
"django": true,
"justMyCode": false
}
]
}

View File

@@ -11,6 +11,7 @@ import os
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'techdb.settings') settings = os.environ.get("DJANGO_SETTINGS_MODULE", "cntmanage.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings)
application = get_asgi_application() application = get_asgi_application()

View File

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.1/ref/settings/
""" """
from pathlib import Path from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@@ -27,7 +28,6 @@ DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
@@ -37,10 +37,23 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'catops', 'nested_admin',
'flightslot',
'durationwidget',
'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 = [ MIDDLEWARE = [
'flightslot.middleware.RedirectNonSuperuserFromAdminMiddleware', # custom middleware to show "user" page to non superuser
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
@@ -50,12 +63,12 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
ROOT_URLCONF = 'techdb.urls' ROOT_URLCONF = 'cntmanage.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [], 'DIRS': [os.path.join(BASE_DIR, 'templates/')],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@@ -68,8 +81,11 @@ TEMPLATES = [
}, },
] ]
WSGI_APPLICATION = 'techdb.wsgi.application' WSGI_APPLICATION = 'cntmanage.wsgi.application'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
# Database # Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases # https://docs.djangoproject.com/en/5.1/ref/settings/#databases
@@ -77,9 +93,9 @@ WSGI_APPLICATION = 'techdb.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.postgresql', 'ENGINE': 'django.db.backends.postgresql',
'NAME': 'techstorage', 'NAME': 'flightslot_db',
'USER': 'tech', 'USER': 'flightslot',
'PASSWORD': 'tech', 'PASSWORD': 'flightslot',
'HOST': 'localhost', 'HOST': 'localhost',
'PORT': '5432' 'PORT': '5432'
} }
@@ -112,9 +128,9 @@ LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC' TIME_ZONE = 'UTC'
USE_I18N = True USE_I18N = False # Disable translation engine
USE_TZ = True USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)

View 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'

View 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
]

View File

@@ -11,6 +11,7 @@ import os
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'techdb.settings') settings = os.environ.get("DJANGO_SETTINGS_MODULE", "cntmanage.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings)
application = get_wsgi_application() application = get_wsgi_application()

View 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
View 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 "$@"

View 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"]

View 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

View 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)

View 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)

View 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})},
}

View 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",)

View 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}")

View 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)

View 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)

View File

@@ -1,6 +1,5 @@
from django.apps import AppConfig from django.apps import AppConfig
class FlightslotConfig(AppConfig):
class CatopsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'catops' name = 'flightslot'

View File

@@ -0,0 +1,18 @@
from colorfield.fields import ColorField
from django.utils.html import format_html
from django.utils.safestring import SafeText
def course_color(color: ColorField | None) -> SafeText:
if not color:
return format_html("")
return format_html(
'<div style="background-color: {}; \
width: 20px; height: 20px; \
border-radius: 25%; \
border-color: gray; \
border-style: solid; \
border-width: 1px; \
display: inline-block; \
"></div>',
color
)

View 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)

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.1.2 on 2024-10-19 16:07
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Student',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('email', models.EmailField(db_index=True, max_length=254)),
('name', models.CharField(max_length=32)),
('surname', models.CharField(max_length=32)),
],
),
]

View File

@@ -0,0 +1,47 @@
# Generated by Django 5.1.2 on 2024-10-19 17:49
import django.db.models.deletion
import django.db.models.expressions
import django.db.models.functions.datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='MissionProfile',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('mtype', models.IntegerField(choices=[('OTHER', 'OTHER'), ('HB', 'HB'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='HB')),
('mnum', models.PositiveSmallIntegerField(default=0, null=True)),
('duration', models.DurationField()),
],
),
migrations.CreateModel(
name='WeekPreference',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('week', models.PositiveSmallIntegerField(db_default=django.db.models.expressions.CombinedExpression(django.db.models.functions.datetime.ExtractWeek(django.db.models.functions.datetime.Now()), '+', models.Value(1)), db_index=True)),
('student', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.student')),
],
),
migrations.CreateModel(
name='Preference',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('monday', models.BooleanField(default=True)),
('tuesday', models.BooleanField(default=True)),
('wednesday', models.BooleanField(default=True)),
('thursday', models.BooleanField(default=True)),
('saturday', models.BooleanField(default=True)),
('sunday', models.BooleanField(default=True)),
('mission', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.missionprofile')),
('weekpref', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.weekpreference')),
],
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-10-19 17:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0002_missionprofile_weekpreference_preference'),
]
operations = [
migrations.AlterField(
model_name='missionprofile',
name='mtype',
field=models.CharField(choices=[('OTHER', 'OTHER'), ('HB', 'HB'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='HB'),
),
]

View File

@@ -0,0 +1,65 @@
# Generated by Django 5.1.2 on 2024-10-20 07:57
import django.db.models.deletion
import django.db.models.expressions
import django.db.models.functions.datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0003_alter_missionprofile_mtype'),
]
operations = [
migrations.RenameModel(
old_name='Preference',
new_name='Training',
),
migrations.AddField(
model_name='missionprofile',
name='notes',
field=models.TextField(max_length=140, null=True),
),
migrations.AlterField(
model_name='missionprofile',
name='duration',
field=models.DurationField(default=1),
),
migrations.AlterField(
model_name='missionprofile',
name='mtype',
field=models.CharField(choices=[('OTHER', 'OTHER'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='PPL'),
),
migrations.AlterField(
model_name='weekpreference',
name='week',
field=models.PositiveSmallIntegerField(auto_created=True, db_index=True, default=django.db.models.expressions.CombinedExpression(django.db.models.functions.datetime.ExtractWeek(django.db.models.functions.datetime.Now()), '+', models.Value(1))),
),
migrations.CreateModel(
name='HourBuilding',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('aircraft', models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('C182', 'Cessna 182Q'), ('P210', 'Tecnam P2010')])),
('monday', models.BooleanField(default=True)),
('tuesday', models.BooleanField(default=True)),
('wednesday', models.BooleanField(default=True)),
('thursday', models.BooleanField(default=True)),
('saturday', models.BooleanField(default=True)),
('sunday', models.BooleanField(default=True)),
('weekpref', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.weekpreference')),
],
),
migrations.CreateModel(
name='HourBuildingLeg',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('departure', models.CharField(default='LILV', max_length=4)),
('destination', models.CharField(default='LILV', max_length=4)),
('time', models.DurationField(default=1)),
('stop', models.BooleanField(default=False)),
('hb', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.hourbuilding')),
],
),
]

View File

@@ -0,0 +1,39 @@
# Generated by Django 5.1.2 on 2024-10-20 08:28
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0004_rename_preference_training_missionprofile_notes_and_more'),
]
operations = [
migrations.AddField(
model_name='hourbuilding',
name='notes',
field=models.TextField(max_length=140, null=True),
),
migrations.AddField(
model_name='training',
name='notes',
field=models.TextField(max_length=140, null=True),
),
migrations.AlterField(
model_name='hourbuilding',
name='weekpref',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.weekpreference'),
),
migrations.AlterField(
model_name='training',
name='mission',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='flightslot.missionprofile'),
),
migrations.AlterField(
model_name='training',
name='weekpref',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.weekpreference'),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.1.2 on 2024-10-20 09:14
import django.db.models.deletion
import django.db.models.functions.datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0005_hourbuilding_notes_training_notes_and_more'),
]
operations = [
migrations.CreateModel(
name='Course',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('ctype', models.CharField(choices=[('PPL', 'PPL'), ('ATPL', 'ATPL')])),
('cnumber', models.PositiveSmallIntegerField(default=django.db.models.functions.datetime.ExtractYear(django.db.models.functions.datetime.Now()))),
],
),
migrations.AddField(
model_name='student',
name='active',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='student',
name='course',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.course'),
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 5.1.2 on 2024-10-20 09:27
import django.db.models.functions.datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0006_course_student_active_student_course'),
]
operations = [
migrations.AddField(
model_name='course',
name='year',
field=models.PositiveSmallIntegerField(db_default=django.db.models.functions.datetime.ExtractYear(django.db.models.functions.datetime.Now()), editable=False),
),
migrations.AlterField(
model_name='hourbuilding',
name='notes',
field=models.TextField(blank=True, max_length=140, null=True),
),
migrations.AlterField(
model_name='missionprofile',
name='notes',
field=models.TextField(blank=True, max_length=140, null=True),
),
migrations.AlterField(
model_name='training',
name='notes',
field=models.TextField(blank=True, max_length=140, null=True),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.1.2 on 2024-10-20 10:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0007_course_year_alter_hourbuilding_notes_and_more'),
]
operations = [
migrations.AddField(
model_name='hourbuilding',
name='friday',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='training',
name='friday',
field=models.BooleanField(default=True),
),
]

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.2.8 on 2025-11-11 17:45
import django.db.models.deletion
import django.db.models.expressions
import django.db.models.functions.datetime
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0008_hourbuilding_friday_training_friday'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='student',
name='user',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='missionprofile',
name='mnum',
field=models.PositiveSmallIntegerField(default=0, null=True, verbose_name='Mission Number'),
),
migrations.AlterField(
model_name='missionprofile',
name='mtype',
field=models.CharField(choices=[('OTHER', 'OTHER'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='PPL', verbose_name='Mission Type'),
),
migrations.AlterField(
model_name='weekpreference',
name='student',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.student', verbose_name='Student Selection'),
),
migrations.AlterField(
model_name='weekpreference',
name='week',
field=models.PositiveSmallIntegerField(auto_created=True, db_index=True, default=django.db.models.expressions.CombinedExpression(django.db.models.functions.datetime.ExtractWeek(django.db.models.functions.datetime.Now()), '+', models.Value(1)), verbose_name='Week Number'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.8 on 2025-11-11 20:31
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0009_student_user_alter_missionprofile_mnum_and_more'),
]
operations = [
migrations.AlterField(
model_name='hourbuildingleg',
name='time',
field=models.DurationField(default=models.DurationField(default=datetime.timedelta(0))),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.8 on 2025-11-11 20:33
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0010_alter_hourbuildingleg_time'),
]
operations = [
migrations.AlterField(
model_name='hourbuildingleg',
name='time',
field=models.DurationField(default=datetime.timedelta(seconds=3600)),
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 5.2.8 on 2025-11-12 09:47
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0011_alter_hourbuildingleg_time'),
]
operations = [
migrations.AlterField(
model_name='course',
name='cnumber',
field=models.PositiveSmallIntegerField(default=2025),
),
migrations.AlterField(
model_name='course',
name='year',
field=models.PositiveSmallIntegerField(db_default=2025, editable=False),
),
migrations.AlterField(
model_name='missionprofile',
name='duration',
field=models.DurationField(default=datetime.timedelta(seconds=3600)),
),
migrations.AlterField(
model_name='weekpreference',
name='week',
field=models.PositiveSmallIntegerField(auto_created=True, db_default=46, db_index=True, verbose_name='Week Number'),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.8 on 2025-11-12 10:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0012_alter_course_cnumber_alter_course_year_and_more'),
]
operations = [
migrations.AddField(
model_name='course',
name='color',
field=models.CharField(choices=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')], default='#ffffff'),
),
migrations.AlterField(
model_name='course',
name='ctype',
field=models.CharField(choices=[('FI', 'FI'), ('PPL', 'PPL'), ('ATPL', 'ATPL'), ('DL', 'DISTANCE'), ('OTHER', 'OTHER')]),
),
migrations.AlterField(
model_name='course',
name='year',
field=models.PositiveSmallIntegerField(default=2025),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.8 on 2025-11-12 10:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0013_course_color_alter_course_ctype_alter_course_year'),
]
operations = [
migrations.AddField(
model_name='student',
name='phone',
field=models.CharField(max_length=16, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.8 on 2025-11-12 10:31
import colorfield.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0014_student_phone'),
]
operations = [
migrations.AlterField(
model_name='course',
name='color',
field=colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')]),
),
]

View File

@@ -0,0 +1,39 @@
# Generated by Django 5.2.8 on 2025-11-14 14:42
import colorfield.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0015_alter_course_color'),
]
operations = [
migrations.AlterField(
model_name='course',
name='cnumber',
field=models.PositiveSmallIntegerField(default=2025, verbose_name='Course Number'),
),
migrations.AlterField(
model_name='course',
name='color',
field=colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')], verbose_name='Binder Color'),
),
migrations.AlterField(
model_name='course',
name='ctype',
field=models.CharField(choices=[('FI', 'FI'), ('PPL', 'PPL'), ('ATPL', 'ATPL'), ('DL', 'DISTANCE'), ('OTHER', 'OTHER')], verbose_name='Course Type'),
),
migrations.AlterField(
model_name='course',
name='year',
field=models.PositiveSmallIntegerField(default=2025, verbose_name='Year'),
),
migrations.AlterField(
model_name='hourbuilding',
name='aircraft',
field=models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010')]),
),
]

View File

@@ -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'),
),
]

View File

@@ -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',
),
]

View File

@@ -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),
),
]

View File

@@ -0,0 +1,9 @@
from django.utils.translation import gettext_lazy as _
from django.db import models
class AircraftTypes(models.TextChoices):
C152 = "C152", _("Cessna 152")
P208 = "P208", _("Tecnam P2008")
PA28 = "PA28", _("Piper PA28R")
C182 = "C182", _("Cessna 182Q")
P210 = "TWEN", _("Tecnam P2010")

View File

@@ -0,0 +1,52 @@
from django.utils.translation import gettext_lazy as _
from django.db import models
from datetime import date
from colorfield.fields import ColorField
class CourseTypes(models.TextChoices):
FI = "FI", _("FI")
PPL = "PPL", _("PPL")
ATPL = "ATPL", _("ATPL")
DISTANCE = "DL", _("DISTANCE")
OTHER = "OTHER",_("OTHER")
class Course(models.Model):
# Add colors according to table from Alessia
COLOR_PALETTE = [
("#ffffff","WHITE"),
("#ff0000", "RED"),
("#00ff00", "GREEN"),
("#0000ff", "BLUE")
]
id = models.AutoField(
primary_key=True
)
ctype = models.CharField(
null=False,
choices=CourseTypes,
verbose_name=_("Course Type")
)
cnumber = models.PositiveSmallIntegerField(
null=False,
default=date.today().year,
verbose_name=_("Course Number")
)
year = models.PositiveSmallIntegerField(
null=False,
default=date.today().year,
verbose_name=_("Year")
)
color = ColorField (
samples=COLOR_PALETTE,
verbose_name=_("Binder Color")
)
def __str__(self):
return f"{self.ctype}-{self.cnumber}"

View File

@@ -0,0 +1,138 @@
from django.utils.translation import gettext_lazy as _
from django.db import models
from polymorphic.models import PolymorphicModel
from ..models.weekpref import WeekPreference
from ..models.aircrafts import AircraftTypes
class HourBuilding(models.Model):
id = models.BigAutoField(
primary_key=True
)
weekpref = models.ForeignKey(
WeekPreference,
null=False,
on_delete=models.CASCADE
)
aircraft = models.CharField(
null=False,
choices=AircraftTypes
)
monday = models.BooleanField(
default=True,
null=False
)
tuesday = models.BooleanField(
default=True,
null=False
)
wednesday = models.BooleanField(
default=True,
null=False
)
thursday = models.BooleanField(
default=True,
null=False
)
friday = models.BooleanField(
default=True,
null=False
)
saturday = models.BooleanField(
default=True,
null=False
)
sunday = models.BooleanField(
default=True,
null=False
)
notes = models.TextField(
max_length=140,
null=True,
blank=True
)
def __str__(self):
return f"Hour Building: {self.aircraft}"
class HourBuildingLegBase(PolymorphicModel):
id = models.BigAutoField(
primary_key=True
)
hb = models.ForeignKey(
HourBuilding,
on_delete=models.CASCADE
)
time = models.DurationField(
null=False,
blank=False
)
# Change displayed name in the inline form
class Meta(PolymorphicModel.Meta):
verbose_name = "Flight Leg or Stop"
verbose_name_plural = "Flight Legs or Stops"
def __str__(self):
return f"Hour Building Leg"
class HourBuildingLegFlight(HourBuildingLegBase):
departure = models.CharField(
null=False,
blank=False,
max_length=4
)
destination = models.CharField(
null=False,
blank=False,
max_length=4
)
pax = models.CharField(
null=True,
blank=True,
max_length=16,
verbose_name="Pax (optional)"
)
# Change displayed name in the inline form
class Meta(HourBuildingLegBase.Meta):
verbose_name = "Flight leg"
verbose_name_plural = "Flight legs"
def save(self, *args, **kwargs):
self.departure = self.departure.upper().strip()
self.destination = self.destination.upper().strip()
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):
return f"Refuel" if self.refuel else f"No Refuel"

View File

@@ -0,0 +1,109 @@
from django.utils.translation import gettext_lazy as _
from django.db import models
from datetime import timedelta
from ..models.weekpref import WeekPreference
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")
CHK = "CHK", _("CHK_6M")
class MissionProfile(models.Model):
id = models.AutoField(
primary_key=True
)
mtype = models.CharField(
null=False,
default=MissionType.PPL,
choices=MissionType,
verbose_name="Mission Type"
)
mnum = models.PositiveSmallIntegerField(
null=True,
default=0,
verbose_name="Mission Number"
)
duration = models.DurationField(
null=False,
default=timedelta(hours=1)
)
notes = models.TextField(
max_length=140,
null=True,
blank=True
)
def __str__(self):
return f"{self.mtype} {self.mnum}"
class Training(models.Model):
id = models.BigAutoField(
primary_key=True
)
weekpref = models.ForeignKey(
WeekPreference,
null=False,
on_delete=models.CASCADE
)
mission = models.ForeignKey(
MissionProfile,
null=True,
on_delete=models.CASCADE
)
monday = models.BooleanField(
default=True,
null=False
)
tuesday = models.BooleanField(
default=True,
null=False
)
wednesday = models.BooleanField(
default=True,
null=False
)
thursday = models.BooleanField(
default=True,
null=False
)
friday = models.BooleanField(
default=True,
null=False
)
saturday = models.BooleanField(
default=True,
null=False
)
sunday = models.BooleanField(
default=True,
null=False
)
notes = models.TextField(
max_length=140,
null=True,
blank=True
)
def __str__(self):
return f"{self.mission}"

View 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}"

View File

@@ -0,0 +1,28 @@
from django.db import models
from datetime import date
from ..models.students import Student
class WeekPreference(models.Model):
id = models.BigAutoField(
primary_key=True
)
week = models.PositiveSmallIntegerField(
null=False,
db_index=True,
db_default=date.today().isocalendar().week,
auto_created=True,
verbose_name="Week Number"
)
student = models.ForeignKey(
Student,
null=False,
db_index=True,
on_delete=models.DO_NOTHING,
verbose_name="Student Selection"
)
def __str__(self):
return f"Week {self.week} - {self.student.surname} {self.student.name[0]}."

View File

@@ -6,7 +6,7 @@ import sys
def main(): def main():
"""Run administrative tasks.""" """Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'techdb.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cntmanage.settings')
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError as exc: except ImportError as exc:

439
cntmanage/poetry.lock generated Normal file
View File

@@ -0,0 +1,439 @@
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "asgiref"
version = "3.11.0"
description = "ASGI specs, helper code, and adapters"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d"},
{file = "asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4"},
]
[package.extras]
tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"]
[[package]]
name = "diff-match-patch"
version = "20241021"
description = "Repackaging of Google's Diff Match and Patch libraries."
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "diff_match_patch-20241021-py3-none-any.whl", hash = "sha256:93cea333fb8b2bc0d181b0de5e16df50dd344ce64828226bda07728818936782"},
{file = "diff_match_patch-20241021.tar.gz", hash = "sha256:beae57a99fa48084532935ee2968b8661db861862ec82c6f21f4acdd6d835073"},
]
[package.extras]
dev = ["attribution (==1.8.0)", "black (==24.8.0)", "build (>=1)", "flit (==3.9.0)", "mypy (==1.12.1)", "ufmt (==2.7.3)", "usort (==1.0.8.post1)"]
[[package]]
name = "django"
version = "5.2.8"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "django-5.2.8-py3-none-any.whl", hash = "sha256:37e687f7bd73ddf043e2b6b97cfe02fcbb11f2dbb3adccc6a2b18c6daa054d7f"},
{file = "django-5.2.8.tar.gz", hash = "sha256:23254866a5bb9a2cfa6004e8b809ec6246eba4b58a7589bc2772f1bcc8456c7f"},
]
[package.dependencies]
asgiref = ">=3.8.1"
sqlparse = ">=0.3.1"
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "django-admin-action-forms"
version = "2.2.1"
description = "Extension for the Django admin panel that allows passing additional parameters to actions by creating intermediate pages with forms."
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "django_admin_action_forms-2.2.1-py3-none-any.whl", hash = "sha256:597d20d36fcb6cfbb0b5e0ed83df0c9dbd5af6b225f7af24f5b96a2ed84d4d35"},
{file = "django_admin_action_forms-2.2.1.tar.gz", hash = "sha256:94ff59964ece5d6b8d2c9c307f22837863be173e3fb64fdcd64d6f301e1d0c9d"},
]
[package.dependencies]
django = ">=3.2"
[[package]]
name = "django-colorfield"
version = "0.14.0"
description = "color field for django models with a nice color-picker in the admin."
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "django_colorfield-0.14.0-py3-none-any.whl", hash = "sha256:f169c4e7ad8f336e51d4ea81f866346e7d4f336f3766e54e144cd16ea7d84a0e"},
{file = "django_colorfield-0.14.0.tar.gz", hash = "sha256:478dbd3975a88f2ea2a9afc325faaca05c54ebf04ec985ce130f6dea39dfb899"},
]
[package.dependencies]
Pillow = ">=9.0.0"
[[package]]
name = "django-durationwidget"
version = "1.0.5"
description = "Django Duration field widget to handle duration field in the form"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "django-durationwidget-1.0.5.tar.gz", hash = "sha256:a77bba14c173cbfe50071ae08759ca37218acb8567639687b0119647cb78c53f"},
{file = "django_durationwidget-1.0.5-py3-none-any.whl", hash = "sha256:7d14d5dfcf46c444fb203fe911cd058f78ba482c81ea2f0665db08369a1eff47"},
]
[package.dependencies]
Django = ">=1.11"
[[package]]
name = "django-import-export"
version = "4.3.14"
description = "Django application and library for importing and exporting data with included admin integration."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "django_import_export-4.3.14-py3-none-any.whl", hash = "sha256:ce6484fa082a1cdb2bf4e0b60276d3e2a7f39f74c20ae663b2f8eebb54141a58"},
{file = "django_import_export-4.3.14.tar.gz", hash = "sha256:224c7d909fec607378bc58271db38b9c6065982306aa644d26a529fcde64869e"},
]
[package.dependencies]
diff-match-patch = "20241021"
Django = ">=4.2"
tablib = ">=3.7.0"
[package.extras]
all = ["tablib[all]"]
cli = ["tablib[cli]"]
docs = ["openpyxl (==3.1.5)", "psycopg[binary] (>=3.2.9)", "sphinx (==8.1.3)", "sphinx-rtd-theme (==3.0.1)"]
ods = ["tablib[ods]"]
pandas = ["tablib[pandas]"]
tests = ["chardet (==5.2.0)", "coverage (==7.6.4)", "django-extensions (==3.2.3)", "memory-profiler (==0.61.0)", "mysqlclient (==2.2.5)", "psycopg[binary] (>=3.2.9)", "pytz (==2024.2)", "setuptools-scm (==8.1.0)", "tablib[all] (>=3.7.0)"]
xls = ["tablib[xls]"]
xlsx = ["tablib[xlsx]"]
yaml = ["tablib[yaml]"]
[[package]]
name = "django-nested-admin"
version = "4.1.6"
description = "Django admin classes that allow for nested inlines"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "django_nested_admin-4.1.6-py3-none-any.whl", hash = "sha256:ae3e1d910631e6cc3b5815d80129f18c6c081b7dd7f775bf392d9bb1ac88ef93"},
{file = "django_nested_admin-4.1.6.tar.gz", hash = "sha256:0222475cc343e7b8813d7a5db583cddef15f5c0f44dbe17b91b4d682db7bd73a"},
]
[package.dependencies]
python-monkey-business = ">=1.0.0"
[package.extras]
dev = ["Pillow", "black", "dj-database-url", "django-selenosis", "flake8", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "selenium"]
test = ["Pillow", "dj-database-url", "django-selenosis", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "selenium"]
[[package]]
name = "django-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"
description = "Python Imaging Library (fork)"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"},
{file = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"},
{file = "pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363"},
{file = "pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca"},
{file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e"},
{file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782"},
{file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10"},
{file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa"},
{file = "pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275"},
{file = "pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d"},
{file = "pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7"},
{file = "pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc"},
{file = "pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257"},
{file = "pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642"},
{file = "pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3"},
{file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c"},
{file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227"},
{file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b"},
{file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e"},
{file = "pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739"},
{file = "pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e"},
{file = "pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d"},
{file = "pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371"},
{file = "pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082"},
{file = "pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f"},
{file = "pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d"},
{file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953"},
{file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8"},
{file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79"},
{file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba"},
{file = "pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0"},
{file = "pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a"},
{file = "pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad"},
{file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643"},
{file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4"},
{file = "pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399"},
{file = "pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5"},
{file = "pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b"},
{file = "pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3"},
{file = "pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07"},
{file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e"},
{file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344"},
{file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27"},
{file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79"},
{file = "pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098"},
{file = "pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905"},
{file = "pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a"},
{file = "pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3"},
{file = "pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced"},
{file = "pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b"},
{file = "pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d"},
{file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a"},
{file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe"},
{file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee"},
{file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef"},
{file = "pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9"},
{file = "pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b"},
{file = "pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47"},
{file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9"},
{file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2"},
{file = "pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a"},
{file = "pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b"},
{file = "pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad"},
{file = "pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01"},
{file = "pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c"},
{file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e"},
{file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e"},
{file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9"},
{file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab"},
{file = "pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b"},
{file = "pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b"},
{file = "pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0"},
{file = "pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6"},
{file = "pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6"},
{file = "pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1"},
{file = "pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e"},
{file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca"},
{file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925"},
{file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8"},
{file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4"},
{file = "pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52"},
{file = "pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a"},
{file = "pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7"},
{file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8"},
{file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a"},
{file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197"},
{file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c"},
{file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e"},
{file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76"},
{file = "pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5"},
{file = "pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353"},
]
[package.extras]
docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
fpx = ["olefile"]
mic = ["olefile"]
test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"]
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
xmp = ["defusedxml"]
[[package]]
name = "psycopg2-binary"
version = "2.9.11"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6fe6b47d0b42ce1c9f1fa3e35bb365011ca22e39db37074458f27921dca40f2"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c0e4262e089516603a09474ee13eabf09cb65c332277e39af68f6233911087"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c47676e5b485393f069b4d7a811267d3168ce46f988fa602658b8bb901e9e64d"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a28d8c01a7b27a1e3265b11250ba7557e5f72b5ee9e5f3a2fa8d2949c29bf5d2"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f2732cf504a1aa9e9609d02f79bea1067d99edf844ab92c247bbca143303b"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:865f9945ed1b3950d968ec4690ce68c55019d79e4497366d36e090327ce7db14"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91537a8df2bde69b1c1db01d6d944c831ca793952e4f57892600e96cee95f2cd"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dca1f356a67ecb68c81a7bc7809f1569ad9e152ce7fd02c2f2036862ca9f66b"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0da4de5c1ac69d94ed4364b6cbe7190c1a70d325f112ba783d83f8440285f152"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37d8412565a7267f7d79e29ab66876e55cb5e8e7b3bbf94f8206f6795f8f7e7e"},
{file = "psycopg2_binary-2.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:c665f01ec8ab273a61c62beeb8cce3014c214429ced8a308ca1fc410ecac3a39"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a311f1edc9967723d3511ea7d2708e2c3592e3405677bf53d5c7246753591fbb"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908"},
{file = "psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d"},
{file = "psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1"},
{file = "psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d"},
{file = "psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20e7fb94e20b03dcc783f76c0865f9da39559dcc0c28dd1a3fce0d01902a6b9c"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bdab48575b6f870f465b397c38f1b415520e9879fdf10a53ee4f49dcbdf8a21"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9d3a9edcfbe77a3ed4bc72836d466dfce4174beb79eda79ea155cc77237ed9e8"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:44fc5c2b8fa871ce7f0023f619f1349a0aa03a0857f2c96fbc01c657dcbbdb49"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9c55460033867b4622cda1b6872edf445809535144152e5d14941ef591980edf"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2d11098a83cca92deaeaed3d58cfd150d49b3b06ee0d0852be466bf87596899e"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:691c807d94aecfbc76a14e1408847d59ff5b5906a04a23e12a89007672b9e819"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b81627b691f29c4c30a8f322546ad039c40c328373b11dff7490a3e1b517855"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:b637d6d941209e8d96a072d7977238eea128046effbf37d1d8b2c0764750017d"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:41360b01c140c2a03d346cec3280cf8a71aa07d94f3b1509fa0161c366af66b4"},
{file = "psycopg2_binary-2.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:875039274f8a2361e5207857899706da840768e2a775bf8c65e82f60b197df02"},
]
[[package]]
name = "python-monkey-business"
version = "1.1.0"
description = "Utility functions for monkey-patching python code"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "python-monkey-business-1.1.0.tar.gz", hash = "sha256:8393839cc741415ed5ddc2bd58e2d4ce07f966a7d26b7aebff19dcec64818edc"},
{file = "python_monkey_business-1.1.0-py2.py3-none-any.whl", hash = "sha256:15b4f603c749ba9a7b4f1acd36af023a6c5ba0f7e591c945f8253f0ef44bf389"},
]
[[package]]
name = "sqlparse"
version = "0.5.3"
description = "A non-validating SQL parser."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"},
{file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"},
]
[package.extras]
dev = ["build", "hatch"]
doc = ["sphinx"]
[[package]]
name = "tablib"
version = "3.9.0"
description = "Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "tablib-3.9.0-py3-none-any.whl", hash = "sha256:eda17cd0d4dda614efc0e710227654c60ddbeb1ca92cdcfc5c3bd1fc5f5a6e4a"},
{file = "tablib-3.9.0.tar.gz", hash = "sha256:1b6abd8edb0f35601e04c6161d79660fdcde4abb4a54f66cc9f9054bd55d5fe2"},
]
[package.extras]
all = ["odfpy", "openpyxl (>=2.6.0)", "pandas", "pyyaml", "tabulate", "xlrd", "xlwt"]
cli = ["tabulate"]
ods = ["odfpy"]
pandas = ["pandas"]
xls = ["xlrd", "xlwt"]
xlsx = ["openpyxl (>=2.6.0)"]
yaml = ["pyyaml"]
[[package]]
name = "tzdata"
version = "2025.2"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
groups = ["main"]
markers = "sys_platform == \"win32\""
files = [
{file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
{file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
]
[metadata]
lock-version = "2.1"
python-versions = "^3.12"
content-hash = "e932d0af75c888d83fecefaaad1d018c508881a3bfde2ea640a82790e3567855"

25
cntmanage/pyproject.toml Normal file
View File

@@ -0,0 +1,25 @@
[tool.poetry]
name = "cntmanage"
version = "0.1.0"
packages = [{include = "flightslot"}, {include = "cntmanage"}]
description = "CantorAir Flight Scheduler"
authors = ["Emanuele <ema.trabattoni@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
django = "^5.1.2"
psycopg2-binary = "^2.9.10"
django-nested-admin = "^4.1.1"
django-durationwidget = "^1.0.5"
django-import-export = "^4.3.13"
django-colorfield = "^0.14.0"
openpyxl = "^3.1.5"
django-admin-action-forms = "^2.2.1"
django-polymorphic = "^4.1.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -0,0 +1 @@
admin: CantorAdmin2k25

View File

@@ -0,0 +1,29 @@
{% extends "admin/base.html" %}
{% load static %}
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name">
<a href="{% url 'admin:index' %}" style="color: #0b1728;">
<img src="{% static 'cantorair_blue.jpg' %}"
height="60px"
style="margin-right: 20px;"/>
</a>
</h1>
<div id="site-name" style="align-self: center; text-align: center;">
<a href="{% url 'admin:index' %}" style="color: #0b1728;">
{{ site_header | default:_('Django administration') }}
</a>
</div>
{% if user.is_anonymous %}
{% include "admin/color_theme_toggle.html" %}
{% endif %}
{% endblock %}
{% block nav-global %}{% endblock %}

View File

@@ -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)

View File

@@ -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)),
],
),
]

View File

@@ -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',
),
]

View File

@@ -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'),
),
]

View File

@@ -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)),
],
),
]

View File

@@ -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)),
],
),
]

View File

@@ -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')),
],
),
]

View File

@@ -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')),
],
),
]

View File

@@ -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')),
],
),
]

View File

@@ -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")

View File

@@ -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)}"

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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

142
techdb/poetry.lock generated
View File

@@ -1,142 +0,0 @@
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]]
name = "asgiref"
version = "3.8.1"
description = "ASGI specs, helper code, and adapters"
optional = false
python-versions = ">=3.8"
files = [
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
]
[package.extras]
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
[[package]]
name = "django"
version = "5.1.2"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false
python-versions = ">=3.10"
files = [
{file = "Django-5.1.2-py3-none-any.whl", hash = "sha256:f11aa87ad8d5617171e3f77e1d5d16f004b79a2cf5d2e1d2b97a6a1f8e9ba5ed"},
{file = "Django-5.1.2.tar.gz", hash = "sha256:bd7376f90c99f96b643722eee676498706c9fd7dc759f55ebfaf2c08ebcdf4f0"},
]
[package.dependencies]
asgiref = ">=3.8.1,<4"
sqlparse = ">=0.3.1"
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "psycopg2-binary"
version = "2.9.10"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
optional = false
python-versions = ">=3.8"
files = [
{file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"},
{file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"},
{file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"},
{file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"},
{file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"},
{file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"},
{file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"},
]
[[package]]
name = "sqlparse"
version = "0.5.1"
description = "A non-validating SQL parser."
optional = false
python-versions = ">=3.8"
files = [
{file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
{file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
]
[package.extras]
dev = ["build", "hatch"]
doc = ["sphinx"]
[[package]]
name = "tzdata"
version = "2024.2"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
files = [
{file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
{file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "c3b2acd3f06588e63224efb9ff8fc91a396c95e22862e40427c05b788901ea24"

View File

@@ -1,16 +0,0 @@
[tool.poetry]
name = "techdb"
version = "0.1.0"
description = "CantorAit Tech Organized Parts Storage"
authors = ["Emanuele <ema.trabattoni@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
django = "^5.1.2"
psycopg2-binary = "^2.9.10"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,22 +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),
]