From dc197555382205369c322134290bbfe1cf1b7427 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Wed, 12 Apr 2017 15:22:27 -0400 Subject: [PATCH 1/7] Display anticipated start and commision date and period in weeks. Create fund and financing overview models. Store entered data in database. --- blocnote/apps/financialInputs/forms.py | 18 ++++++++++++ blocnote/apps/financialInputs/models.py | 15 +++++++++- .../static/financialInputs/scripts/app.js | 9 ++++++ .../templates/financialInputs/headerForm.html | 26 +++++++++++++++++ .../templates/financialInputs/index.html | 14 +++++++-- blocnote/apps/financialInputs/urls.py | 3 +- blocnote/apps/financialInputs/views.py | 29 +++++++++++++++++-- blocnote/settings.py | 6 ++-- blocnote/templates/base.html | 26 +++++++++++++++++ 9 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 blocnote/apps/financialInputs/forms.py create mode 100644 blocnote/apps/financialInputs/static/financialInputs/scripts/app.js create mode 100644 blocnote/apps/financialInputs/templates/financialInputs/headerForm.html diff --git a/blocnote/apps/financialInputs/forms.py b/blocnote/apps/financialInputs/forms.py new file mode 100644 index 0000000..281b3d6 --- /dev/null +++ b/blocnote/apps/financialInputs/forms.py @@ -0,0 +1,18 @@ +from django.forms import ModelForm +from django import forms +from django.forms.extras import SelectDateWidget + +from .models import FinancingOverview + + +class BlocNoteHeaderForm(ModelForm): + anticipated_construction_start_date = forms.DateField(widget=SelectDateWidget()) + anticipated_commissioning_date = forms.DateField(widget=SelectDateWidget()) + + class Meta: + model = FinancingOverview + fields = ('anticipated_construction_start_date', + 'anticipated_commissioning_date', + 'anticipated_construction_period') + + diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 71a8362..feff4e5 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -1,3 +1,16 @@ from django.db import models -# Create your models here. + +class Fund(models.Model): + Name = models.CharField(max_length=200) + + +class FinancingOverview(models.Model): + building_id = models.IntegerField() + fund = models.ForeignKey(Fund, on_delete=models.CASCADE, default=1) + required_noi_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) + requrired_cash_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) + anticipated_construction_start_date = models.DateField() + anticipated_commissioning_date = models.DateField() + anticipated_construction_period = models.DecimalField(max_digits=2, + decimal_places=0) diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js new file mode 100644 index 0000000..e4d81dc --- /dev/null +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -0,0 +1,9 @@ +function headerFormSubmit(form) { + const formData = new FormData(form); + request('./financial-inputs/header/', { + method: 'post', + credentials: 'same-origin', + body: formData + }); + return false; +} diff --git a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html new file mode 100644 index 0000000..962cf58 --- /dev/null +++ b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html @@ -0,0 +1,26 @@ +
+ {% csrf_token %} +
+
+
+ {{ form.anticipated_construction_start_date.label_tag }} +
+
+ {{ form.anticipated_construction_start_date }} +
+
+ {{ form.anticipated_commissioning_date.label_tag }} +
+
+ {{ form.anticipated_commissioning_date }} +
+
+
+
+ {{ form.anticipated_construction_period.label_tag }} + {{ form.anticipated_construction_period }} weeks +
+ +
+
+
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/index.html b/blocnote/apps/financialInputs/templates/financialInputs/index.html index 580a3fd..41229dd 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/index.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/index.html @@ -5,9 +5,16 @@

This is finance inputs

-
- Building id: {{ id }}
- Address: {{ address }} +
+
+
+ Building id: {{ building.building_id }}
+ Address: {{ building.address }} +
+
+ {% include "financialInputs/headerForm.html" with form=header_form %} +
+
{% endblock %} @@ -15,4 +22,5 @@ + {% endblock %} diff --git a/blocnote/apps/financialInputs/urls.py b/blocnote/apps/financialInputs/urls.py index a3780aa..dd9a474 100644 --- a/blocnote/apps/financialInputs/urls.py +++ b/blocnote/apps/financialInputs/urls.py @@ -3,5 +3,6 @@ from django.conf.urls import url from . import views urlpatterns = [ - url(r'^$', views.index, name='index'), + url(r'^$', views.Index, name='index'), + url(r'^/header/$', views.BlocNoteHeader.as_view(), name='header'), ] diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py index 89bc0b7..1984929 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -1,14 +1,37 @@ from django.shortcuts import render -from django.http import HttpResponse +from django.http import HttpResponse, JsonResponse from django.db import connections +from django.views import View +from .models import FinancingOverview +from .forms import BlocNoteHeaderForm -def index(request, building_id): + +def Index(request, building_id): cursor = connections['building_db'].cursor() cursor.execute('SELECT * FROM public.get_building(in_building_id := {})'.format(building_id)) columns = [col[0] for col in cursor.description] row = cursor.fetchone() building = dict(zip(columns, row)) - context = {'address': building['address'], 'id': building['building_id']} + context = { + 'building': building, + 'header_form': BlocNoteHeaderForm(), + } return render(request, 'financialInputs/index.html', context=context) + + +class BlocNoteHeader(View): + model = FinancingOverview + form_class = BlocNoteHeaderForm + + def post(self, request, building_id): + form = self.form_class(request.POST) + if form.is_valid(): + header = form.save(commit=False) + header.building_id = building_id + header.save() + return JsonResponse({**form.data, 'id': header.id}) + + return JsonResponse(form.errors, status=400) + diff --git a/blocnote/settings.py b/blocnote/settings.py index eef190f..cce5531 100644 --- a/blocnote/settings.py +++ b/blocnote/settings.py @@ -135,7 +135,7 @@ USE_TZ = True # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = config('STATIC_URL') -# STATICFILES_DIRS = [ -# os.path.join(BASE_DIR, 'blocnote/static'), -# ] +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, 'blocnote/static'), +] STATIC_ROOT = os.path.join(BASE_DIR, 'static') diff --git a/blocnote/templates/base.html b/blocnote/templates/base.html index 3e4727e..ae04b1b 100644 --- a/blocnote/templates/base.html +++ b/blocnote/templates/base.html @@ -10,11 +10,37 @@ {% block title %}BlocNote{% endblock %} + + {% block head %}{% endblock %} {% block content %}{% endblock %} + + + + {% block scripts %}{% endblock %} -- GitLab From f2869e0053639da21c6455e34f4d417ac92c9fe9 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Wed, 12 Apr 2017 15:33:49 -0400 Subject: [PATCH 2/7] Change submit button orientation --- .../financialInputs/templates/financialInputs/headerForm.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html index 962cf58..fe59845 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html @@ -20,7 +20,9 @@ {{ form.anticipated_construction_period.label_tag }} {{ form.anticipated_construction_period }} weeks
- +
+ +
-- GitLab From 74d94f8177aab0e5679ba6f5cd1da1ff427bb44e Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Wed, 12 Apr 2017 17:58:04 -0400 Subject: [PATCH 3/7] BlocNote header component to select anticipated start and commission date, period and fund for a building. Date validation and period of commission validation not performed --- blocnote/apps/financialInputs/forms.py | 5 ++--- blocnote/apps/financialInputs/models.py | 7 +++++-- .../templates/financialInputs/headerForm.html | 5 +++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/blocnote/apps/financialInputs/forms.py b/blocnote/apps/financialInputs/forms.py index 281b3d6..63bed5c 100644 --- a/blocnote/apps/financialInputs/forms.py +++ b/blocnote/apps/financialInputs/forms.py @@ -13,6 +13,5 @@ class BlocNoteHeaderForm(ModelForm): model = FinancingOverview fields = ('anticipated_construction_start_date', 'anticipated_commissioning_date', - 'anticipated_construction_period') - - + 'anticipated_construction_period', + 'fund') diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index feff4e5..576b0c4 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -4,13 +4,16 @@ from django.db import models class Fund(models.Model): Name = models.CharField(max_length=200) + def __str__(self): + return self.Name + class FinancingOverview(models.Model): building_id = models.IntegerField() - fund = models.ForeignKey(Fund, on_delete=models.CASCADE, default=1) + fund = models.ForeignKey(Fund, on_delete=models.CASCADE, blank=True, null=True) required_noi_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) requrired_cash_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) anticipated_construction_start_date = models.DateField() anticipated_commissioning_date = models.DateField() - anticipated_construction_period = models.DecimalField(max_digits=2, + anticipated_construction_period = models.DecimalField(max_digits=4, decimal_places=0) diff --git a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html index fe59845..8ba5a52 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html @@ -20,6 +20,11 @@ {{ form.anticipated_construction_period.label_tag }} {{ form.anticipated_construction_period }} weeks +
+ {{ form.fund }} +
+ +
-- GitLab From d0d4197806e08122ca72e1955da227b7d7350866 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Fri, 14 Apr 2017 11:33:21 -0400 Subject: [PATCH 4/7] Header component complete. Does GET request to pull up header component. If component for that building already exists, pulls up stored information. Does PUT request to store data in the database. --- blocnote/apps/financialInputs/forms.py | 24 ++++++++- blocnote/apps/financialInputs/models.py | 6 ++- .../static/financialInputs/scripts/app.js | 48 +++++++++++++++-- .../templates/financialInputs/headerForm.html | 54 ++++++++++++------- .../templates/financialInputs/index.html | 29 +++++----- blocnote/apps/financialInputs/urls.py | 4 +- blocnote/apps/financialInputs/views.py | 41 +++++++++----- 7 files changed, 150 insertions(+), 56 deletions(-) diff --git a/blocnote/apps/financialInputs/forms.py b/blocnote/apps/financialInputs/forms.py index 63bed5c..ddbdee6 100644 --- a/blocnote/apps/financialInputs/forms.py +++ b/blocnote/apps/financialInputs/forms.py @@ -1,3 +1,5 @@ +import datetime + from django.forms import ModelForm from django import forms from django.forms.extras import SelectDateWidget @@ -6,12 +8,30 @@ from .models import FinancingOverview class BlocNoteHeaderForm(ModelForm): - anticipated_construction_start_date = forms.DateField(widget=SelectDateWidget()) - anticipated_commissioning_date = forms.DateField(widget=SelectDateWidget()) + years_to_display = range(datetime.datetime.now().year - 15, + datetime.datetime.now().year+50) + anticipated_construction_start_date = forms.DateField(widget=SelectDateWidget(years=years_to_display)) + anticipated_commissioning_date = forms.DateField(widget=SelectDateWidget(years=years_to_display)) + pro_forma_start_date = forms.DateField(widget=SelectDateWidget(years=years_to_display)) + analysis_date = forms.DateField(widget=SelectDateWidget(years=years_to_display)) class Meta: model = FinancingOverview fields = ('anticipated_construction_start_date', 'anticipated_commissioning_date', 'anticipated_construction_period', + 'pro_forma_start_date', + 'pro_forma_duration', + 'analysis_date', 'fund') + + def __init__(self, *args, **kwargs): + super(BlocNoteHeaderForm, self).__init__(*args, **kwargs) + for field in iter(self.fields): + self.fields[field].widget.attrs.update({ + 'class': 'form-control' + }) + self.fields['fund'].widget.attrs.update({ + 'class': 'custom-select' + }) + self.initial['fund'] = 1 diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 576b0c4..24d63e9 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -13,7 +13,11 @@ class FinancingOverview(models.Model): fund = models.ForeignKey(Fund, on_delete=models.CASCADE, blank=True, null=True) required_noi_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) requrired_cash_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) + pro_forma_start_date = models.DateField() + pro_forma_duration = models.DecimalField(max_digits=2, decimal_places=0) + analysis_date = models.DateField() anticipated_construction_start_date = models.DateField() anticipated_commissioning_date = models.DateField() - anticipated_construction_period = models.DecimalField(max_digits=4, + anticipated_construction_period = models.DecimalField(max_digits=3, decimal_places=0) + diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index e4d81dc..b3da3b2 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -1,9 +1,47 @@ function headerFormSubmit(form) { const formData = new FormData(form); - request('./financial-inputs/header/', { - method: 'post', - credentials: 'same-origin', - body: formData - }); + var validDate = validateDate(formData); + if (!validDate) { + alert("Anticipated Commissioning date has to be after Anticipated Construction start date"); + } + else { + const result = {}; + for (const [key, value] of formData.entries()) { + result[key] = value; + } + request('./financial-inputs/finance-overview/', { + method: 'put', + credentials: 'same-origin', + body: JSON.stringify(result), + headers: new Headers({ + 'Content-Type': 'application/json', + 'X-CSRFToken': Cookies.get('csrftoken') + }) + }); + } return false; } + +function validateDate(data) { + var startDateYear = data.get('anticipated_construction_start_date_year'); + var startDateMonth = data.get('anticipated_construction_start_date_month'); + var startDateDay = data.get('anticipated_construction_start_date_day'); + var endDateYear = data.get('anticipated_commissioning_date_year'); + var endDateMonth = data.get('anticipated_commissioning_date_month'); + var endDateDay = data.get('anticipated_commissioning_date_day'); + if (endDateYear < startDateYear) { + return false; + } + else if (endDateYear === startDateYear) { + if (endDateMonth < startDateMonth) { + return false; + } + else if (endDateMonth === startDateMonth) { + if (endDateDay <= startDateDay) { + return false; + } + } + } + return true; +} + diff --git a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html index 8ba5a52..516b7b9 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html @@ -1,33 +1,49 @@
{% csrf_token %} -
-
-
- {{ form.anticipated_construction_start_date.label_tag }} +
+
+
+ {{ form.pro_forma_start_date.label_tag }}
-
- {{ form.anticipated_construction_start_date }} +
+ {{ form.pro_forma_start_date }}
-
- {{ form.anticipated_commissioning_date.label_tag }} +
+ {{ form.pro_forma_duration.label_tag }}
-
- {{ form.anticipated_commissioning_date }} +
+ {{ form.pro_forma_duration }} (years)
-
-
- {{ form.anticipated_construction_period.label_tag }} - {{ form.anticipated_construction_period }} weeks +
+
+
+
+ {{ form.analysis_date.label_tag }}
-
- {{ form.fund }} +
+ {{ form.analysis_date }}
+
+ Select Fund: {{ form.fund }} +
+
+
+
+
+ {{ form.anticipated_construction_start_date.label_tag }} + {{ form.anticipated_construction_start_date }} + + {{ form.anticipated_commissioning_date.label_tag }} + {{ form.anticipated_commissioning_date }}
-
-
+
+
+
+ {{ form.anticipated_construction_period.label_tag }} + {{ form.anticipated_construction_period }} weeks +
-
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/index.html b/blocnote/apps/financialInputs/templates/financialInputs/index.html index 41229dd..07c2840 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/index.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/index.html @@ -1,23 +1,22 @@ {% extends 'base.html' %} {% load staticfiles %} - {% block content %} -

- This is finance inputs -

-
-
-
- Building id: {{ building.building_id }}
- Address: {{ building.address }} -
-
- {% include "financialInputs/headerForm.html" with form=header_form %} -
-
+
+
+

+ Financial Inputs +

+
+ {{ building.address }} +
+
+
+
+ {% include "financialInputs/headerForm.html" with form=header_form %} +
+
{% endblock %} - {% block scripts %} diff --git a/blocnote/apps/financialInputs/urls.py b/blocnote/apps/financialInputs/urls.py index dd9a474..a83e9eb 100644 --- a/blocnote/apps/financialInputs/urls.py +++ b/blocnote/apps/financialInputs/urls.py @@ -3,6 +3,6 @@ from django.conf.urls import url from . import views urlpatterns = [ - url(r'^$', views.Index, name='index'), - url(r'^/header/$', views.BlocNoteHeader.as_view(), name='header'), + url(r'^$', views.index, name='index'), + url(r'^/finance-overview/$', views.BlocNoteHeader.as_view(), name='header'), ] diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py index 1984929..25f5e3f 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -1,5 +1,7 @@ +import json + from django.shortcuts import render -from django.http import HttpResponse, JsonResponse +from django.http import JsonResponse from django.db import connections from django.views import View @@ -7,31 +9,46 @@ from .models import FinancingOverview from .forms import BlocNoteHeaderForm -def Index(request, building_id): +def index(request, building_id): cursor = connections['building_db'].cursor() - cursor.execute('SELECT * FROM public.get_building(in_building_id := {})'.format(building_id)) - + command = 'SELECT * FROM public.get_building(in_building_id := {})' + cursor.execute(command.format(building_id)) columns = [col[0] for col in cursor.description] row = cursor.fetchone() building = dict(zip(columns, row)) + model_obj = get_model_object(FinancingOverview, building_id) + if model_obj: + header_form = BlocNoteHeaderForm(instance=model_obj) + else: + header_form = BlocNoteHeaderForm() context = { 'building': building, - 'header_form': BlocNoteHeaderForm(), + 'header_form': header_form, } return render(request, 'financialInputs/index.html', context=context) +def get_model_object(model, building_id): + try: + obj = model.objects.get(building_id=building_id) + return obj + except: + return 0 + + class BlocNoteHeader(View): model = FinancingOverview form_class = BlocNoteHeaderForm - def post(self, request, building_id): - form = self.form_class(request.POST) + def put(self, request, building_id): + put = json.loads(request.body.decode()) + form = self.form_class(put) + financing_overview_obj = get_model_object(self.model, building_id) + if financing_overview_obj: + form = self.form_class(put, instance=financing_overview_obj) + else: + form.instance.building_id = building_id if form.is_valid(): - header = form.save(commit=False) - header.building_id = building_id - header.save() + header = form.save() return JsonResponse({**form.data, 'id': header.id}) - return JsonResponse(form.errors, status=400) - -- GitLab From 8b140a45297068e257740a129f8f39fc479b43b3 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Wed, 19 Apr 2017 14:22:15 -0400 Subject: [PATCH 5/7] Create header component, Bills Table and Bills Overview table. --- .gitignore | 1 + blocnote/apps/financialInputs/forms.py | 23 +- .../migrations/0001_initial.py | 41 ++ .../migrations/0002_auto_20170412_2043.py | 31 ++ .../migrations/0003_auto_20170412_2143.py | 21 + .../migrations/0004_auto_20170412_2153.py | 20 + .../migrations/0005_auto_20170413_1530.py | 33 ++ .../migrations/0006_auto_20170413_1533.py | 20 + .../financialInputs/migrations/0007_bills.py | 27 ++ .../migrations/0008_auto_20170417_2026.py | 20 + .../migrations/0009_billsoverview.py | 24 + .../migrations/0010_auto_20170417_2338.py | 20 + .../migrations/0011_auto_20170418_0232.py | 59 +++ blocnote/apps/financialInputs/models.py | 22 + .../static/financialInputs/scripts/app.js | 227 +++++++++ .../templates/financialInputs/bills.html | 41 ++ .../financialInputs/billsOverview.html | 5 + .../templates/financialInputs/index.html | 6 + blocnote/apps/financialInputs/urls.py | 4 +- blocnote/apps/financialInputs/views.py | 445 +++++++++++++++++- 20 files changed, 1062 insertions(+), 28 deletions(-) create mode 100644 blocnote/apps/financialInputs/migrations/0001_initial.py create mode 100644 blocnote/apps/financialInputs/migrations/0002_auto_20170412_2043.py create mode 100644 blocnote/apps/financialInputs/migrations/0003_auto_20170412_2143.py create mode 100644 blocnote/apps/financialInputs/migrations/0004_auto_20170412_2153.py create mode 100644 blocnote/apps/financialInputs/migrations/0005_auto_20170413_1530.py create mode 100644 blocnote/apps/financialInputs/migrations/0006_auto_20170413_1533.py create mode 100644 blocnote/apps/financialInputs/migrations/0007_bills.py create mode 100644 blocnote/apps/financialInputs/migrations/0008_auto_20170417_2026.py create mode 100644 blocnote/apps/financialInputs/migrations/0009_billsoverview.py create mode 100644 blocnote/apps/financialInputs/migrations/0010_auto_20170417_2338.py create mode 100644 blocnote/apps/financialInputs/migrations/0011_auto_20170418_0232.py create mode 100644 blocnote/apps/financialInputs/templates/financialInputs/bills.html create mode 100644 blocnote/apps/financialInputs/templates/financialInputs/billsOverview.html diff --git a/.gitignore b/.gitignore index 8310350..32b1e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ node_modules # misc .idea +.vscode/ diff --git a/blocnote/apps/financialInputs/forms.py b/blocnote/apps/financialInputs/forms.py index ddbdee6..7b371e3 100644 --- a/blocnote/apps/financialInputs/forms.py +++ b/blocnote/apps/financialInputs/forms.py @@ -8,12 +8,27 @@ from .models import FinancingOverview class BlocNoteHeaderForm(ModelForm): + """Header Form. + + Form that appears at the top of the page. This class specifies the fields + and sets the form field types for dates, sets the range of dates to display + and adds attributes to some of the fields. + """ + years_to_display = range(datetime.datetime.now().year - 15, datetime.datetime.now().year+50) - anticipated_construction_start_date = forms.DateField(widget=SelectDateWidget(years=years_to_display)) - anticipated_commissioning_date = forms.DateField(widget=SelectDateWidget(years=years_to_display)) - pro_forma_start_date = forms.DateField(widget=SelectDateWidget(years=years_to_display)) - analysis_date = forms.DateField(widget=SelectDateWidget(years=years_to_display)) + anticipated_construction_start_date = forms.DateField( + widget=SelectDateWidget(years=years_to_display) + ) + anticipated_commissioning_date = forms.DateField( + widget=SelectDateWidget(years=years_to_display) + ) + pro_forma_start_date = forms.DateField( + widget=SelectDateWidget(years=years_to_display) + ) + analysis_date = forms.DateField( + widget=SelectDateWidget(years=years_to_display) + ) class Meta: model = FinancingOverview diff --git a/blocnote/apps/financialInputs/migrations/0001_initial.py b/blocnote/apps/financialInputs/migrations/0001_initial.py new file mode 100644 index 0000000..934afed --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-12 15:19 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='FinancingOverview', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('required_noi_dscr', models.DecimalField(decimal_places=2, max_digits=5)), + ('requrired_cash_dscr', models.DecimalField(decimal_places=2, max_digits=5)), + ('anticipated_construction_start_date', models.DateField()), + ('anticipated_commissioning_date', models.DateField()), + ('anticipated_construction_period', models.DecimalField(decimal_places=0, max_digits=2)), + ], + ), + migrations.CreateModel( + name='Fund', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('Name', models.CharField(max_length=200)), + ], + ), + migrations.AddField( + model_name='financingoverview', + name='fund', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Fund'), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0002_auto_20170412_2043.py b/blocnote/apps/financialInputs/migrations/0002_auto_20170412_2043.py new file mode 100644 index 0000000..bb6bee6 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0002_auto_20170412_2043.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-12 20:43 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='financingoverview', + name='fund', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Fund'), + ), + migrations.AlterField( + model_name='financingoverview', + name='required_noi_dscr', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5), + ), + migrations.AlterField( + model_name='financingoverview', + name='requrired_cash_dscr', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0003_auto_20170412_2143.py b/blocnote/apps/financialInputs/migrations/0003_auto_20170412_2143.py new file mode 100644 index 0000000..2929542 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0003_auto_20170412_2143.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-12 21:43 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0002_auto_20170412_2043'), + ] + + operations = [ + migrations.AlterField( + model_name='financingoverview', + name='fund', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='financialInputs.Fund'), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0004_auto_20170412_2153.py b/blocnote/apps/financialInputs/migrations/0004_auto_20170412_2153.py new file mode 100644 index 0000000..ca34540 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0004_auto_20170412_2153.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-12 21:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0003_auto_20170412_2143'), + ] + + operations = [ + migrations.AlterField( + model_name='financingoverview', + name='anticipated_construction_period', + field=models.DecimalField(decimal_places=0, max_digits=4), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0005_auto_20170413_1530.py b/blocnote/apps/financialInputs/migrations/0005_auto_20170413_1530.py new file mode 100644 index 0000000..3086180 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0005_auto_20170413_1530.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-13 15:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0004_auto_20170412_2153'), + ] + + operations = [ + migrations.AddField( + model_name='financingoverview', + name='analysis_date', + field=models.DateField(default='2017-01-01'), + preserve_default=False, + ), + migrations.AddField( + model_name='financingoverview', + name='pro_forma_duration', + field=models.DecimalField(decimal_places=0, default=2, max_digits=2), + preserve_default=False, + ), + migrations.AddField( + model_name='financingoverview', + name='pro_forma_start_date', + field=models.DateField(default='2017-01-01'), + preserve_default=False, + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0006_auto_20170413_1533.py b/blocnote/apps/financialInputs/migrations/0006_auto_20170413_1533.py new file mode 100644 index 0000000..a101685 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0006_auto_20170413_1533.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-13 15:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0005_auto_20170413_1530'), + ] + + operations = [ + migrations.AlterField( + model_name='financingoverview', + name='anticipated_construction_period', + field=models.DecimalField(decimal_places=0, max_digits=3), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0007_bills.py b/blocnote/apps/financialInputs/migrations/0007_bills.py new file mode 100644 index 0000000..cf60028 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0007_bills.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-17 15:04 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0006_auto_20170413_1533'), + ] + + operations = [ + migrations.CreateModel( + name='Bills', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('date_from', models.DateField()), + ('date_to', models.DateField()), + ('utility_type', models.CharField(max_length=11)), + ('usage', models.DecimalField(decimal_places=2, max_digits=6)), + ('charge', models.DecimalField(decimal_places=2, max_digits=10)), + ], + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0008_auto_20170417_2026.py b/blocnote/apps/financialInputs/migrations/0008_auto_20170417_2026.py new file mode 100644 index 0000000..3ecb611 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0008_auto_20170417_2026.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-17 20:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0007_bills'), + ] + + operations = [ + migrations.AlterField( + model_name='bills', + name='usage', + field=models.DecimalField(decimal_places=2, max_digits=10), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0009_billsoverview.py b/blocnote/apps/financialInputs/migrations/0009_billsoverview.py new file mode 100644 index 0000000..6f0b789 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0009_billsoverview.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-17 23:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0008_auto_20170417_2026'), + ] + + operations = [ + migrations.CreateModel( + name='BillsOverview', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('building_id', models.IntegerField()), + ('year', models.DecimalField(decimal_places=0, max_digits=4)), + ('estimation_algorithm', models.CharField(choices=[('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior')], max_length=100)), + ], + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0010_auto_20170417_2338.py b/blocnote/apps/financialInputs/migrations/0010_auto_20170417_2338.py new file mode 100644 index 0000000..5a4c4fd --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0010_auto_20170417_2338.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-17 23:38 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0009_billsoverview'), + ] + + operations = [ + migrations.AlterField( + model_name='billsoverview', + name='estimation_algorithm', + field=models.CharField(max_length=100), + ), + ] diff --git a/blocnote/apps/financialInputs/migrations/0011_auto_20170418_0232.py b/blocnote/apps/financialInputs/migrations/0011_auto_20170418_0232.py new file mode 100644 index 0000000..8198ef5 --- /dev/null +++ b/blocnote/apps/financialInputs/migrations/0011_auto_20170418_0232.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-18 02:32 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('financialInputs', '0010_auto_20170417_2338'), + ] + + operations = [ + migrations.AddField( + model_name='billsoverview', + name='electricity', + field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10), + preserve_default=False, + ), + migrations.AddField( + model_name='billsoverview', + name='electricity_is_user_input', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='billsoverview', + name='gas', + field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10), + preserve_default=False, + ), + migrations.AddField( + model_name='billsoverview', + name='gas_is_user_input', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='billsoverview', + name='oil', + field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10), + preserve_default=False, + ), + migrations.AddField( + model_name='billsoverview', + name='oil_is_user_input', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='billsoverview', + name='water', + field=models.DecimalField(decimal_places=2, default=0, max_digits=10), + preserve_default=False, + ), + migrations.AddField( + model_name='billsoverview', + name='water_is_user_input', + field=models.BooleanField(default=False), + ), + ] diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 24d63e9..7f844a4 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -21,3 +21,25 @@ class FinancingOverview(models.Model): anticipated_construction_period = models.DecimalField(max_digits=3, decimal_places=0) + +class Bills(models.Model): + building_id = models.IntegerField() + date_from = models.DateField() + date_to = models.DateField() + utility_type = models.CharField(max_length=11) + usage = models.DecimalField(max_digits=10, decimal_places=2) + charge = models.DecimalField(max_digits=10, decimal_places=2) + + +class BillsOverview(models.Model): + building_id = models.IntegerField() + year = models.DecimalField(max_digits=4, decimal_places=0) + estimation_algorithm = models.CharField(max_length=100) + electricity = models.DecimalField(max_digits=10, decimal_places=2) + electricity_is_user_input = models.BooleanField(default=False) + water = models.DecimalField(max_digits=10, decimal_places=2) + water_is_user_input = models.BooleanField(default=False) + oil = models.DecimalField(max_digits=10, decimal_places=2) + oil_is_user_input = models.BooleanField(default=False) + gas = models.DecimalField(max_digits=10, decimal_places=2) + gas_is_user_input = models.BooleanField(default=False) diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index b3da3b2..13447d9 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -1,4 +1,17 @@ +utilities = ['electricity', 'gas', 'oil', 'water']; +for (var utility_index in utilities) { + loadInitialBillsTable(utilities[utility_index]); +} +loadBillsOverview(); + function headerFormSubmit(form) { + /* + * Handle submition of the header form. Validate commissioning date is greater + * than construction start date. Create result dictionary containing the form + * data and convert into JSON to send to the backend. Upon success, load the + * Bills overview table as the Pro Forma year changes the start year of the + * Bills overview table. + */ const formData = new FormData(form); var validDate = validateDate(formData); if (!validDate) { @@ -17,12 +30,15 @@ function headerFormSubmit(form) { 'Content-Type': 'application/json', 'X-CSRFToken': Cookies.get('csrftoken') }) + }).then(res => { + loadBillsOverview(); }); } return false; } function validateDate(data) { + /* Validate that commissioning date is after the construction start date. */ var startDateYear = data.get('anticipated_construction_start_date_year'); var startDateMonth = data.get('anticipated_construction_start_date_month'); var startDateDay = data.get('anticipated_construction_start_date_day'); @@ -45,3 +61,214 @@ function validateDate(data) { return true; } +function loadInitialBillsTable(utility) { + /* + * Load initial bills table given a utility. Send GET request to obtain + * bill information and call updateTable function to update the bills table + * with the data obtained from backend. + */ + const table = document.querySelector('#'+utility); + + request(`./financial-inputs/bills/?utility_type=${utility}`, { + method: 'get', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + }).then(res => { + if (res.payload.present) { + var text = getText(res.payload.result, utility); + updateTable(utility, text); + } + }) +} + + +function updateTable(utility, text) { + /* Update the Bills table with the data. Function queries to get the table + * component and then updates the component with the text. + */ + table = document.querySelector('#'+utility); + table.innerHTML = text; +} + +function getText(result, utility) { + /* Generate html text for a given utility and bills data from backend. */ + var text = ` + + + + + + + + + `; + for(var i =0; i`; + for(var j=0; j` + result[i][j] + ``; + } + text += ``; + } + text += ``; + return text; +} + +function uploadFile(data) { + /* Upload an energy bill. Get the id of the component to identify the + * utility type. Use reader to read as text and call the sendFile function + * to send the contents to the backend. + */ + const id = document.getElementsByClassName("show")[0].getAttribute("id"); + csvFile = data.files[0]; + var reader = new FileReader(); + reader.onload = function(e) { + var content = {} + content.text = reader.result; + content.utility_type = id; + sendFile(id, content); + } + reader.readAsText(csvFile); + return false; +} + +function sendFile(id, content) { + /* HTTP PUT request to upload the contents of the utility bills file */ + request(`./financial-inputs/bills/`, { + method: 'put', + credentials: 'same-origin', + body: JSON.stringify(content), + headers: new Headers({ + 'Content-Type': 'application/json', + 'X-CSRFToken': Cookies.get('csrftoken') + }) + }).then(res => { + var text = getText(res.payload.result, id); + updateTable(id, text); + loadBillsOverview(); + }); +} + +function startOverviewForm() { + /* Create form tag Energy Bills Overview */ + var text = ``; + return text; +} + +function estimationDropDownBox() { + /* Create dropdown box to select estimation algorithm */ + var text = ``; + return text; +} + +function createOverviewColumnHeaders(year) { + /* Create table and column headings */ + var text = `
Date FromDate ToUsage Bill Charge ($)
+ + + + + + + `; + return text; +} + +function createFlag(state) { + /* Display if user input is needed or not */ + if(state) { + text = `Need User Input`; + } + else { + text = `Bill Data Present`; + } + return text; +} + +function createInput(id, state, value) { + /* Create input field. Add attribute readonly if value is grabbed from + * database. If value is not from database, make it a user input. Name + * the input along with utility type to uniquely identify it. + */ + var text = ``; + var is_readonly = ``; + if (!state) { + is_readonly = `readonly`; + } + text = ``; + return text; +} + +function createOverviewRows(data) { + /* Add rows to the energy bills table. Currently adds only one entry in + * each column. Will be modified to display entries for 20 years. + */ + var user_input = ''; + var text = ``; + for (var utility in utilities) { + user_input = utilities[utility] + '_is_user_input'; + text += ` + + + + `; + } + text += ` +
User InputUtility/Year${year}
+ ${createFlag(data[user_input])} + ${utilities[utility]} + ${createInput(utilities[utility], data[user_input], data[utilities[utility]])} +
+ + `; + return text; +} + +function loadBillsOverview() { + /* Make HTTP GET request to get the bills overview information. Call all + * the relevant functions to create the energy overview table. + */ + request(`./financial-inputs/bills-overview/`, { + method: 'get', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + }).then(res => { + if(!res.err) { + const table = document.querySelector('#Energy-Bills-Overview'); + var text = ""; + text += startOverviewForm(); + text += estimationDropDownBox(); + text += createOverviewColumnHeaders(res.payload.instance.year); + text += createOverviewRows(res.payload.instance); + table.innerHTML = text; + } + }) +} + +function billsOverviewFormSubmit(data) { + /* Handle Submition of energy bills overview form. Get form data and create + * a dictionary result with the formdata. Send result to backend. This will + * be modified to receive all projected rows for ~20 years. + */ + const formData = new FormData(data); + const result = {}; + for (const [key, value] of formData.entries()) { + result[key] = value; + } + request(`./financial-inputs/bills-overview/`, { + method: 'put', + credentials: 'same-origin', + body: JSON.stringify(result), + headers: new Headers({ + 'Content-Type': 'application/json', + 'X-CSRFToken': Cookies.get('csrftoken') + }) + }); + return false; +} diff --git a/blocnote/apps/financialInputs/templates/financialInputs/bills.html b/blocnote/apps/financialInputs/templates/financialInputs/bills.html new file mode 100644 index 0000000..b57366d --- /dev/null +++ b/blocnote/apps/financialInputs/templates/financialInputs/bills.html @@ -0,0 +1,41 @@ +
+

Energy Bills

+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/billsOverview.html b/blocnote/apps/financialInputs/templates/financialInputs/billsOverview.html new file mode 100644 index 0000000..c015442 --- /dev/null +++ b/blocnote/apps/financialInputs/templates/financialInputs/billsOverview.html @@ -0,0 +1,5 @@ +
+

Energy Bills Overview

+
+
+
diff --git a/blocnote/apps/financialInputs/templates/financialInputs/index.html b/blocnote/apps/financialInputs/templates/financialInputs/index.html index 07c2840..f615ef0 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/index.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/index.html @@ -16,6 +16,12 @@ {% include "financialInputs/headerForm.html" with form=header_form %}
+
+ {% include "financialInputs/bills.html" %} +
+
+ {% include "financialInputs/billsOverview.html" %} +
{% endblock %} {% block scripts %} diff --git a/blocnote/apps/financialInputs/urls.py b/blocnote/apps/financialInputs/urls.py index a83e9eb..435e306 100644 --- a/blocnote/apps/financialInputs/urls.py +++ b/blocnote/apps/financialInputs/urls.py @@ -3,6 +3,8 @@ from django.conf.urls import url from . import views urlpatterns = [ - url(r'^$', views.index, name='index'), + url(r'^$', views.Index.as_view(), name='index'), url(r'^/finance-overview/$', views.BlocNoteHeader.as_view(), name='header'), + url(r'^/bills/$', views.BillsTable.as_view(), name='bills'), + url(r'^/bills-overview/$', views.BillsOverviewView.as_view(), name='bills_overview'), ] diff --git a/blocnote/apps/financialInputs/views.py b/blocnote/apps/financialInputs/views.py index 25f5e3f..0488f73 100644 --- a/blocnote/apps/financialInputs/views.py +++ b/blocnote/apps/financialInputs/views.py @@ -1,34 +1,25 @@ import json - from django.shortcuts import render from django.http import JsonResponse from django.db import connections from django.views import View -from .models import FinancingOverview +from .models import FinancingOverview, Bills, BillsOverview from .forms import BlocNoteHeaderForm -def index(request, building_id): - cursor = connections['building_db'].cursor() - command = 'SELECT * FROM public.get_building(in_building_id := {})' - cursor.execute(command.format(building_id)) - columns = [col[0] for col in cursor.description] - row = cursor.fetchone() - building = dict(zip(columns, row)) - model_obj = get_model_object(FinancingOverview, building_id) - if model_obj: - header_form = BlocNoteHeaderForm(instance=model_obj) - else: - header_form = BlocNoteHeaderForm() - context = { - 'building': building, - 'header_form': header_form, - } - return render(request, 'financialInputs/index.html', context=context) +def get_model_object(model, building_id): + """Function that returns a model object. + + Return the model object for a given model for a building. + Args: + model: Model class whose object needs to be obtained. + building_id: The id of the building to which the model belongs. -def get_model_object(model, building_id): + Returns: + obj: The object for the given model and building_id. + """ try: obj = model.objects.get(building_id=building_id) return obj @@ -36,13 +27,116 @@ def get_model_object(model, building_id): return 0 +def change_date_format(date): + """Change date format. + + The CSV file contains date in MM/DD/YYYY format. The database needs it + to be in YYYY-MM-DD. This function performs this task. + + Args: + date: The date in MM/DD/YYYY format. + + Returns: + new_date: The date in YYYY-MM-DD format. + """ + date_split = date.split('/') + new_date = date_split[2] + '-' + date_split[0] + '-' + date_split[1] + return new_date + + +class Index(View): + """Class that renders the index page.""" + + model = FinancingOverview + + def get_building_data(self, building_id): + """Return building data given it's id. + + Connect to the building database and retrieves the building data. + + Args: + building_id: id of the building_id. + + Returns: + building: A dictionary containing all the relevant building data. + """ + cursor = connections['building_db'].cursor() + command = 'SELECT * FROM public.get_building(in_building_id := {})' + cursor.execute(command.format(building_id)) + columns = [col[0] for col in cursor.description] + row = cursor.fetchone() + building = dict(zip(columns, row)) + return building + + def get_context_for_index(self, building_id, building): + """Return context for index page. + + Create the context for the index page. The index page should get the + building data(which is a dictionary) and the header form. The function + checks if a header form for the given building. If it does not exist, + it creates a new form else it takes the existing form. + + Args: + building_id: id of the building. + building: A dictionary containing the building data. + + Returns: + context: A dictionary with the building data and header form. + """ + financing_overview_model_obj = get_model_object( + self.model, building_id + ) + if financing_overview_model_obj: + header_form = BlocNoteHeaderForm( + instance=financing_overview_model_obj + ) + else: + header_form = BlocNoteHeaderForm() + context = { + 'building': building, + 'header_form': header_form, + } + return context + + def get(self, request, building_id): + """GET route for Index page. + + Get the building data and context. Renders result to the frontend. + + Args: + request: HTTP GET request. + building_id: id of the building. + + Returns: + render: The index.html page with the context. + """ + building = self.get_building_data(building_id) + context = self.get_context_for_index(building_id, building) + return render(request, 'financialInputs/index.html', context=context) + + class BlocNoteHeader(View): + """This class handles header creation and update.""" + model = FinancingOverview form_class = BlocNoteHeaderForm - def put(self, request, building_id): - put = json.loads(request.body.decode()) - form = self.form_class(put) + def handle_form(self, put, form, building_id): + """Handle form submit. + + Take the header form passed by the frontend and validates it. If it is + a valid form, it makes an entry in the database and returns the JSON + response to be sent to the frontend. + + Args: + put: This is the data sent in the PUT request by frontend. + form: Header form with data passed by frontend. + building_id: id of the building. + + Returns: + JsonResponse: A JSON response sent to the frontend. If success, it + sends the form data else it returns error. + """ financing_overview_obj = get_model_object(self.model, building_id) if financing_overview_obj: form = self.form_class(put, instance=financing_overview_obj) @@ -52,3 +146,308 @@ class BlocNoteHeader(View): header = form.save() return JsonResponse({**form.data, 'id': header.id}) return JsonResponse(form.errors, status=400) + + def put(self, request, building_id): + """PUT route for header. + + Handle the PUT request for the header form. It loads the JSON data + onto a form and handles the submition of the form. It sends a JSON + response to the frontend with form data or error status. + + Args: + request: HTTP PUT request. + building_id: id of the building. + + Returns: + JsonResponse: If success, returns form data and if not, returns + error status as JSON. + """ + put = json.loads(request.body.decode()) + form = self.form_class(put) + return self.handle_form(put, form, building_id) + + +class BillsTable(View): + """Create the Energy Bills tables. + + Class to upload energy bills for each utility, store bills and send + the data to the frontend. + """ + + model = Bills + + def put(self, request, building_id): + """Handle HTTP PUT request. + + Obtain bill information from frontend as JSON. The data is in CSV. + The relevant data is stored in the database. Upon successfull upload, + the data is sent to the frontend to create the table. + + Args: + request: HTTP PUT request. + building_id: id of the building. + + Returns: + JsonResponse: JSON of the result which is a list containing + rows to be displayed. + """ + put = json.loads(request.body.decode()) + result = self.handle_file(put, building_id) + return JsonResponse({'result': result}) + + def handle_file(self, data, building_id): + """Handle file input. + + Take file data from frontend and obtain the indices of the relevant + columns. Go through the file and get the relevant data and store in the + database. + + Args: + data: The file data obtained from frontend. + building_id: id of the building. + + Returns: + result: List of rows to be sent to the frontend to display table. + """ + utility_type = data['utility_type'] + + self.model.objects.filter( + building_id=building_id, + utility_type=utility_type + ).delete() + + text = data['text'].split('\n') + + column_headings = text[0].split(',') + for index, heading in enumerate(column_headings): + if heading == "Bill From Date": + date_from_index = index + elif heading == "Bill To Date": + date_to_index = index + elif heading == "Usage": + usage_index = index + elif heading == "Total Charges": + charge_index = index + + result = [] + for line in range(1, len(text)-1): + row_list = [] + line_array = text[line].split(',') + date_from = change_date_format(line_array[date_from_index]) + row_list.append(date_from) + date_to = change_date_format(line_array[date_to_index]) + row_list.append(date_to) + usage = line_array[usage_index] + row_list.append(usage) + charge = line_array[charge_index] + row_list.append(charge) + self.model.objects.create(building_id=building_id, + date_from=date_from, + date_to=date_to, + utility_type=data['utility_type'], + usage=usage, + charge=charge) + result.append(row_list) + + return result + + def get(self, request, building_id): + """Handle HTTP GET request. + + Fetch stored utility bills data and send to the frontend to display as + table. + + Args: + request: HTTP GET request. + building_id: id of the building. + + Return: + JsonResponse: JSON of the result which is a list of rows to populate + the table. + """ + result = [] + present = False + utility_type = request.GET.get('utility_type') + + try: + model_obj = self.model.objects.filter( + building_id=building_id, + utility_type=utility_type + ) + if model_obj: + present = True + for row in model_obj: + row_list = [] + row_list.append(row.date_from) + row_list.append(row.date_to) + row_list.append(row.usage) + row_list.append(row.charge) + result.append(row_list) + + return JsonResponse({'result': result, 'present': present}) + except: + return JsonResponse({'result': result, 'present': present}) + + +def get_total_charge(obj): + """Get total utility charge. + + Obtain the total charge for a utility. This function will be replaced by + finance team logic. + + Args: + object: Model object which contains the charge value. + + Returns: + total: The sum of all charges for the utility. + """ + total = 0 + for row in obj: + total += row.charge + return total + + +class BillsOverviewView(View): + """Generate Energy Bills Overview table. + + Generate energy bills overview table from the energy bills data. If bill not + available, take user input for calculation purposes. + """ + + model_bills_overview = BillsOverview + model_bills = Bills + utility = ['electricity', 'gas', 'oil', 'water'] + + def get(self, request, building_id): + """Handle HTTP GET request. + + Generate energy bills overview table with bills present in the + database. If a certain utility bill is absent, allow user to input + data. + + Args: + request: HTTP GET request. + building_id: id of the building. + + Returns: + JsonResponse: Returns instance, which is a dictionary containing + all the data to be filled in the table. + """ + instance = self.get_instance(building_id) + return JsonResponse({'instance': instance}) + + def get_instance(self, building_id): + """Create dictionary instance. + + This dictionary is an instance of the data present in the database. It + contains the annual utility charge(for now, this will be replaced by + value obtained from finance logic), Pro Forma start year and flags + indicating if a utility is present in the database. This is to tell the + frontend if an input has to be taken or not. + + Args: + building_id: id of the building. + + Returns: + instance: A dictionary containing the annual utility charge and a + flag indicating if utility bill present or not. + """ + instance = {} + + try: + obj = FinancingOverview.objects.get(building_id=building_id) + date = obj.pro_forma_start_date + year = date.year + except: + year = 'TBD' + + utility_objects = {} + for util in self.utility: + utility_objects[util] = self.model_bills.objects.filter( + building_id=building_id, + utility_type=util + ) + + for util in self.utility: + instance[util] = 0 + instance[util+'_is_user_input'] = True + + for util in self.utility: + if utility_objects[util]: + instance[util] = get_total_charge( + utility_objects[util] + ) + instance[util+'_is_user_input'] = False + else: + try: + bills_overview_obj = self.model_bills_overview.objects.get( + building_id=building_id + ) + if util == 'electricity': + instance[util] = bills_overview_obj.electricity + elif util == 'gas': + instance[util] = bills_overview_obj.gas + elif util == 'oil': + instance[util] = bills_overview_obj.oil + elif util == 'water': + instance[util] = bills_overview_obj.water + except: + instance[util] = 0 + instance['year'] = year + + return instance + + def put(self, request, building_id): + """Handle HTTP PUT request. + + Take annual utility charge and projection option from frontend and + store in the database. This will be modified to call the finance + projection fuction. + + Args: + request: HTTP PUT request. + building_id: id of the building. + + Returns: + JsonResponse: Returns new instance with the updated values. + """ + put = json.loads(request.body.decode()) + + entry = self.model_bills_overview.objects.filter( + building_id=building_id + ) + + instance = self.get_instance(building_id) + + if entry: + entry.update( + building_id=building_id, + year=instance['year'], + electricity=put['electricity-value'], + electricity_is_user_input=instance['electricity_is_user_input'], + gas=put['gas-value'], + gas_is_user_input=instance['gas_is_user_input'], + oil=put['oil-value'], + oil_is_user_input=instance['oil_is_user_input'], + water=put['water-value'], + water_is_user_input=instance['water_is_user_input'], + estimation_algorithm=put['Estimation Model'] + ) + else: + entry = self.model_bills_overview.objects.create( + building_id=building_id, + year=instance['year'], + electricity=put['electricity-value'], + electricity_is_user_input=instance['electricity_is_user_input'], + gas=put['gas-value'], + gas_is_user_input=instance['gas_is_user_input'], + oil=put['oil-value'], + oil_is_user_input=instance['oil_is_user_input'], + water=put['water-value'], + water_is_user_input=instance['water_is_user_input'], + estimation_algorithm=put['Estimation Model'] + ) + + new_instance = self.get_instance(building_id) + + return JsonResponse({'status': new_instance}) -- GitLab From 1a3349fae22b164d56cdd1205b82aaddeb44b245 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Wed, 19 Apr 2017 15:18:10 -0400 Subject: [PATCH 6/7] Add comments to models.py file. --- blocnote/apps/financialInputs/models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/blocnote/apps/financialInputs/models.py b/blocnote/apps/financialInputs/models.py index 7f844a4..b10647c 100644 --- a/blocnote/apps/financialInputs/models.py +++ b/blocnote/apps/financialInputs/models.py @@ -2,13 +2,22 @@ from django.db import models class Fund(models.Model): + """Model that represents which fund is the money from.""" + Name = models.CharField(max_length=200) def __str__(self): + """Return fund name in string format.""" return self.Name class FinancingOverview(models.Model): + """Basic dates and fund. + + Store necessary dates, fund name and relevant time periods. This is used in + Bill Projection in bpfin. + """ + building_id = models.IntegerField() fund = models.ForeignKey(Fund, on_delete=models.CASCADE, blank=True, null=True) required_noi_dscr = models.DecimalField(max_digits=5, decimal_places=2, default=0) @@ -23,6 +32,8 @@ class FinancingOverview(models.Model): class Bills(models.Model): + """Utility bill information obtained from an external file.""" + building_id = models.IntegerField() date_from = models.DateField() date_to = models.DateField() @@ -32,6 +43,8 @@ class Bills(models.Model): class BillsOverview(models.Model): + """Store annual charge of each utility used for bill projection.""" + building_id = models.IntegerField() year = models.DecimalField(max_digits=4, decimal_places=0) estimation_algorithm = models.CharField(max_length=100) -- GitLab From 5ac05d45d9e6833e4e1f5282221ca920b3f16e47 Mon Sep 17 00:00:00 2001 From: Adarsh Murthy Date: Wed, 19 Apr 2017 16:13:01 -0400 Subject: [PATCH 7/7] Add space around + operator in forms. Change function name from headerForm to billProjectionDatesForm. Change url in blocnote to have a trailing / and removed leading slashes in urls of financialInputs. Change request route in app.js to not have the period and unnecessary slashes. --- blocnote/apps/financialInputs/forms.py | 2 +- .../static/financialInputs/scripts/app.js | 18 +++++++++--------- .../templates/financialInputs/headerForm.html | 2 +- blocnote/apps/financialInputs/urls.py | 6 +++--- blocnote/urls.py | 3 ++- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/blocnote/apps/financialInputs/forms.py b/blocnote/apps/financialInputs/forms.py index 7b371e3..584eda4 100644 --- a/blocnote/apps/financialInputs/forms.py +++ b/blocnote/apps/financialInputs/forms.py @@ -16,7 +16,7 @@ class BlocNoteHeaderForm(ModelForm): """ years_to_display = range(datetime.datetime.now().year - 15, - datetime.datetime.now().year+50) + datetime.datetime.now().year + 50) anticipated_construction_start_date = forms.DateField( widget=SelectDateWidget(years=years_to_display) ) diff --git a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js index 13447d9..9f076c4 100644 --- a/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js +++ b/blocnote/apps/financialInputs/static/financialInputs/scripts/app.js @@ -4,7 +4,7 @@ for (var utility_index in utilities) { } loadBillsOverview(); -function headerFormSubmit(form) { +function billProjectionDatesForm(form) { /* * Handle submition of the header form. Validate commissioning date is greater * than construction start date. Create result dictionary containing the form @@ -22,7 +22,7 @@ function headerFormSubmit(form) { for (const [key, value] of formData.entries()) { result[key] = value; } - request('./financial-inputs/finance-overview/', { + request('finance-overview/', { method: 'put', credentials: 'same-origin', body: JSON.stringify(result), @@ -69,7 +69,7 @@ function loadInitialBillsTable(utility) { */ const table = document.querySelector('#'+utility); - request(`./financial-inputs/bills/?utility_type=${utility}`, { + request(`bills/?utility_type=${utility}`, { method: 'get', credentials: 'same-origin', headers: { @@ -135,7 +135,7 @@ function uploadFile(data) { function sendFile(id, content) { /* HTTP PUT request to upload the contents of the utility bills file */ - request(`./financial-inputs/bills/`, { + request(`bills/`, { method: 'put', credentials: 'same-origin', body: JSON.stringify(content), @@ -232,7 +232,7 @@ function loadBillsOverview() { /* Make HTTP GET request to get the bills overview information. Call all * the relevant functions to create the energy overview table. */ - request(`./financial-inputs/bills-overview/`, { + request(`bills-overview/`, { method: 'get', credentials: 'same-origin', headers: { @@ -258,10 +258,10 @@ function billsOverviewFormSubmit(data) { */ const formData = new FormData(data); const result = {}; - for (const [key, value] of formData.entries()) { - result[key] = value; - } - request(`./financial-inputs/bills-overview/`, { + for (const [key, value] of formData.entries()) { + result[key] = value; + } + request(`bills-overview/`, { method: 'put', credentials: 'same-origin', body: JSON.stringify(result), diff --git a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html index 516b7b9..c5b3ccd 100644 --- a/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html +++ b/blocnote/apps/financialInputs/templates/financialInputs/headerForm.html @@ -1,4 +1,4 @@ -
+ {% csrf_token %}
diff --git a/blocnote/apps/financialInputs/urls.py b/blocnote/apps/financialInputs/urls.py index 435e306..7f66bed 100644 --- a/blocnote/apps/financialInputs/urls.py +++ b/blocnote/apps/financialInputs/urls.py @@ -4,7 +4,7 @@ from . import views urlpatterns = [ url(r'^$', views.Index.as_view(), name='index'), - url(r'^/finance-overview/$', views.BlocNoteHeader.as_view(), name='header'), - url(r'^/bills/$', views.BillsTable.as_view(), name='bills'), - url(r'^/bills-overview/$', views.BillsOverviewView.as_view(), name='bills_overview'), + url(r'^finance-overview/$', views.BlocNoteHeader.as_view(), name='header'), + url(r'^bills/$', views.BillsTable.as_view(), name='bills'), + url(r'^bills-overview/$', views.BillsOverviewView.as_view(), name='bills_overview'), ] diff --git a/blocnote/urls.py b/blocnote/urls.py index b55bc83..69e442f 100644 --- a/blocnote/urls.py +++ b/blocnote/urls.py @@ -17,6 +17,7 @@ from django.conf.urls import include, url from django.contrib import admin urlpatterns = [ - url(r'^building/(?P[0-9]+)/financial-inputs', include('blocnote.apps.financialInputs.urls')), + url('', admin.site.urls), + url(r'^building/(?P[0-9]+)/financial-inputs/', include('blocnote.apps.financialInputs.urls')), url(r'^admin/', admin.site.urls), ] -- GitLab