initial commit
This commit is contained in:
commit
83cfa15723
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.venv
|
||||||
|
tmp
|
||||||
|
output
|
||||||
6
config.ini
Normal file
6
config.ini
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[main]
|
||||||
|
username =
|
||||||
|
password =
|
||||||
|
driver = gecko
|
||||||
|
output_directory = ./output
|
||||||
|
tenant_id =
|
||||||
163
save_progress_reports.py
Normal file
163
save_progress_reports.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.common.print_page_options import PrintOptions
|
||||||
|
from selenium.webdriver.firefox.service import Service as FirefoxService
|
||||||
|
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
|
||||||
|
from webdriver_manager.firefox import GeckoDriverManager
|
||||||
|
from pypdf import PdfWriter
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import errno
|
||||||
|
import glob
|
||||||
|
|
||||||
|
GECKO_DRIVER = 'gecko'
|
||||||
|
CHROME_DRIVER = 'chrome'
|
||||||
|
|
||||||
|
|
||||||
|
# HACK
|
||||||
|
os.environ["TMPDIR"] = "./tmp"
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
|
||||||
|
config.read('./config.ini')
|
||||||
|
output_dir = config['main']['output_directory']
|
||||||
|
try:
|
||||||
|
os.makedirs(output_dir)
|
||||||
|
except OSError as exception:
|
||||||
|
files = glob.glob(os.path.join(output_dir, "*.pdf"))
|
||||||
|
for f in files:
|
||||||
|
os.remove(f)
|
||||||
|
if exception.errno != errno.EEXIST:
|
||||||
|
raise
|
||||||
|
|
||||||
|
driver_type = config['main']['driver']
|
||||||
|
# TODO only firefox for now
|
||||||
|
if driver_type == GECKO_DRIVER:
|
||||||
|
opt = webdriver.FirefoxOptions()
|
||||||
|
opt.binary_location = "/usr/bin/firefox"
|
||||||
|
opt.add_argument("-headless") # Here
|
||||||
|
firefox_profile = FirefoxProfile()
|
||||||
|
firefox_profile.set_preference("print.enabled", False)
|
||||||
|
opt.profile = firefox_profile
|
||||||
|
driver = webdriver.Firefox(options=opt, service=FirefoxService(GeckoDriverManager().install()))
|
||||||
|
else:
|
||||||
|
print(format('unsupported driver type "%s"', driver_type))
|
||||||
|
os.exit(1)
|
||||||
|
|
||||||
|
url = 'https://app.flightschedulepro.com/Account/Login?company='+ config['main']['tenant_id']
|
||||||
|
driver.get(url)
|
||||||
|
|
||||||
|
# wait for stupid cookie modal to load and accept all ¯\_(ツ)_/¯
|
||||||
|
try:
|
||||||
|
WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_all_elements_located((By.ID, 'onetrust-accept-btn-handler'))
|
||||||
|
)
|
||||||
|
cookie_btn = driver.find_element(By.ID, 'onetrust-accept-btn-handler')
|
||||||
|
cookie_btn.click()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Login
|
||||||
|
form = driver.find_element(By.CLASS_NAME, 'account-form')
|
||||||
|
username = driver.find_element(By.ID, 'username')
|
||||||
|
username.send_keys(config['main']['username'])
|
||||||
|
username.submit()
|
||||||
|
|
||||||
|
WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_all_elements_located((By.ID, 'password'))
|
||||||
|
)
|
||||||
|
password = driver.find_element(By.ID, 'password')
|
||||||
|
password.send_keys(config['main']['password'])
|
||||||
|
|
||||||
|
password.submit()
|
||||||
|
|
||||||
|
# Wait for app to load and naviagte to students "page"
|
||||||
|
WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_all_elements_located((By.CLASS_NAME, 'fsp-main-drawer-content'))
|
||||||
|
)
|
||||||
|
driver.get('https://app.flightschedulepro.com/App/Students')
|
||||||
|
WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_all_elements_located((By.CLASS_NAME, 'clickable-course'))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get all the student rows and iterate
|
||||||
|
students = driver.find_elements(By.CLASS_NAME, 'clickable-course')
|
||||||
|
student_count = len(students)
|
||||||
|
|
||||||
|
print(f'processing {student_count} students')
|
||||||
|
|
||||||
|
original_window = driver.current_window_handle
|
||||||
|
for i in range(0, student_count):
|
||||||
|
# Need to reload student elements after returning to page
|
||||||
|
students = driver.find_elements(By.CLASS_NAME, 'clickable-course')
|
||||||
|
if len(students) is not student_count:
|
||||||
|
raise 'student count changed. aborting...'
|
||||||
|
student = students[i]
|
||||||
|
student_name = student.find_element(By.XPATH, '//div[@class="student"]/div[@class="bold"]').get_attribute("innerText")
|
||||||
|
student_name_no_space = student_name.replace(' ', '_')
|
||||||
|
course_td = student.find_element(By.CLASS_NAME, 'course-td')
|
||||||
|
course_td.click()
|
||||||
|
WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_all_elements_located((By.XPATH, '//span[text()[contains(., "Sessions")]]'))
|
||||||
|
)
|
||||||
|
sessions_tab = driver.find_element(By.XPATH, '//span[text()[contains(., "Sessions")]]')
|
||||||
|
sessions_tab.click()
|
||||||
|
WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_all_elements_located((By.XPATH, '//fsp-ui-button[@Text="View"]'))
|
||||||
|
)
|
||||||
|
# Ugh... Sessions Table reloads on modal opening after the first modal is opened. So we have to record
|
||||||
|
# the count of sessions initially and iterate while reloading the elements each time a button is clicked
|
||||||
|
view_btns = driver.find_elements(By.XPATH, '//fsp-ui-button[@Text="View"]')
|
||||||
|
btn_count = len(view_btns)
|
||||||
|
pdf_count = 0
|
||||||
|
print(f'Downloading {btn_count} sessions for student "{student_name}"')
|
||||||
|
for j in range(0, btn_count):
|
||||||
|
view_btns = driver.find_elements(By.XPATH, '//fsp-ui-button[@Text="View"]')
|
||||||
|
if len(view_btns) is not btn_count:
|
||||||
|
raise 'session count changed. aborting...'
|
||||||
|
view_btns[j].click()
|
||||||
|
WebDriverWait(driver, 10).until(
|
||||||
|
EC.presence_of_all_elements_located((By.XPATH, '//fsp-ui-button[@Text="Print"]'))
|
||||||
|
)
|
||||||
|
print_btn = driver.find_element(By.XPATH, '//fsp-ui-button[@Text="Print"]')
|
||||||
|
print_btn.click()
|
||||||
|
|
||||||
|
# Wait for the new window or tab
|
||||||
|
WebDriverWait(driver, 10).until(EC.number_of_windows_to_be(2))
|
||||||
|
|
||||||
|
# Loop through until we find a new window handle
|
||||||
|
for window_handle in driver.window_handles:
|
||||||
|
if window_handle != original_window:
|
||||||
|
driver.switch_to.window(window_handle)
|
||||||
|
break
|
||||||
|
# Wait for the new tab to finish loading content
|
||||||
|
WebDriverWait(driver, 10).until(EC.title_is('Print Training Session'))
|
||||||
|
print_options = PrintOptions()
|
||||||
|
print_options.orientation = "portrait"
|
||||||
|
pdf = driver.print_page(print_options)
|
||||||
|
with open(os.path.join(output_dir, student_name_no_space + "{:03d}".format(pdf_count) + '.pdf'), 'wb') as file:
|
||||||
|
file.write(base64.decodebytes(pdf.encode('utf-8')))
|
||||||
|
driver.close()
|
||||||
|
|
||||||
|
driver.switch_to.window(original_window)
|
||||||
|
WebDriverWait(driver, 10).until(EC.title_is('Flight Schedule Pro'))
|
||||||
|
close_btn = driver.find_element(By.XPATH, '//fsp-ui-button[@Text="Close"]')
|
||||||
|
close_btn.click()
|
||||||
|
pdf_count = pdf_count + 1
|
||||||
|
# Merge all Session PDFs for the student
|
||||||
|
print(f'Merging PDFs into "{student_name_no_space}.pdf"')
|
||||||
|
driver.close()
|
||||||
|
writer = PdfWriter()
|
||||||
|
|
||||||
|
pdfs = [a for a in os.listdir(output_dir) if a.endswith(".pdf") and a.startswith(student_name_no_space)]
|
||||||
|
pdfs.sort()
|
||||||
|
|
||||||
|
for pdf in pdfs:
|
||||||
|
writer.append(os.path.join(output_dir, pdf))
|
||||||
|
|
||||||
|
writer.write(student_name_no_space + '.pdf')
|
||||||
|
writer.close()
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user