Compare commits
6 Commits
369c3b5e19
...
email
| Author | SHA1 | Date | |
|---|---|---|---|
| a9587776e8 | |||
| ec7ae4a48e | |||
| c93171dbc3 | |||
| 42417927c9 | |||
| 7c7d0e1e62 | |||
| e41eea8527 |
@@ -31,6 +31,7 @@ ALLOWED_HOSTS = []
|
|||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'admin_confirm',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
@@ -143,3 +144,14 @@ STATIC_URL = 'static/'
|
|||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
#### Send Email for user password communication ####
|
||||||
|
# https://docs.djangoproject.com/en/5.2/topics/email/
|
||||||
|
EMAIL_HOST = "smtp.gmail.com"
|
||||||
|
EMAIL_HOST_USER = "ema.trabattoni@gmail.com"
|
||||||
|
EMAIL_HOST_PASSWORD = "okorjsenzptdiwcr"
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
|
||||||
|
EMAIL_FILE_PATH = "/mnt/d/Test/flightslot-mail" # change this to a proper location
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ CSRF_TRUSTED_ORIGINS = ["http://localhost:8000", "http://127.0.0.1:8000", "http:
|
|||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'admin_confirm',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
@@ -74,7 +75,7 @@ ROOT_URLCONF = 'cntmanage.urls'
|
|||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': ['/var/www/templates'],
|
'DIRS': ['/var/www/templates', '/app/templates'],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@@ -138,9 +139,23 @@ USE_TZ = True
|
|||||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||||
STATIC_URL = "static/"
|
STATIC_URL = "static/"
|
||||||
STATIC_ROOT = "/var/www/static/"
|
STATIC_ROOT = "/var/www/static/"
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
"/app/static/"
|
||||||
|
]
|
||||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
#### Send Email for user password communication ####
|
||||||
|
# https://docs.djangoproject.com/en/5.2/topics/email/
|
||||||
|
EMAIL_HOST = "smtp.gmail.com"
|
||||||
|
EMAIL_HOST_USER = "ema.trabattoni@gmail.com"
|
||||||
|
EMAIL_HOST_PASSWORD = "okorjsenzptdiwcr"
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
|
||||||
|
# Use dummy backed for testing
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ FROM python:3.12-slim AS deploy
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# Copy application custom static files
|
# Copy application custom static files
|
||||||
RUN mkdir -p static
|
RUN mkdir -p static
|
||||||
COPY ./static/cantorair.jpg ./static
|
COPY ./static/* ./static
|
||||||
COPY ./static/cantorair_blue.jpg ./static
|
|
||||||
# Copy application custom templates for admin page
|
# Copy application custom templates for admin page
|
||||||
RUN mkdir -p /templates/admin
|
RUN mkdir -p /templates/admin
|
||||||
|
RUN mkdir -p /templates/email
|
||||||
COPY ./templates/admin/* ./templates/admin/
|
COPY ./templates/admin/* ./templates/admin/
|
||||||
|
COPY ./templates/email/* ./templates/email/
|
||||||
# Copy and install application wheel package
|
# Copy and install application wheel package
|
||||||
COPY --from=builder /build/dist/*.whl ./
|
COPY --from=builder /build/dist/*.whl ./
|
||||||
RUN pip install --no-cache-dir *.whl
|
RUN pip install --no-cache-dir *.whl
|
||||||
|
|||||||
73
cntmanage/flightslot/actions/send_email.py
Normal file
73
cntmanage/flightslot/actions/send_email.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
from django.contrib.staticfiles import finders
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
from ..models.students import Student
|
||||||
|
|
||||||
|
from smtplib import SMTPException
|
||||||
|
from email.mime.image import MIMEImage
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def send_mail_password(request: HttpRequest, queryset: QuerySet[Student]) -> None:
|
||||||
|
img: MIMEImage | None = None
|
||||||
|
filename: str
|
||||||
|
candidates = finders.find("cantorair.png")
|
||||||
|
if not candidates:
|
||||||
|
messages.error(request=request, message="Cannot Load CantorAir Logo")
|
||||||
|
return
|
||||||
|
elif isinstance(candidates, list):
|
||||||
|
filename = candidates.pop()
|
||||||
|
else:
|
||||||
|
filename = candidates
|
||||||
|
|
||||||
|
with open(filename, "rb") as f:
|
||||||
|
img = MIMEImage(f.read())
|
||||||
|
img.add_header("Content-ID", "logo_image")
|
||||||
|
img.add_header("Content-Disposition", "inline", filename="cantorair.png")
|
||||||
|
|
||||||
|
# build mail list filling template
|
||||||
|
mails: List[EmailMultiAlternatives] = []
|
||||||
|
for student in queryset:
|
||||||
|
if not student.user or not student.email: # skip student if has not an associated user
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
username: str = student.user.username
|
||||||
|
password: str = student.default_password()
|
||||||
|
address: str = student.email
|
||||||
|
|
||||||
|
text_message: str = f"Cantor Air Flight Scheduler\nUsername:{username}\nPassword:{password}\n"
|
||||||
|
|
||||||
|
html_message: SafeText = render_to_string(
|
||||||
|
template_name="email/mail.html",
|
||||||
|
context={"username": username, "password": password}
|
||||||
|
)
|
||||||
|
|
||||||
|
mail: EmailMultiAlternatives = EmailMultiAlternatives(
|
||||||
|
subject="CantorAir Flight Scheduler 🛫",
|
||||||
|
from_email="ema.trabattoni@gmail.com",
|
||||||
|
body=text_message,
|
||||||
|
to = [ address ]
|
||||||
|
)
|
||||||
|
mail.attach(filename=img)
|
||||||
|
mail.attach_alternative(content=html_message, mimetype="text/html")
|
||||||
|
mails.append(mail)
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request=request, message=f"General Error: {e}")
|
||||||
|
|
||||||
|
# Open only one conenction and send mass email
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
conn.send_messages(mails)
|
||||||
|
except SMTPException as e:
|
||||||
|
messages.error(request=request, message=f"Send Mail SMTP error: {e.strerror}")
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request=request, message=f"Send Mail General error: {e}")
|
||||||
|
else:
|
||||||
|
messages.success(request=request, message=f"Successfully sent {len(mails)} messages")
|
||||||
|
|
||||||
|
return
|
||||||
@@ -10,13 +10,15 @@ from import_export.tmp_storages import CacheStorage
|
|||||||
from import_export.resources import ModelResource
|
from import_export.resources import ModelResource
|
||||||
from import_export.forms import ConfirmImportForm, ImportForm
|
from import_export.forms import ConfirmImportForm, ImportForm
|
||||||
|
|
||||||
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, ActionForm, action_with_form
|
||||||
|
from admin_confirm import AdminConfirmMixin, confirm_action
|
||||||
|
|
||||||
from ..models.aircrafts import AircraftTypes
|
from ..models.aircrafts import AircraftTypes
|
||||||
from ..models.courses import Course
|
from ..models.courses import Course
|
||||||
from ..models.students import Student
|
from ..models.students import Student
|
||||||
|
|
||||||
from ..actions.assign_aircraft import assign_aircraft
|
from ..actions.assign_aircraft import assign_aircraft
|
||||||
|
from ..actions.send_email import send_mail_password
|
||||||
|
|
||||||
from ..custom.colortag import course_color
|
from ..custom.colortag import course_color
|
||||||
|
|
||||||
@@ -64,17 +66,19 @@ class ChangeCourseForm(AdminActionForm):
|
|||||||
class ChangeAircraftForm(AdminActionForm):
|
class ChangeAircraftForm(AdminActionForm):
|
||||||
aircrafts = TypedMultipleChoiceField(choices=AircraftTypes)
|
aircrafts = TypedMultipleChoiceField(choices=AircraftTypes)
|
||||||
|
|
||||||
class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
|
||||||
|
class StudentAdmin(ImportMixin, AdminConfirmMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||||
model = Student
|
model = Student
|
||||||
list_display = ("surname", "name", "course", "course_color", "email", "phone", "username", "password", "active", )
|
list_display = ("surname", "name", "course", "course_color", "email", "phone", "username", "password", "active", )
|
||||||
list_filter = ("course", "active", )
|
list_filter = ("course", "active", )
|
||||||
search_fields = ("surname", "name", "phone", "email", )
|
search_fields = ("surname", "name", "phone", "email", )
|
||||||
actions = ("change_course", "deactivate_students", "change_aircraft", )
|
actions = ("change_course", "deactivate_students", "change_aircraft", "send_mail", )
|
||||||
resource_classes = [StudentResource]
|
resource_classes = [StudentResource]
|
||||||
confirm_form_class = StudentCustomConfirmImportForm
|
confirm_form_class = StudentCustomConfirmImportForm
|
||||||
tmp_storage_class = CacheStorage
|
tmp_storage_class = CacheStorage
|
||||||
skip_admin_log = True
|
skip_admin_log = True
|
||||||
|
|
||||||
|
|
||||||
@admin.display(description="Color")
|
@admin.display(description="Color")
|
||||||
def course_color(self, obj: Student) -> SafeText:
|
def course_color(self, obj: Student) -> SafeText:
|
||||||
if not obj.course:
|
if not obj.course:
|
||||||
@@ -111,6 +115,11 @@ class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
|||||||
i, ac_types = assign_aircraft(queryset=queryset, data=data)
|
i, ac_types = assign_aircraft(queryset=queryset, data=data)
|
||||||
messages.success(request, f"{i} Students updated to {ac_types}")
|
messages.success(request, f"{i} Students updated to {ac_types}")
|
||||||
|
|
||||||
|
@confirm_action
|
||||||
|
@admin.action(description="Send Access Credentials e-mail")
|
||||||
|
def send_mail(self, request: HttpRequest, queryset: QuerySet[Student], *args: Any) -> None:
|
||||||
|
send_mail_password(request=request, queryset=queryset)
|
||||||
|
|
||||||
# Return the initial form for import confirmations, request course to user
|
# Return the initial form for import confirmations, request course to user
|
||||||
def get_confirm_form_initial(self, request: HttpRequest, import_form) -> Dict[str, Any]:
|
def get_confirm_form_initial(self, request: HttpRequest, import_form) -> Dict[str, Any]:
|
||||||
initial: Dict[str, Any] = super().get_confirm_form_initial(request, import_form)
|
initial: Dict[str, Any] = super().get_confirm_form_initial(request, import_form)
|
||||||
|
|||||||
@@ -64,8 +64,13 @@ class WeekPreferenceAdmin(nested_admin.NestedPolymorphicModelAdmin):
|
|||||||
TrainingInLIne(self.model, self.admin_site),
|
TrainingInLIne(self.model, self.admin_site),
|
||||||
HourBuildingInLine(self.model, self.admin_site),
|
HourBuildingInLine(self.model, self.admin_site),
|
||||||
)
|
)
|
||||||
# All other courses have only training
|
# All other courses have only training
|
||||||
return (TrainingInLIne(self.model, self.admin_site), )
|
return (TrainingInLIne(self.model, self.admin_site), )
|
||||||
|
else:
|
||||||
|
return (
|
||||||
|
TrainingInLIne(self.model, self.admin_site),
|
||||||
|
HourBuildingInLine(self.model, self.admin_site),
|
||||||
|
)
|
||||||
|
|
||||||
# If a user is registered as student do not show actions
|
# If a user is registered as student do not show actions
|
||||||
def get_actions(self, request: HttpRequest) -> Dict[str, Any]:
|
def get_actions(self, request: HttpRequest) -> Dict[str, Any]:
|
||||||
|
|||||||
17
cntmanage/poetry.lock
generated
17
cntmanage/poetry.lock
generated
@@ -66,6 +66,21 @@ files = [
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
django = ">=3.2"
|
django = ">=3.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-admin-confirm"
|
||||||
|
version = "1.0.1"
|
||||||
|
description = "Adds confirmation to Django Admin changes, additions and actions"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django-admin-confirm-1.0.1.tar.gz", hash = "sha256:fb6a4b7cb9fc6ccd97f92f88275ee8e3912f3ee0ce82da962ad0a2b1b17cd6a0"},
|
||||||
|
{file = "django_admin_confirm-1.0.1-py3-none-any.whl", hash = "sha256:271a7135e8e5f0cce94a6c06f708dec794d3538ada37e111c1f8d7c8f762b012"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=3.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-colorfield"
|
name = "django-colorfield"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -468,4 +483,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "5147211bd07992aff3915544175c8d95d77511b9d42273d17c4452fbef9299eb"
|
content-hash = "b45301c627836abac1ef9628e67fc63189b03e7857a7a003854aa1fb30f2a4a3"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ openpyxl = "^3.1.5"
|
|||||||
django-admin-action-forms = "^2.2.1"
|
django-admin-action-forms = "^2.2.1"
|
||||||
django-polymorphic = "^4.1.0"
|
django-polymorphic = "^4.1.0"
|
||||||
django-phonenumber-field = {extras = ["phonenumberslite"], version = "^8.4.0"}
|
django-phonenumber-field = {extras = ["phonenumberslite"], version = "^8.4.0"}
|
||||||
|
django-admin-confirm = "^1.0.1"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|||||||
BIN
cntmanage/static/cantorair.png
Normal file
BIN
cntmanage/static/cantorair.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@@ -1 +0,0 @@
|
|||||||
admin: CantorAdmin2k25
|
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
<h1 id="site-name">
|
<h1 id="site-name">
|
||||||
<a href="{% url 'admin:index' %}" style="color: #0b1728;">
|
<a href="{% url 'admin:index' %}" style="color: #0b1728;">
|
||||||
<img src="{% static 'cantorair_blue.jpg' %}"
|
<img src="{% static 'cantorair.png' %}"
|
||||||
height="60px"
|
height="70px"
|
||||||
style="margin-right: 20px;"/>
|
style="margin-right: 20px;"/>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
408
cntmanage/templates/email/mail.html
Normal file
408
cntmanage/templates/email/mail.html
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html
|
||||||
|
xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG />
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="x-apple-disable-message-reformatting" />
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<!--<![endif]-->
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
@media only screen and (min-width: 520px) {
|
||||||
|
.u-row {
|
||||||
|
width: 500px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row .u-col {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row .u-col-100 {
|
||||||
|
width: 500px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 520px) {
|
||||||
|
.u-row-container {
|
||||||
|
max-width: 100% !important;
|
||||||
|
padding-left: 0px !important;
|
||||||
|
padding-right: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row .u-col {
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 320px !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-row .u-col > div {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
table,
|
||||||
|
td,
|
||||||
|
tr {
|
||||||
|
border-collapse: collapse;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.ie-container table,
|
||||||
|
.mso-container table {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
a[x-apple-data-detectors="true"] {
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table,
|
||||||
|
td {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body
|
||||||
|
class="clean-body u_body"
|
||||||
|
style="
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
background-color: #f7f8f9;
|
||||||
|
color: #000000;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if IE]><div class="ie-container"><![endif]-->
|
||||||
|
<!--[if mso]><div class="mso-container"><![endif]-->
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
id="u_body"
|
||||||
|
style="
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
border-spacing: 0;
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
vertical-align: top;
|
||||||
|
min-width: 320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #f7f8f9;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr style="vertical-align: top">
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
word-break: break-word;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
vertical-align: top;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if (mso)|(IE)]><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color: #F7F8F9;"><![endif]-->
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="u-row-container"
|
||||||
|
style="padding: 0px; background-color: transparent"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="u-row"
|
||||||
|
style="
|
||||||
|
margin: 0 auto;
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 500px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
background-color: transparent;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
border-collapse: collapse;
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if (mso)|(IE)]><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:500px;"><tr style="background-color: transparent;"><![endif]-->
|
||||||
|
|
||||||
|
<!--[if (mso)|(IE)]><td align="center" width="500" style="width: 500px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;border-radius: 0px;-webkit-border-radius: 0px; -moz-border-radius: 0px;" valign="top"><![endif]-->
|
||||||
|
<div
|
||||||
|
class="u-col u-col-100"
|
||||||
|
style="
|
||||||
|
max-width: 320px;
|
||||||
|
min-width: 500px;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: top;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
height: 100%;
|
||||||
|
width: 100% !important;
|
||||||
|
border-radius: 0px;
|
||||||
|
-webkit-border-radius: 0px;
|
||||||
|
-moz-border-radius: 0px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!--[if (!mso)&(!IE)]><!--><div
|
||||||
|
style="
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0px;
|
||||||
|
border-top: 0px solid transparent;
|
||||||
|
border-left: 0px solid transparent;
|
||||||
|
border-right: 0px solid transparent;
|
||||||
|
border-bottom: 0px solid transparent;
|
||||||
|
border-radius: 0px;
|
||||||
|
-webkit-border-radius: 0px;
|
||||||
|
-moz-border-radius: 0px;
|
||||||
|
"
|
||||||
|
><!--<![endif]-->
|
||||||
|
<table
|
||||||
|
style="font-family: arial, helvetica, sans-serif"
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: arial, helvetica, sans-serif;
|
||||||
|
"
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
width="100%"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
padding-right: 0px;
|
||||||
|
padding-left: 0px;
|
||||||
|
"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://www.cantorair.it"
|
||||||
|
target="_blank"
|
||||||
|
style="
|
||||||
|
color: rgb(0, 0, 238);
|
||||||
|
text-decoration: underline;
|
||||||
|
line-height: inherit;
|
||||||
|
"
|
||||||
|
><img
|
||||||
|
align="center"
|
||||||
|
border="0"
|
||||||
|
src="cid:logo_image"
|
||||||
|
alt="Cantor Air Logo"
|
||||||
|
title=""
|
||||||
|
style="
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
clear: both;
|
||||||
|
display: inline-block !important;
|
||||||
|
border: none;
|
||||||
|
height: auto;
|
||||||
|
float: none;
|
||||||
|
width: 50%;
|
||||||
|
max-width: 240px;
|
||||||
|
"
|
||||||
|
width="240"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
style="font-family: arial, helvetica, sans-serif"
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: arial, helvetica, sans-serif;
|
||||||
|
"
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
<!--[if mso]><table role="presentation" width="100%"><tr><td><![endif]-->
|
||||||
|
<h1
|
||||||
|
style="
|
||||||
|
margin: 0px;
|
||||||
|
line-height: 140%;
|
||||||
|
text-align: center;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 400;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>✈️ Welcome to CantorAir Flight Scheduler
|
||||||
|
✈️</span
|
||||||
|
>
|
||||||
|
</h1>
|
||||||
|
<!--[if mso]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table
|
||||||
|
style="font-family: arial, helvetica, sans-serif"
|
||||||
|
role="presentation"
|
||||||
|
cellpadding="0"
|
||||||
|
cellspacing="0"
|
||||||
|
width="100%"
|
||||||
|
border="0"
|
||||||
|
>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style="
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: arial, helvetica, sans-serif;
|
||||||
|
"
|
||||||
|
align="left"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 140%;
|
||||||
|
text-align: left;
|
||||||
|
word-wrap: break-word;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style="
|
||||||
|
text-align: center;
|
||||||
|
line-height: inherit;
|
||||||
|
margin: 0px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>You can access the flight scheduler
|
||||||
|
website with this link: </span
|
||||||
|
><br /><a href="https://cms.etss.it/user"
|
||||||
|
><span>https://cms.etss.it</span></a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p style="text-align: center"><br /></p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<span style="font-weight: bold"
|
||||||
|
>Your access credentials:</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<br /><span>Username: {{username}}</span
|
||||||
|
><br /><span>Password: {{password}}</span>
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<br />
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<span
|
||||||
|
>Flight Scheduling is available from </span
|
||||||
|
><br /><span style="font-weight: bold"
|
||||||
|
>Monday to Tuesday until 15.00</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p style="text-align: center">
|
||||||
|
<br /><span>_______________________</span>
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<span style="font-style: italic">Team</span
|
||||||
|
><br /><span style="font-weight: bold"
|
||||||
|
>CANTOR AIR IT.ATO.0004</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p dir="ltr" style="text-align: center">
|
||||||
|
<span> Email: </span
|
||||||
|
><a
|
||||||
|
href="mailto:info@cantorair.it"
|
||||||
|
target="_blank"
|
||||||
|
style="line-height: 115%"
|
||||||
|
><span>info@cantorair.it</span></a
|
||||||
|
><br /><span>Phone: +39 035 520035</span
|
||||||
|
><br /><span>_______________________</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!--[if (!mso)&(!IE)]><!-->
|
||||||
|
</div>
|
||||||
|
<!--<![endif]-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||||
|
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--[if mso]></div><![endif]-->
|
||||||
|
<!--[if IE]></div><![endif]-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4
note.txt
4
note.txt
@@ -1,6 +1,8 @@
|
|||||||
OK leg delle hb all'interno del riquadro per i giorni
|
OK leg delle hb all'interno del riquadro per i giorni
|
||||||
OK aereo assegnato allo studente di fianco al numero della missione per PPL, invece per CPL e IR e HB il tipo va di fiaco al nome dello studente
|
OK aereo assegnato allo studente di fianco al numero della missione per PPL, invece per CPL e IR e HB il tipo va di fiaco al nome dello studente
|
||||||
OK le missioni ripetute su piu' giorni hanno una cella per giorno (non unite)
|
OK le missioni ripetute su piu' giorni hanno una cella per giorno (non unite)
|
||||||
lo studente vede solo le missioni della sua fase PPL->PPL ATPL-> tutto
|
OK lo studente vede solo le missioni della sua fase PPL->PPL ATPL-> tutto
|
||||||
OK ogni richiesta ha un colore diverso che cicla con delle tinte pastello
|
OK ogni richiesta ha un colore diverso che cicla con delle tinte pastello
|
||||||
|
|
||||||
|
|
||||||
|
password: CantorAir2k25
|
||||||
|
|||||||
Reference in New Issue
Block a user