diff --git a/app/config/development.default.py b/app/config/development.default.py index 6fd0056805c1af886d1815a0c7e0eae1b998ad75..e5fab21efef71719af8207f1484ad9b0208f4488 100644 --- a/app/config/development.default.py +++ b/app/config/development.default.py @@ -40,3 +40,15 @@ EMAIL_RECEIVING_ADDRESS = os.environ['EMAIL_RECEIVING_ADDRESS'] EMAIL_SENDING_PASSWORD = os.environ['EMAIL_SENDING_PASSWORD'] EMAIL_SENDING_SMTP = os.environ['EMAIL_SENDING_SMTP'] EMAIL_SENDING_PORT = os.environ['EMAIL_SENDING_PORT'] + +# Automate config +AUTOMATE_EMAIL_SENDING_ADDRESS = os.environ['AUTOMATE_EMAIL_SENDING_ADDRESS'] +# comma seperated list of emails +AUTOMATE_EMAIL_RECEIVING_ADDRESS = os.environ['AUTOMATE_EMAIL_RECEIVING_ADDRESS'] +AUTOMATE_EMAIL_SENDING_PASSWORD = os.environ['AUTOMATE_EMAIL_SENDING_PASSWORD'] +AUTOMATE_EMAIL_SENDING_SMTP = os.environ['AUTOMATE_EMAIL_SENDING_SMTP'] +AUTOMATE_EMAIL_SENDING_PORT = os.environ['AUTOMATE_EMAIL_SENDING_PORT'] + +# URLs for sending emails about accounts +BLOCLINK_URL = os.environ['BLOCLINK_URL'] +DASHBOARD_URL = os.environ['DASHBOARD_URL'] diff --git a/app/config/local.default.py b/app/config/local.default.py index b6ff77eff1822b9b1a2278597ca423dfc32416b9..6a9be784a8b023d2def8246d604a4a26942478ff 100644 --- a/app/config/local.default.py +++ b/app/config/local.default.py @@ -35,8 +35,20 @@ HEADER_AUTH_KEY = 'x-blocpower-auth-key' HEADER_AUTH_TOKEN = 'x-blocpower-auth-token' # Email config -EMAIL_SENDING_ADDRESS = os.environ['EMAIL_SENDING_ADDRESS'] -EMAIL_RECEIVING_ADDRESS = os.environ['EMAIL_RECEIVING_ADDRESS'] -EMAIL_SENDING_PASSWORD = os.environ['EMAIL_SENDING_PASSWORD'] -EMAIL_SENDING_SMTP = os.environ['EMAIL_SENDING_SMTP'] -EMAIL_SENDING_PORT = os.environ['EMAIL_SENDING_PORT'] +EMAIL_SENDING_ADDRESS = '$EMAIL_SENDING_ADDRESS' +EMAIL_RECEIVING_ADDRESS = '$EMAIL_RECEIVING_ADDRESS' +EMAIL_SENDING_PASSWORD = '$EMAIL_SENDING_PASSWORD' +EMAIL_SENDING_SMTP = '$EMAIL_SENDING_SMTP' +EMAIL_SENDING_PORT = '$EMAIL_SENDING_PORT' + +# Automate config +AUTOMATE_EMAIL_SENDING_ADDRESS = '$AUTOMATE_EMAIL_SENDING_ADDRESS' +# comma seperated list of emails +AUTOMATE_EMAIL_RECEIVING_ADDRESS = '$AUTOMATE_EMAIL_RECEIVING_ADDRESS' +AUTOMATE_EMAIL_SENDING_PASSWORD = '$AUTOMATE_EMAIL_SENDING_PASSWORD' +AUTOMATE_EMAIL_SENDING_SMTP = '$AUTOMATE_EMAIL_SENDING_SMTP' +AUTOMATE_EMAIL_SENDING_PORT = '$AUTOMATE_EMAIL_SENDING_PORT' + +# URLs for sending emails about accounts +BLOCLINK_URL = '$BLOCLINK_URL' +DASHBOARD_URL = '$DASHBOARD_URL' diff --git a/app/config/production.default.py b/app/config/production.default.py index b770f040a2df22d59419545bde641c51fa871aef..039ab142b7bbded1ee7cf26ff8d42463ace0c120 100644 --- a/app/config/production.default.py +++ b/app/config/production.default.py @@ -40,3 +40,15 @@ EMAIL_RECEIVING_ADDRESS = os.environ['EMAIL_RECEIVING_ADDRESS'] EMAIL_SENDING_PASSWORD = os.environ['EMAIL_SENDING_PASSWORD'] EMAIL_SENDING_SMTP = os.environ['EMAIL_SENDING_SMTP'] EMAIL_SENDING_PORT = os.environ['EMAIL_SENDING_PORT'] + +# Automate config +AUTOMATE_EMAIL_SENDING_ADDRESS = os.environ['AUTOMATE_EMAIL_SENDING_ADDRESS'] +# comma seperated list of emails +AUTOMATE_EMAIL_RECEIVING_ADDRESS = os.environ['AUTOMATE_EMAIL_RECEIVING_ADDRESS'] +AUTOMATE_EMAIL_SENDING_PASSWORD = os.environ['AUTOMATE_EMAIL_SENDING_PASSWORD'] +AUTOMATE_EMAIL_SENDING_SMTP = os.environ['AUTOMATE_EMAIL_SENDING_SMTP'] +AUTOMATE_EMAIL_SENDING_PORT = os.environ['AUTOMATE_EMAIL_SENDING_PORT'] + +# URLs for sending emails about accounts +BLOCLINK_URL = os.environ['BLOCLINK_URL'] +DASHBOARD_URL = os.environ['DASHBOARD_URL'] diff --git a/app/config/staging.default.py b/app/config/staging.default.py index d3a80a157c8f3148c1c3f51612e3493935edf43a..88db037ad2ac53784c4032470a45c26f353a2517 100644 --- a/app/config/staging.default.py +++ b/app/config/staging.default.py @@ -40,3 +40,15 @@ EMAIL_RECEIVING_ADDRESS = os.environ['EMAIL_RECEIVING_ADDRESS'] EMAIL_SENDING_PASSWORD = os.environ['EMAIL_SENDING_PASSWORD'] EMAIL_SENDING_SMTP = os.environ['EMAIL_SENDING_SMTP'] EMAIL_SENDING_PORT = os.environ['EMAIL_SENDING_PORT'] + +# Automate config +AUTOMATE_EMAIL_SENDING_ADDRESS = os.environ['AUTOMATE_EMAIL_SENDING_ADDRESS'] +# comma seperated list of emails +AUTOMATE_EMAIL_RECEIVING_ADDRESS = os.environ['AUTOMATE_EMAIL_RECEIVING_ADDRESS'] +AUTOMATE_EMAIL_SENDING_PASSWORD = os.environ['AUTOMATE_EMAIL_SENDING_PASSWORD'] +AUTOMATE_EMAIL_SENDING_SMTP = os.environ['AUTOMATE_EMAIL_SENDING_SMTP'] +AUTOMATE_EMAIL_SENDING_PORT = os.environ['AUTOMATE_EMAIL_SENDING_PORT'] + +# URLs for sending emails about accounts +BLOCLINK_URL = os.environ['BLOCLINK_URL'] +DASHBOARD_URL = os.environ['DASHBOARD_URL'] diff --git a/app/config/test.default.py b/app/config/test.default.py index abe778631772bb1740f4a6a5cea9d7b2148b42af..9ed5722a961a1e6097d4ea0fcc18f19ae38c0e34 100644 --- a/app/config/test.default.py +++ b/app/config/test.default.py @@ -39,6 +39,18 @@ HEADER_AUTH_TOKEN = 'x-blocpower-auth-token' UTILITY_SCRAPER_KEY = os.environ['UTILITY_SCRAPER_KEY'] UTILITY_SCRAPER_REFERRER = os.environ['UTILITY_SCRAPER_REFERRER'] +# Automate config +AUTOMATE_EMAIL_SENDING_ADDRESS = os.environ['AUTOMATE_EMAIL_SENDING_ADDRESS'] +# comma seperated list of emails +AUTOMATE_EMAIL_RECEIVING_ADDRESS = os.environ['AUTOMATE_EMAIL_RECEIVING_ADDRESS'] +AUTOMATE_EMAIL_SENDING_PASSWORD = os.environ['AUTOMATE_EMAIL_SENDING_PASSWORD'] +AUTOMATE_EMAIL_SENDING_SMTP = os.environ['AUTOMATE_EMAIL_SENDING_SMTP'] +AUTOMATE_EMAIL_SENDING_PORT = os.environ['AUTOMATE_EMAIL_SENDING_PORT'] + +# URLs for sending emails about accounts +BLOCLINK_URL = os.environ['BLOCLINK_URL'] +DASHBOARD_URL = os.environ['DASHBOARD_URL'] + # HACK Fix issue where raising an exception in a test context creates an extra # request. PRESERVE_CONTEXT_ON_EXCEPTION = False diff --git a/app/controllers/account.py b/app/controllers/account.py index c1baf20acfc599f39e2af50be612c46b144029be..7f8d5d1d4bbd24cf2e770ef36873c1f82e123ba6 100644 --- a/app/controllers/account.py +++ b/app/controllers/account.py @@ -1,14 +1,17 @@ -"""Controllers for posting or manipulating an account.""" +"""Controllers for posting or manipulating a account.""" from .base import RestController from .bill import BillController +from .disaggregate import DisaggregateController +from .scrape import ScrapeController from .disaggregated_bill import DisaggregatedBillController from ..forms.account import AccountForm from ..models.account import Account +from flask import g, current_app from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from flask import g, current_app import smtplib +from threading import Thread from werkzeug.datastructures import MultiDict from werkzeug.exceptions import BadRequest, Unauthorized @@ -70,14 +73,76 @@ class AccountController(RestController): not data['username'] and not data['password'] and data['access_code'] and - utility == "national_grid_gas" + utility == "national_grid_gas" and + not filter_data.get('automate') ): - self.utility_email_notification( + self.national_grid_email_notification( data['account_number'], data['access_code'], email_address, ) - return super().post(data, filter_data) + + result = super().post(data, filter_data) + if filter_data.get('automate'): + post_data = { + 'account_id': result.id, + 'utility': utility, + 'account_number': result.account_number, + 'username': result.username, + 'password': result.password, + 'building_id': result.building_id, + 'usage_type': result.usage_type, + } + scrape_thread = Thread( + target=self.automate_scrape, + args=( + current_app._get_current_object(), + post_data, + filter_data, + utility + ) + ) + scrape_thread.start() + return result + + def automate_scrape(self, app, post_data, filter_data, utility): + with app.app_context(): + additional_text = '' + try: + ScrapeController().post(post_data, filter_data) + additional_text += '\nScraping was successful' + except Exception as e: + if utility == 'national_grid_gas': + additional_text += '\nScraping was not succesful. '\ + 'Please add this national grid account to our '\ + 'account and run the scraper again' + else: + additional_text += '\nScraping was not successful' + + additional_text += '\n' + try: + result = DisaggregateController().post(post_data, filter_data) + if (result['disaggregate_database_output']): + additional_text += '\nDisaggregation was successful' + else: + additional_text += '\nDisaggregation was not successful due to no bill data' + except Exception as e: + try: + if e.description == 'Less than a years worth of data. Cannot disaggregate': + additional_text += '\nDisaggregation was not successful because there is less than a year of data' + else: + additional_text += '\nDisaggregation was not successful' + except: + pass + additional_text += '\nDisaggregation was not successful' + + self.automate_email_notification( + post_data['account_number'], + utility, + post_data['building_id'], + additional_text, + ) + def put(self, id_, data, filter_data): utility = data.pop('utility') @@ -155,7 +220,7 @@ class AccountController(RestController): except KeyError: raise BadRequest('Invalid utility {} given'.format(utility)) - def utility_email_notification(self, account_number, access_code, to_address=None): + def national_grid_email_notification(self, account_number, access_code, to_address=None): """ send out email notification to request for manual input """ from_address = current_app.config.get("EMAIL_SENDING_ADDRESS") from_password = current_app.config.get("EMAIL_SENDING_PASSWORD") @@ -163,10 +228,7 @@ class AccountController(RestController): to_address = current_app.config.get("EMAIL_RECEIVING_ADDRESS") smtp = current_app.config.get("EMAIL_SENDING_SMTP") port = current_app.config.get("EMAIL_SENDING_PORT") - msg = MIMEMultipart() - msg['From'] = from_address - msg['To'] = to_address - msg['Subject'] = "Notification for NationalGrid Utility Account " + subject = "Notification for NationalGrid Utility Account " body = 'In order for the scraper to work, the account number and '\ 'access code need to be added to blocpower national grid ' \ @@ -181,8 +243,64 @@ class AccountController(RestController): 'and login with user BlocpowerTest (ask a developer for '\ 'the password). Once logged, click "Add Account" on the right '\ 'side.'.format(account_number, access_code) - msg.attach(MIMEText(body, 'plain')) + self.send_email( + from_address, + from_password, + to_address, + smtp, + port, + subject, + body, + ) + + def automate_email_notification(self, account_number, utility, building_id, additional_text): + from_address = current_app.config.get("AUTOMATE_EMAIL_SENDING_ADDRESS") + from_password = current_app.config.get("AUTOMATE_EMAIL_SENDING_PASSWORD") + to_address_list = current_app.config.get("AUTOMATE_EMAIL_RECEIVING_ADDRESS") + smtp = current_app.config.get("AUTOMATE_EMAIL_SENDING_SMTP") + port = current_app.config.get("AUTOMATE_EMAIL_SENDING_PORT") + subject = "Notification for BlocMaps automated input " + + body = 'An account for {} (account number {}) has been automatically '\ + 'added to the dashboard, probably from blocmaps. Please navigate to '\ + '{}/buildings/{}/utilities to '\ + 'view the bill.\n\n{}\n\nA simulation has also started. Check the status at '\ + '{}/buildings/{}/envelope'.format( + utility, + account_number, + current_app.config['DASHBOARD_URL'], + building_id, + additional_text, + current_app.config['BLOCLINK_URL'], + building_id, + ) + for to_address in to_address_list.split(','): + self.send_email( + from_address, + from_password, + to_address, + smtp, + port, + subject, + body, + ) + + def send_email( + self, + from_address, + from_password, + to_address, + smtp, + port, + subject, + body, + ): + msg = MIMEMultipart() + msg['From'] = from_address + msg['To'] = to_address + msg['Subject'] = subject + msg.attach(MIMEText(body, 'plain')) try: server = smtplib.SMTP(smtp, port) server.starttls() diff --git a/app/controllers/scrape.py b/app/controllers/scrape.py index 2acbda6006a6d37f7ee73227f281c611b47c087e..2c24f60e2537c75c0f159abb976c1fd8e2e0695d 100644 --- a/app/controllers/scrape.py +++ b/app/controllers/scrape.py @@ -1,6 +1,5 @@ """Controllers for posting or manipulating a scrape.""" from .base import RestController -from ..controllers.disaggregated_bill import DisaggregatedBillController from ..controllers.bill import BillController from ..forms.account import AccountForm from ..lib.database import db diff --git a/app/views/account.py b/app/views/account.py index 086d8d7c278271b4e40bacefa57f84f332773b6e..582ece241cec3afc8df175cca7f36eb196447ddd 100644 --- a/app/views/account.py +++ b/app/views/account.py @@ -1,17 +1,22 @@ """Views for working with accounts.""" from werkzeug.exceptions import MethodNotAllowed, BadRequest from ..controllers.account import AccountController -from .base import RestView +from .base import UnprotectedRestView from ..permissions.application import app_need +from ..permissions.auth import standard_login_need from flask import request -class AccountView(RestView): +class AccountView(UnprotectedRestView): + decorators = (app_need,) + """The account view.""" def get_controller(self): """Return an instance of the account controller.""" return AccountController() + + @standard_login_need def index(self): """/{id} GET - Retrieve a resource by id.""" return_list = [] @@ -26,10 +31,16 @@ class AccountView(RestView): return self.json(return_list) + @standard_login_need def get(self, id_): """/{id} GET - Retrieve a resource by id.""" return self.json(self.get_controller().get(id_, request.args)) + @standard_login_need + def put(self, id_): + return super().put(id_) + + @standard_login_need def delete(self, id_): response = self.get_controller().delete( id_, request.args)