merge with upstream

This commit is contained in:
Senad Uka
2017-12-20 20:27:55 +01:00
parent 8beef5faea
commit 2ea9e2e702
12 changed files with 237 additions and 81 deletions

View File

@@ -88,8 +88,6 @@ class EbomCalculator(object):
for monitor in monitors:
if monitor['power_source'][0] == 'Switch Gear/External':
add_parts_to_list(part_list, {proper_monitor_controller: 1}, 1)
if (is_delta):
add_parts_to_list(part_list, {ethernet_plug: 2},1)
if is_delta:
clips_amount = inverter_count * self.row_count * 1.15

View File

@@ -4,9 +4,11 @@ from helix.calculators.subarray_helper import extract_subarray
from helix.constants.global_constants import minimum_racking_capacity
from helix.constants.panel_type import PanelType
from helix.constants.file_validation_error import FileValidationMessage,FileValidationException
from helix.models.subarray import Subarray
class SeismicCalculator(object):
def __init__(self, values, graph_repository):
self.values = values
@@ -37,13 +39,20 @@ class SeismicCalculator(object):
more_anchors_needed = True
perimeter_covered = sds < 1.0
anchor_threshold = 0
was_rung_empty = False
while more_anchors_needed:
rung = graph.pop_rung()
interval = int(self.seismic_anchor_interval())
nodes_since_last_anchor = interval
if len(rung) == 0:
if was_rung_empty:
# detected an infinite loop
# something is wrong with the input file
# probably panels overlapping
raise FileValidationException(FileValidationMessage.PanelsTooClose.value)
graph.reset()
anchor_threshold += 1
was_rung_empty = True
continue
while more_anchors_needed and interval >= 0:
for node in rung:

View File

@@ -49,3 +49,7 @@ class FileValidationError(object):
if self.__class__ != other.__class__:
return False
return self.row_number == other.row_number and self.validation_message == other.validation_message
class FileValidationException(Exception):
def __init__(self, message):
self.message = message

View File

@@ -1,9 +1,10 @@
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
redirect, url_for, jsonify
from flask import got_request_exception
from flask.ext import assets
from flask_oauthlib.client import OAuth
@@ -16,6 +17,7 @@ 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
@@ -184,6 +186,44 @@ def summary():
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.
@@ -202,8 +242,7 @@ def array_summary():
validator = FileValidator(session_manager.user_values())
file = request.files['file_upload']
file_contents = validator.obtain_stream(file)
validation_error = validator.validate(file_contents, file,
FileType.Csv)
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)
@@ -215,36 +254,13 @@ def array_summary():
elif 'dxf_upload' in request.files and request.files['dxf_upload'].filename:
file = request.files['dxf_upload']
user_values = session_manager.user_values()
calculator = Calculator(user_values, calculate_panel_data=False)
validator = FileValidator(user_values)
file_contents = validator.obtain_stream(file)
validation_error = validator.validate(file_contents, file,
FileType.AuroraDxf)
if validation_error:
error_msg = validation_error.format_error_message()
array_form.dxf_upload.errors.append(error_msg)
success, errors = handle_dxf_file(session_manager, file_contents, filename=file.filename)
if success:
return redirect(url_for('array_summary'))
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=file.filename)
buildings = dxf_data['buildings']
session_manager.save_buildings_polygons(buildings)
session_manager.save_is_drawing_inaccurate(dxf_data['is_panel_drawing_inaccurate'])
return redirect(url_for('array_summary'))
except DXFError as error:
array_form.dxf_upload.errors.append(error.message)
except OldDxfFormatException as error:
array_form.dxf_upload.errors.append(error.message)
array_form.dxf_upload.errors.extend(errors)
elif context['csv_available']:
return redirect(url_for('power_station_configuration'))
else:
@@ -256,39 +272,81 @@ def array_summary():
context['dxf_file_name'] = ''
if context['site_data_available'] and context['csv_available']:
user_values = session_manager.user_values()
calculator = Calculator(user_values)
system_type = user_values.system_type()
module_type = user_values.module_type()
project_presenter = ProjectPresenter(system_type, module_type)
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()
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()
context['panel_array'] = project_presenter.get_panel_data(panels,
calculator.subarrays,
project_presenter.get_max_y(
calculator.buildings_for_drawing,
panels))
context['buildings'] = project_presenter.get_buildings(calculator.buildings_for_drawing)
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, \
panels = calculator.get_computed_csv_columns()
context['panel_array'] = project_presenter.get_panel_data(panels,
calculator.subarrays,
project_presenter.get_max_y(
calculator.buildings_for_drawing,
panels))
context['buildings'] = project_presenter.get_buildings(calculator.buildings_for_drawing)
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_sfdc_session() and 'dxf_link_loaded' not in session:
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 'dxf_link' not in session:
errors = ['DXF link not found']
response = jsonify({'errors': errors})
response.status_code = 404
return response
dxf_link = session['dxf_link']
response = requests.get(dxf_link)
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['dxf_link_loaded'] = True
if success:
return jsonify({'status': 'success'})
else:
errors = ['Unable to download DXF file from Sales Force ({})'.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()
@@ -485,6 +543,7 @@ def sales_force_authorized():
data = sf_tasks.get_site_characterization_from_sales_force(session, resp['instance_url'])
if data:
session['dxf_link'] = data['dxf_link']
session_manager.save_form_submission(data)
return redirect(next_url)
else:
@@ -492,7 +551,6 @@ def sales_force_authorized():
# FIXME
from flask import jsonify
@app.route("/export-sfdc")
def export_sfdc():
if not is_sfdc_session():
@@ -515,6 +573,8 @@ def get_sales_force_token(token=None):
def sales_force_logout():
session.pop('SFID', None)
session.pop('sales_force_token', None)
session.pop('dxf_link', None)
session.pop('dxf_link_loaded', None)
session.clear()
return redirect('/')
# End of Sales Force Integration

View File

@@ -151,7 +151,7 @@ a.back {
a {
text-decoration: none;
width: auto;
margin: 0 5px;
margin: 2px 5px;
}
.button {

View File

@@ -59,3 +59,35 @@ h1 {
.spacer {
flex-grow: 1;
}
.spinner-panel {
position: fixed;
margin: 0 auto;
top: 0;
left: 0;
width: 0; /* It will be updated to 100% in JS. Workaround for Safari issue with display:none; */
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
font-size: 24px;
overflow: hidden;
}
.msg-container {
background-color: $off-white;
border: 1px solid $medium-border-color;
margin: 20px;
padding: 10px;
padding-left: 30px;
}
.msg-container li {
padding-left: 0 !important;
}

View File

@@ -832,6 +832,41 @@ h1 {
flex-grow: 1;
}
/* line 63 */
.spinner-panel {
position: fixed;
margin: 0 auto;
top: 0;
left: 0;
width: 0;
/* It will be updated to 100% in JS. Workaround for Safari issue with display:none; */
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
font-size: 24px;
overflow: hidden;
}
/* line 84 */
.msg-container {
background-color: #FDFDFD;
border: 1px solid #D8D8D8;
margin: 20px;
padding: 10px;
padding-left: 30px;
}
/* line 91 */
.msg-container li {
padding-left: 0 !important;
}
/* line 3 */
.navigation_header {
display: flex;
@@ -1023,21 +1058,3 @@ table .right_border_cell {
margin-bottom: 25px;
}
.spinner-panel {
position: fixed;
margin: 0 auto;
top: 0;
left: 0;
width: 0; /* It will be updated to 100% in JS. Workaround for Safari issue with display:none; */
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
font-size: 24px;
}

View File

@@ -0,0 +1,22 @@
"use strict";
$(document).ready(function () {
$("#sf_msg_container").empty();
$("#sf_msg_container").show();
$('#sf-spinner-panel').css('width', '100%'); // Workaround for Safari issue
$.ajax({
type: 'POST',
url: '/load_dxf/'
}).done(function(r) {
console.log(r)
$("#sf_msg_container").append('<li class="">DXF from Sales Force loaded successfully.</li>');
setTimeout(function() { window.location.reload() }, 500);
}).fail(function(r) {
// console.log(r.status)
r.responseJSON.errors.forEach((msg) => {
$("#sf_msg_container").append('<li class="error_message">' + msg + '</li>');
})
}).always(function() {
$('#sf-spinner-panel').css('width', '0%'); // Workaround for Safari issue
});
});

View File

@@ -10,6 +10,16 @@
<i class="icon-spin6 animate-spin"></i>
</div>
{% if 'SFID' in session %}
<ul id="sf_msg_container" class="msg-container" style="display:none;">
</ul>
<div id="sf-spinner-panel" class="spinner-panel">
<p>Loading DXF file from Sales Force. Please wait, this may take a while.</p>
<i class="icon-spin6 animate-spin"></i>
</div>
{% endif %}
{% if not context['csv_available'] %}
<form action="" method="post" enctype="multipart/form-data">
{{ form.csrf_token }}
@@ -33,6 +43,11 @@
<span id="error_message_txt" class="error_message centered_error">{{ field.errors[0] }}</span>
{% endif %}
{% endfor %}
{% if context['infinite_loop_detection_message'] %}
<span id="error_message_txt_inf_loop" class="error_message centered_error">{{ context['infinite_loop_detection_message'] }}</span>
{% endif %}
</div>
</div>

View File

@@ -100,13 +100,13 @@ class FileValidator(object):
finally:
return content
def validate(self, stream, file, expected):
def validate(self, stream, expected, file=None, extension=None):
"""Validates the uploaded file by extension
and content
Arguments;
stream (string): File content
file (FileObject): A file object provided from the ui
file (FileObject): A file object provided from the ui. Used only to get the file extension.
"""
@@ -114,7 +114,8 @@ class FileValidator(object):
file_type = self.identify_file_type(stream)
assert file_type == expected
validator = file_type.validator(self.values)
extension = self.obtain_extension(file)
if extension is None:
extension = self.obtain_extension(file)
file_type.valid_mapping(extension)
return validator.validate(stream)
except AssertionError:

View File

@@ -1118,7 +1118,6 @@ class EbomCalculatorTest(unittest.TestCase):
cable_support_lid: 0,
rear_skirt_1_1: 0,
monitor_controller_480_v: 1,
ethernet_plug: 2,
}
assert_dictionary_equal(self.subject.compute_ebom(), expected_output)

View File

@@ -28,12 +28,12 @@ class FileValidatorTest(unittest.TestCase):
with open('test/fixtures/input_single_tilt.csv', 'r',
newline='') as file:
cad_input = file.read()
eq_(self.subject.validate(cad_input, fake_file, FileType.Csv), None)
eq_(self.subject.validate(cad_input, FileType.Csv, fake_file), None)
def test_unknown_files_are_invalid(self):
fake_file = MagicMock()
type(fake_file).filename = PropertyMock(return_value="Hi")
result = self.subject.validate("Hi", fake_file, FileType.Unknown)
result = self.subject.validate("Hi", FileType.Unknown, fake_file)
self.should_have_error(result,
FileValidationMessage.UnknownFileUploaded, None)
@@ -62,8 +62,7 @@ class FileValidatorTest(unittest.TestCase):
fake_file.read.return_value = open(fname, "rb").read()
fake_file.filename.return_value = "expected_dual_tilt_pseries_image.png"
stream = self.subject.obtain_stream(fake_file)
self.should_have_error(self.subject.validate(stream, fake_file,
FileType.AuroraDxf),
self.should_have_error(self.subject.validate(stream, FileType.AuroraDxf, fake_file),
FileValidationMessage.ExpectedDxfFile,
None)