734 lines
29 KiB
Python
734 lines
29 KiB
Python
import os
|
|
import requests
|
|
from urllib.parse import urlparse
|
|
import rollbar
|
|
import rollbar.contrib.flask
|
|
from flask import Flask, request, make_response, session, render_template, \
|
|
redirect, url_for, jsonify, flash
|
|
from flask import got_request_exception
|
|
from flask.ext import assets
|
|
from flask_oauthlib.client import OAuth
|
|
from webassets.filter import get_filter
|
|
|
|
from helix.sales_force import tasks as sf_tasks
|
|
from helix.Services.doc_gen_service import DocGenService
|
|
from helix.Services.dxf_helper import DXFHelper
|
|
from helix.Services.dxf_service import DXFService
|
|
from helix.api.api import api
|
|
from helix.calculators.calculator import Calculator
|
|
from helix.constants import redis_constant, sql_constant
|
|
from helix.constants.file_validation_error import FileValidationError, FileValidationException
|
|
from helix.constants.inverter_type import InverterType
|
|
from helix.constants.system_type import SystemType
|
|
from helix.csv_builder import CsvBuilder
|
|
from helix.doc_gen_params_builder import DocGenParamsBuilder
|
|
from helix.forms.ebom_form import EbomForm, InverterBrandForm, \
|
|
SupervisorForm, SupervisorFormSMA, StandAloneInverterFormSMA, \
|
|
StandAloneInverterFormDelta
|
|
from helix.forms.input_form import InputForm, ArrayForm, TestDXFForm
|
|
from helix.models.dxf.dxf_error import DXFError, OldDxfFormatException
|
|
from helix.presenters.image_presenter import ImagePresenter
|
|
from helix.presenters.panel_presenter import ProjectPresenter
|
|
from helix.qa_helper import QAScenario
|
|
from helix.session_manager import SessionManager
|
|
from helix.validators.file_validator import FileValidator, FileType
|
|
from helix.validators.subarray_validator import SubarrayValidator
|
|
from flask_featureflags import FeatureFlag
|
|
import flask_featureflags as feature
|
|
import pprint
|
|
|
|
app = Flask(__name__)
|
|
app.register_blueprint(api, url_prefix='/api')
|
|
app.secret_key = os.getenv('SECRET_KEY', 'verysecretkey')
|
|
app.config['PROFILE'] = True
|
|
|
|
app.config['FEATURE_FLAGS'] = {
|
|
'ff_dummy_feature': True,
|
|
'ff_cpp': True
|
|
}
|
|
|
|
FeatureFlag(app) # initializes feature flags
|
|
|
|
if feature.is_active('ff_dummy_feature'):
|
|
app.logger.info('Feature flags working!')
|
|
app.logger.info('Current state: ')
|
|
app.logger.info(pprint.PrettyPrinter(width=41, compact=True).pformat(app.config['FEATURE_FLAGS']))
|
|
|
|
# Salesforce integrations
|
|
oauth = OAuth()
|
|
SFDC_BASE_URL = os.getenv('SFDC_BASE_URL', 'https://test.salesforce.com')
|
|
try:
|
|
sales_force = oauth.remote_app('sales_force',
|
|
consumer_key=os.getenv('SFDC_ACCESS_KEY_ID'),
|
|
consumer_secret=os.getenv('SFDC_SECRET_ACCESS_KEY'),
|
|
base_url=SFDC_BASE_URL,
|
|
request_token_url=None, # OAuth 2
|
|
access_token_method='POST', # Sales Force requirement
|
|
access_token_url=SFDC_BASE_URL + '/services/oauth2/token',
|
|
authorize_url=SFDC_BASE_URL + '/services/oauth2/authorize',
|
|
)
|
|
except TypeError:
|
|
print('Salesforce integration disabled')
|
|
sales_force = None
|
|
|
|
|
|
assets_env = assets.Environment(app)
|
|
assets_env.init_app(app)
|
|
assets_env.load_path = [
|
|
os.path.join(os.path.dirname(__file__), 'scss')
|
|
]
|
|
sass = get_filter('scss')
|
|
sass.load_paths = [os.path.join(os.path.dirname(os.path.realpath(__file__)), 'scss')]
|
|
|
|
assets_env.register(
|
|
'main_css',
|
|
assets.Bundle(
|
|
'*.scss',
|
|
filters=(sass,),
|
|
output='css/main.css'
|
|
)
|
|
)
|
|
|
|
|
|
@app.before_first_request
|
|
def init_rollbar():
|
|
# Do nothing unless Rollbar is configured
|
|
if not os.getenv("ROLLBAR_ACCESS_TOKEN"):
|
|
return
|
|
|
|
rollbar.init(os.getenv("ROLLBAR_ACCESS_TOKEN"),
|
|
# Setup this var in heroku to distinguish errors from different envs
|
|
os.getenv("ROLLBAR_ENV", "development"),
|
|
root=os.path.dirname(os.path.realpath(__file__)),
|
|
allow_logging_basic_config=False)
|
|
|
|
got_request_exception.connect(rollbar.contrib.flask.report_exception, app)
|
|
|
|
|
|
def is_sales_force_session():
|
|
return 'sf_session' in session
|
|
|
|
|
|
@app.route("/")
|
|
def index():
|
|
return redirect(url_for('site_characterization'))
|
|
|
|
|
|
@app.route("/test_dxf/", methods=['GET', 'POST'])
|
|
def test_dxf():
|
|
form = TestDXFForm()
|
|
if form.validate_on_submit():
|
|
file = request.files['dxf_upload']
|
|
file_contents = file.read().decode('utf-8')
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
user_values = session_manager.user_values()
|
|
calculator = Calculator(user_values, calculate_panel_data=False)
|
|
l_b = calculator.L_B() * 12 # convert from feet to inches
|
|
try:
|
|
dxf_data = DXFService().parse(file_contents, user_values.module_system_constants(),
|
|
user_values.system_type(), l_b, DXFHelper(), SubarrayValidator())
|
|
dxf_data['panels'].sort(key=lambda p: p.id)
|
|
dxf_data['l_b'] = l_b
|
|
if not form.show_wind_zones.data:
|
|
dxf_data['lb_polygons'] = []
|
|
except DXFError as error:
|
|
form.dxf_upload.errors.append(error.message)
|
|
dxf_data = {}
|
|
else:
|
|
dxf_data = {}
|
|
dxf_data['colors'] = [
|
|
'red',
|
|
'orange',
|
|
'yellow',
|
|
'green',
|
|
'blue',
|
|
'purple',
|
|
]
|
|
return render_template('test_dxf.html.jinja', context=dxf_data, form=form)
|
|
|
|
|
|
# wizard steps
|
|
@app.route("/site_characterization/", methods=['GET', 'POST'])
|
|
def site_characterization():
|
|
if is_sales_force_session():
|
|
return redirect('/summary/')
|
|
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
site_info_form = InputForm()
|
|
context = session_manager.context()
|
|
context['current_step'] = 1
|
|
context['javascripts'] = ['site_characterization']
|
|
|
|
if site_info_form.validate_on_submit():
|
|
session_manager.save_form_submission(request.form)
|
|
return redirect(url_for('summary'))
|
|
|
|
if request.method != 'POST':
|
|
session_manager.fill_saved_values_in_form(site_info_form)
|
|
db_session.close()
|
|
return render_template('site_characterization.html.jinja',
|
|
context=context,
|
|
form=site_info_form)
|
|
|
|
|
|
@app.route("/summary/")
|
|
def summary():
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
context = session_manager.context()
|
|
context['current_step'] = 2
|
|
|
|
if context['site_data_available']:
|
|
context['project_name'] = session_manager.site.project_name
|
|
user_values = session_manager.user_values()
|
|
calculator = Calculator(user_values, calculate_panel_data=False)
|
|
context['wind_zones'] = user_values.system_type().system_constants().wind_zones
|
|
context['summary_table'] = calculator.summary_table()
|
|
context['minimum_array_sizes'] = calculator.minimum_array_sizes()
|
|
context['l_b'] = round(calculator.L_B(), 2)
|
|
context['k_z'] = round(calculator.k_z(), 2)
|
|
context['q_z'] = round(calculator.q_z(), 2)
|
|
context['warning_messages'] = set()
|
|
context['javascripts'] = ['https://cdn.rawgit.com/noelboss/featherlight/1.7.6/release/featherlight.min.js']
|
|
for panel_type, values in context['summary_table'].items():
|
|
for panel_type_warnings in values['warnings']:
|
|
for warning in panel_type_warnings:
|
|
context['warning_messages'].add(warning)
|
|
else:
|
|
context['no_proceed'] = True
|
|
|
|
if is_sales_force_session():
|
|
context['hide_back'] = True
|
|
|
|
db_session.close()
|
|
return render_template('site_summary.html.jinja', context=context)
|
|
|
|
|
|
def handle_dxf_file(session_manager, file_contents, filename=None):
|
|
errors = []
|
|
user_values = session_manager.user_values()
|
|
validator = FileValidator(user_values)
|
|
calculator = Calculator(user_values, calculate_panel_data=False)
|
|
|
|
extension = os.path.splitext(filename)[1] or 'dxf'
|
|
extension = extension.split('.')[-1]
|
|
|
|
validation_error = validator.validate(file_contents, FileType.AuroraDxf, extension=extension)
|
|
if validation_error:
|
|
error_msg = validation_error.format_error_message()
|
|
errors.append(error_msg)
|
|
else:
|
|
try:
|
|
module_constants = user_values.module_system_constants()
|
|
# FIXME: parsing a file with many entities is very slow
|
|
dxf_data = DXFService().parse(file_contents,
|
|
module_constants,
|
|
user_values.system_type(),
|
|
calculator.L_B() * 12,
|
|
DXFHelper(),
|
|
SubarrayValidator())
|
|
csv = CsvBuilder().build_cad_output(dxf_data['panels'])
|
|
session_manager.save_uploaded_file(csv, dxf_file_name=filename)
|
|
|
|
buildings = dxf_data['buildings']
|
|
session_manager.save_buildings_polygons(buildings)
|
|
session_manager.save_is_drawing_inaccurate(dxf_data['is_panel_drawing_inaccurate'])
|
|
|
|
return True, errors
|
|
except DXFError as error:
|
|
errors.append(error.message)
|
|
except OldDxfFormatException as error:
|
|
errors.append(error.message)
|
|
return False, errors
|
|
|
|
|
|
@app.route("/array_summary/", methods=['GET', 'POST'])
|
|
def array_summary():
|
|
"""This endpoint allows you to upload a file.
|
|
The content of the file is parsed, and then
|
|
several objects are created that aid in the validation
|
|
of the uploaded file.
|
|
"""
|
|
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
context = session_manager.context()
|
|
context['current_step'] = 3
|
|
array_form = ArrayForm()
|
|
if array_form.validate_on_submit():
|
|
if 'file_upload' in request.files and request.files['file_upload'].filename:
|
|
validator = FileValidator(session_manager.user_values())
|
|
file = request.files['file_upload']
|
|
file_contents = validator.obtain_stream(file)
|
|
validation_error = validator.validate(file_contents, FileType.Csv, file)
|
|
if not validation_error:
|
|
session_manager.save_uploaded_file(file_contents,
|
|
cad_file_name=file.filename)
|
|
session_manager.save_buildings_polygons([]) # no buildings in the csv file
|
|
session_manager.save_is_drawing_inaccurate(False)
|
|
return redirect(url_for('array_summary'))
|
|
else:
|
|
array_form.file_upload.errors.append(validation_error.format_error_message())
|
|
elif 'dxf_upload' in request.files and request.files['dxf_upload'].filename:
|
|
file = request.files['dxf_upload']
|
|
user_values = session_manager.user_values()
|
|
validator = FileValidator(user_values)
|
|
file_contents = validator.obtain_stream(file)
|
|
success, errors = handle_dxf_file(session_manager, file_contents, filename=file.filename)
|
|
if success:
|
|
if is_sales_force_session():
|
|
session['sf_session']['new_dxf_file'] = True
|
|
return redirect(url_for('array_summary'))
|
|
else:
|
|
array_form.dxf_upload.errors.extend(errors)
|
|
elif context['csv_available']:
|
|
return redirect(url_for('power_station_configuration'))
|
|
else:
|
|
array_form.file_upload.errors.append('Please provide a .txt file!')
|
|
|
|
context['javascripts'] = ['array_summary_bundle']
|
|
context['hide_submit'] = True
|
|
context['cad_file_name'] = ''
|
|
context['dxf_file_name'] = ''
|
|
if context['site_data_available'] and context['csv_available']:
|
|
user_values = session_manager.user_values()
|
|
try:
|
|
calculator = Calculator(user_values)
|
|
system_type = user_values.system_type()
|
|
module_type = user_values.module_type()
|
|
project_presenter = ProjectPresenter(system_type, module_type)
|
|
|
|
context['wind_zones'] = system_type.system_constants().wind_zones
|
|
context['summary_table'] = calculator.summary_table()
|
|
context['minimum_array_sizes'] = calculator.minimum_array_sizes()
|
|
context['seismic_anchors'] = calculator.subarray_summary()
|
|
context['summary_values'] = calculator.summary_values()
|
|
|
|
panels = calculator.get_computed_csv_columns()
|
|
max_y = project_presenter.get_max_y(calculator.buildings_for_drawing,panels)
|
|
context['panel_array'] = project_presenter.get_panel_data(panels,
|
|
calculator.subarrays,
|
|
max_y)
|
|
context['buildings'] = project_presenter.get_buildings(calculator.buildings_for_drawing, max_y)
|
|
context['corners'] = project_presenter.get_corners(calculator.buildings)
|
|
context['override_form'] = True
|
|
context['cad_file_name'] = session_manager.site.cad_file_name or 'Upload System Text Data'
|
|
context['dxf_file_name'] = session_manager.site.dxf_file_name or 'Upload System DXF'
|
|
context['is_drawing_inaccurate'] = session_manager.user_values().is_panel_drawing_inaccurate()
|
|
context['inaccurate_drawing_warning'] = 'The subarrays in this design are not parallel to each other, \
|
|
and the graphical representation on this page may not be accurate.'
|
|
|
|
except FileValidationException as error:
|
|
# when calculator is about to enter infinte loop
|
|
# it throws an exception - it is supplied wrong data
|
|
context['site_data_available'] = False
|
|
context['csv_available'] = False
|
|
context['no_proceed'] = True
|
|
context['cad_file_name'] = ''
|
|
context['dxf_file_name'] = ''
|
|
context['infinite_loop_detection_message'] = error.message
|
|
|
|
elif not context['site_data_available']:
|
|
context['no_proceed'] = True
|
|
|
|
if is_sales_force_session() and \
|
|
session['sf_session'].get('dxf_link', None) and \
|
|
not session['sf_session'].get('dxf_link_loaded', None):
|
|
|
|
context['javascripts'].append('auto_dxf_load')
|
|
|
|
db_session.close()
|
|
return render_template('array_summary.html.jinja', context=context, form=array_form)
|
|
|
|
|
|
@app.route("/load_dxf/", methods=['GET', 'POST'])
|
|
def load_dxf_file():
|
|
if (not is_sales_force_session()) or session['sf_session'].get('dxf_link', None) is None:
|
|
errors = ['DXF link not found']
|
|
response = jsonify({'errors': errors})
|
|
response.status_code = 404
|
|
return response
|
|
|
|
dxf_link = session['sf_session'].get('dxf_link', None)
|
|
if not dxf_link:
|
|
session['sf_session']['dxf_link_loaded'] = True
|
|
response = jsonify({'status': 'success', 'messages': ['No DXF file to load from Salesforce.']})
|
|
response.status_code = 202
|
|
return response
|
|
|
|
print('Loading DXF file from {}'.format(dxf_link))
|
|
response = requests.get(dxf_link, timeout=30)
|
|
if response.status_code == 200:
|
|
file_contents = response.content.decode('utf-8')
|
|
filename = urlparse(dxf_link).path.strip('/')
|
|
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
|
|
success, errors = handle_dxf_file(session_manager, file_contents, filename=filename)
|
|
session['sf_session']['dxf_link_loaded'] = True
|
|
if success:
|
|
return jsonify({'status': 'success', 'messages': ['DXF from Salesforce loaded successfully.']})
|
|
else:
|
|
errors = ['Unable to download DXF file from Salesforce ({})'.format(response.status_code)]
|
|
|
|
response = jsonify({'errors': errors})
|
|
response.status_code = 400
|
|
return response
|
|
|
|
|
|
@app.route("/power_station_configuration/", methods=['GET', 'POST'])
|
|
def power_station_configuration():
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
context = session_manager.context()
|
|
|
|
if session_manager.site:
|
|
system_type = session_manager.user_values().system_type()
|
|
else:
|
|
system_type = None
|
|
|
|
inverter_brand_form = InverterBrandForm()
|
|
if request.method != 'POST' or request.form['form_id'] != 'inverter_brand_form':
|
|
inverter_brand_form.populate_choices(context['inverter_brands'])
|
|
is_delta = inverter_brand_form.is_delta()
|
|
|
|
if is_delta:
|
|
ebom_form = EbomForm()
|
|
standalone_inverter_form = StandAloneInverterFormDelta()
|
|
standalone_inverter_form.populate_choices()
|
|
supervisor_form = SupervisorForm()
|
|
else:
|
|
ebom_form = EbomForm()
|
|
ebom_form.update_inverter_strings_choices(system_type)
|
|
standalone_inverter_form = StandAloneInverterFormSMA()
|
|
standalone_inverter_form.update_inverter_strings_choices(system_type)
|
|
standalone_inverter_form.populate_choices(context['power_stations'], context['standalone_inverters'])
|
|
supervisor_form = SupervisorFormSMA()
|
|
supervisor_form.populate_choices(context['power_stations'], context['power_monitors'])
|
|
|
|
if request.method == 'POST':
|
|
if request.form['form_id'] == 'inverter_brand_form' and inverter_brand_form.validate_on_submit():
|
|
session_manager.delete_power_station_config_data()
|
|
session_manager.save_or_update_inverter_brands(request.form)
|
|
return redirect("/power_station_configuration/")
|
|
|
|
elif request.form['form_id'] == 'power_station_form' and ebom_form.validate_on_submit():
|
|
session_manager.save_or_update_power_station(request.form)
|
|
return redirect("/power_station_configuration/")
|
|
|
|
elif request.form['form_id'] == 'standalone_inverter_form' and standalone_inverter_form.validate_on_submit():
|
|
session_manager.save_or_update_standalone_inverter(request.form)
|
|
return redirect("/power_station_configuration/")
|
|
|
|
elif request.form['form_id'] == 'supervisor_form' and supervisor_form.validate_on_submit():
|
|
session_manager.save_or_update_supervisor_monitor(request.form)
|
|
return redirect("/power_station_configuration")
|
|
|
|
ebom_form.power_station_description.data = "Power Station " + str(len(context['power_stations']) + 1)
|
|
|
|
inverter_enum = InverterType.DELTA if is_delta else InverterType.SMA
|
|
string_limits = {}
|
|
string_defaults = {}
|
|
for i_e in inverter_enum.all():
|
|
string_limits[i_e.value] = list(i_e.valid_string_ranges) if i_e.valid_string_ranges is not None else []
|
|
string_defaults[i_e.value] = i_e.default_string
|
|
|
|
context['standalone_inverter_string_limits'] = string_limits
|
|
context['standalone_inverter_string_defaults'] = string_defaults
|
|
context['current_step'] = 4
|
|
context['javascripts'] = ['power_station_configuration']
|
|
|
|
db_session.close()
|
|
return render_template('power_station_configuration.html.jinja',
|
|
context=context,
|
|
ebom_form=ebom_form,
|
|
is_delta=is_delta,
|
|
inverter_brand_form=inverter_brand_form,
|
|
standalone_inverter_form=standalone_inverter_form,
|
|
supervisor_monitor_form=supervisor_form)
|
|
|
|
|
|
@app.route("/download/")
|
|
def download():
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
context = session_manager.context()
|
|
context['current_step'] = 5
|
|
if is_sales_force_session():
|
|
error, data = session['sf_session'].pop('export_urls', (None, None))
|
|
if data is not None:
|
|
context['sfdc_export_error'] = error
|
|
context['sfdc_export_urls'] = data
|
|
db_session.close()
|
|
return render_template('download.html.jinja', context=context)
|
|
|
|
|
|
@app.route("/delete_power_station/<uuid>")
|
|
def delete_power_station(uuid):
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
session_manager.delete_power_station(uuid)
|
|
db_session.close()
|
|
return redirect(url_for('power_station_configuration'))
|
|
|
|
|
|
@app.route('/delete_standalone_inverter/<uuid>')
|
|
def delete_standalone_inverter(uuid):
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
session_manager.delete_standalone_inverter(uuid)
|
|
db_session.close()
|
|
return redirect(url_for('power_station_configuration'))
|
|
|
|
|
|
@app.route('/delete_supervisor_monitor/<uuid>')
|
|
def delete_supervisor_monitor(uuid):
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
session_manager.delete_supervisor_monitor(uuid)
|
|
db_session.close()
|
|
return redirect(url_for('power_station_configuration'))
|
|
|
|
|
|
@app.route("/result")
|
|
def result():
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
user_values = session_manager.user_values()
|
|
calculator = Calculator(user_values)
|
|
csv = CsvBuilder().build_cad_output(calculator.get_computed_csv_columns())
|
|
response = make_response(csv)
|
|
response.headers["Content-Disposition"] = "attachment; filename=%s_result.txt" % user_values.project_name_no_spaces()
|
|
db_session.close()
|
|
return response
|
|
|
|
|
|
@app.route("/documentation")
|
|
def documentation():
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
user_values = session_manager.user_values()
|
|
calculator = Calculator(user_values)
|
|
image_presenter = ImagePresenter(user_values.system_type(), user_values.module_type())
|
|
doc_gen_service = DocGenService(requests, DocGenParamsBuilder(user_values, user_values.system_type(), calculator, image_presenter))
|
|
document = doc_gen_service.generate()
|
|
|
|
response = make_response(document)
|
|
response.headers["Content-Disposition"] = "attachment; filename=%s_documentation.pdf" % user_values.project_name_no_spaces()
|
|
db_session.close()
|
|
return response
|
|
|
|
|
|
@app.route("/bom")
|
|
def bom():
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
user_values = session_manager.user_values()
|
|
calculator = Calculator(user_values)
|
|
csv = CsvBuilder().build_bom_output(calculator.compute_bom())
|
|
response = make_response(csv)
|
|
response.headers["Content-Disposition"] = "attachment; filename=%s_bom.txt" % user_values.project_name_no_spaces()
|
|
db_session.close()
|
|
return response
|
|
|
|
|
|
@app.route("/exposure_categories")
|
|
def exposure_categories():
|
|
db_session = sql_constant.sql_session_maker()
|
|
context = SessionManager(session, redis_constant.redis_store, db_session).context()
|
|
db_session.close()
|
|
return render_template('exposure_categories.html.jinja', context=context)
|
|
|
|
|
|
@app.route("/helix_documentation")
|
|
def helix_documentation():
|
|
db_session = sql_constant.sql_session_maker()
|
|
context = SessionManager(session, redis_constant.redis_store, db_session).context()
|
|
db_session.close()
|
|
return render_template('helix_documentation.jinja', context=context)
|
|
|
|
|
|
# Salesforce Integration
|
|
@app.route('/sales_force_login')
|
|
def sales_force_login():
|
|
# To test it locally: https://localhost:8443/sales_force_login?sfid=a3cL00000004QsQIAU
|
|
sfid = request.args.get('sfid')
|
|
if sfid:
|
|
session.clear()
|
|
session['sf_session'] = {}
|
|
session['sf_session']['sfid'] = sfid
|
|
return sales_force.authorize(callback=url_for('sales_force_authorized', _external=True, sfid=sfid), sfid=sfid)
|
|
else:
|
|
flash('Missing sfid')
|
|
return redirect('/')
|
|
|
|
|
|
@app.route('/sales_force_authorized')
|
|
def sales_force_authorized():
|
|
if not is_sales_force_session():
|
|
flash('This is not a Salesforce session')
|
|
return redirect('/')
|
|
|
|
next_url = url_for('summary')
|
|
|
|
try:
|
|
resp = sales_force.authorized_response()
|
|
except Exception as e:
|
|
flash('Error on authenticating to Salesforce.')
|
|
print('Error on authenticating to Salesforce.')
|
|
print(str(e))
|
|
return sales_force_logout()
|
|
|
|
if resp is None:
|
|
flash('Unknown response from Salesforce.')
|
|
print('Unknown response from Salesforce.')
|
|
return redirect(next_url)
|
|
|
|
print('New Salesforce - OAuth2 login')
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
session['sf_session']['access_token'] = resp['access_token']
|
|
|
|
helix_session_id = session['id']
|
|
access_token = session['sf_session']['access_token']
|
|
sfid = session['sf_session']['sfid']
|
|
try:
|
|
data, status_code = sf_tasks.get_site_characterization_from_sales_force(helix_session_id, access_token, sfid)
|
|
if data and status_code == 200:
|
|
session['sf_session']['dxf_link'] = data['dxf_link']
|
|
session_manager.save_form_submission(data)
|
|
flash('Connected to Salesforce.')
|
|
return redirect(next_url)
|
|
else:
|
|
flash('Unable to get site characterization from Salesforce. ({})'.format(status_code))
|
|
return sales_force_logout()
|
|
except Exception as e:
|
|
print(str(e))
|
|
flash('Unable to get site characterization from Salesforce.')
|
|
return sales_force_logout()
|
|
|
|
|
|
@app.route("/export-sfdc")
|
|
def export_sfdc():
|
|
if not is_sales_force_session():
|
|
flash('This is not a Salesforce session')
|
|
return redirect('/')
|
|
|
|
db_session = sql_constant.sql_session_maker()
|
|
session_manager = SessionManager(session, redis_constant.redis_store, db_session)
|
|
helix_session_id = session_manager.session['id']
|
|
sf_session = session['sf_session']
|
|
access_token = sf_session['access_token']
|
|
sfid = sf_session['sfid']
|
|
new_dxf_file = sf_session.get('new_dxf_file', False)
|
|
error, data = sf_tasks.export_to_sfdc(helix_session_id, access_token, sfid, new_dxf_file=new_dxf_file, qa_test=session.get('qa_test', None))
|
|
data.pop('bom', None)
|
|
data['dxfUrlFromSF'] = sf_session['dxf_link']
|
|
sf_session['export_urls'] = (error, data)
|
|
db_session.close()
|
|
return redirect('/download')
|
|
|
|
|
|
if sales_force:
|
|
@sales_force.tokengetter
|
|
def get_sales_force_token(token=None):
|
|
return session.get('sf_session', {}).get('access_token', None)
|
|
|
|
|
|
@app.route('/sales_force_logout')
|
|
def sales_force_logout():
|
|
session.pop('sf_session', None)
|
|
return redirect('/')
|
|
# End of Salesforce Integration
|
|
|
|
|
|
# QA scenario simulations
|
|
|
|
@app.route('/qa')
|
|
def qa_simulations():
|
|
test = request.args.get('test', None)
|
|
bad_request = None
|
|
if not test:
|
|
bad_request = 'Missing `test` parameter.'
|
|
elif test not in QAScenario.all():
|
|
bad_request = 'Unknown test scenario: <b>' + test + '</b>.'
|
|
if bad_request:
|
|
tests = ''.join(['<li><a href="/qa?test=' + s + '">' + s + '</a></li>' for s in QAScenario.all()])
|
|
tests = '<ul>' + tests + '</ul>'
|
|
return bad_request + tests, 400
|
|
|
|
if test.startswith('sf_') and 'sf_session' not in session:
|
|
return 'Please, connect to Salesforce first.', 400
|
|
|
|
url = None
|
|
if test == QAScenario.SF_RESET_ALL.value:
|
|
session['sf_session']['dxf_link_loaded'] = False
|
|
session['qa_test'] = None
|
|
url = request.url_root
|
|
elif test == QAScenario.SF_NO_DXF.value:
|
|
session['sf_session']['dxf_link'] = None
|
|
session['sf_session']['dxf_link_loaded'] = False
|
|
url = request.url_root + 'array_summary/'
|
|
elif test == QAScenario.SF_VALID_DXF.value:
|
|
session['sf_session']['dxf_link'] = 'https://sunpower-test-dgplatform-spectrum.s3.amazonaws.com/a5FL00000008gKcMAI-DXF.dxf'
|
|
session['sf_session']['dxf_link_loaded'] = False
|
|
url = request.url_root + 'array_summary/'
|
|
elif test in (QAScenario.SF_ERROR_UPLOAD_S3.value, QAScenario.SF_ERROR_NOTIFY_SF.value):
|
|
session['qa_test'] = test
|
|
url = request.url_root + 'export-sfdc'
|
|
|
|
msg = '<b>' + test + '</b> test enabled.'
|
|
if url:
|
|
msg += ' Access: <a href="' + url + '">' + url + '</a> to check.'
|
|
return msg, 200
|
|
|
|
|
|
@app.template_filter('format_number')
|
|
def format_number(number):
|
|
return "{:,g}".format(number)
|
|
|
|
|
|
@app.template_filter('is_dual_tilt')
|
|
def is_dual_tilt(system_type):
|
|
return system_type == SystemType.dualTilt
|
|
|
|
|
|
@app.context_processor
|
|
def power_station_has_monitor():
|
|
def _power_station_has_monitor(power_station, monitors):
|
|
for monitor in monitors:
|
|
if monitor['power_source'][1] == power_station['power_station_id']:
|
|
return True
|
|
return False
|
|
return dict(power_station_has_monitor=_power_station_has_monitor)
|
|
|
|
|
|
@app.context_processor
|
|
def enum():
|
|
def _enum(item):
|
|
return enumerate(item)
|
|
return dict(enum=_enum)
|
|
|
|
|
|
def main():
|
|
host = '0.0.0.0'
|
|
debug = bool(os.getenv("FLASK_DEBUG", False))
|
|
if os.getenv('FLASK_DEBUG_SSL', None):
|
|
port = int(os.getenv('PORT', 8443))
|
|
app.run(host=host, port=port, debug=debug, ssl_context='adhoc')
|
|
else:
|
|
port = int(os.getenv('PORT', 5000))
|
|
app.run(host=host, port=port, debug=debug)
|
|
|
|
|
|
@app.route("/fail-test")
|
|
def fail_test():
|
|
raise RuntimeError("This is a test failure, ignore it")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|