Fix merge conflict
This commit is contained in:
commit
9a20cf72a8
29 changed files with 657 additions and 364 deletions
|
@ -9,7 +9,8 @@ verify_ssl = true
|
||||||
django = "==2.1.5"
|
django = "==2.1.5"
|
||||||
django-cors-headers = "==2.4.0"
|
django-cors-headers = "==2.4.0"
|
||||||
djangorestframework = "==3.8.2"
|
djangorestframework = "==3.8.2"
|
||||||
|
django-rest-auth = "==0.9.3"
|
||||||
|
django-allauth = "==0.37.1"
|
||||||
gunicorn = "==19.6.0"
|
gunicorn = "==19.6.0"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
|
|
86
back/Pipfile.lock
generated
86
back/Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "b5222b4256c8f09a9b1b1d380285fa65c443f84d28dc03450684fca84b38a26b"
|
"sha256": "b1fc6b06ec8daa4efd9573865bc6c1732ae9354309e036bfe3ce0ab76b1a3bcd"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -16,6 +16,27 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
|
||||||
|
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
|
||||||
|
],
|
||||||
|
"version": "==2018.11.29"
|
||||||
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
|
},
|
||||||
|
"defusedxml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4",
|
||||||
|
"sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20"
|
||||||
|
],
|
||||||
|
"version": "==0.5.0"
|
||||||
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8",
|
"sha256:a32c22af23634e1d11425574dce756098e015a165be02e4690179889b207c7a8",
|
||||||
|
@ -24,6 +45,13 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.1.5"
|
"version": "==2.1.5"
|
||||||
},
|
},
|
||||||
|
"django-allauth": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02175aa1c2ddfd935a54011d1196d70c976647fc46f603f8b8758fc395b9d277"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.37.1"
|
||||||
|
},
|
||||||
"django-cors-headers": {
|
"django-cors-headers": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5545009c9b233ea7e70da7dbab7cb1c12afa01279895086f98ec243d7eab46fa",
|
"sha256:5545009c9b233ea7e70da7dbab7cb1c12afa01279895086f98ec243d7eab46fa",
|
||||||
|
@ -32,6 +60,13 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.4.0"
|
"version": "==2.4.0"
|
||||||
},
|
},
|
||||||
|
"django-rest-auth": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ad155a0ed1061b32e3e46c9b25686e397644fd6acfd35d5c03bc6b9d2fc6c82a"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.9.3"
|
||||||
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:b6714c3e4b0f8d524f193c91ecf5f5450092c2145439ac2769711f7eba89a9d9",
|
"sha256:b6714c3e4b0f8d524f193c91ecf5f5450092c2145439ac2769711f7eba89a9d9",
|
||||||
|
@ -48,12 +83,61 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==19.6.0"
|
"version": "==19.6.0"
|
||||||
},
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
|
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||||
|
],
|
||||||
|
"version": "==2.8"
|
||||||
|
},
|
||||||
|
"oauthlib": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298",
|
||||||
|
"sha256:3e1e14f6cde7e5475128d30e97edc3bfb4dc857cb884d8714ec161fdbb3b358e"
|
||||||
|
],
|
||||||
|
"version": "==3.0.1"
|
||||||
|
},
|
||||||
|
"python3-openid": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa",
|
||||||
|
"sha256:628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502"
|
||||||
|
],
|
||||||
|
"version": "==3.1.0"
|
||||||
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
|
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
|
||||||
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
|
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
|
||||||
],
|
],
|
||||||
"version": "==2018.9"
|
"version": "==2018.9"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||||
|
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
|
||||||
|
],
|
||||||
|
"version": "==2.21.0"
|
||||||
|
},
|
||||||
|
"requests-oauthlib": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57",
|
||||||
|
"sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140"
|
||||||
|
],
|
||||||
|
"version": "==1.2.0"
|
||||||
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||||
|
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||||
|
],
|
||||||
|
"version": "==1.12.0"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
|
||||||
|
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
|
||||||
|
],
|
||||||
|
"version": "==1.24.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {}
|
"develop": {}
|
||||||
|
|
0
back/backend/__init__.py
Normal file
0
back/backend/__init__.py
Normal file
18
back/backend/migrations/0005_field_field_name.py
Normal file
18
back/backend/migrations/0005_field_field_name.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.1.5 on 2019-02-07 22:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('backend', '0004_auto_20190131_1645'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='field',
|
||||||
|
name='field_name',
|
||||||
|
field=models.CharField(default='field', max_length=512),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import datetime
|
import datetime
|
||||||
|
import ntpath
|
||||||
|
|
||||||
class Report(models.Model):
|
class Report(models.Model):
|
||||||
user_id = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
user_id = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
|
@ -26,17 +27,20 @@ class Section(models.Model):
|
||||||
|
|
||||||
class Field(models.Model):
|
class Field(models.Model):
|
||||||
section_id = models.ForeignKey(Section, on_delete=models.CASCADE)
|
section_id = models.ForeignKey(Section, on_delete=models.CASCADE)
|
||||||
|
field_name = models.CharField(max_length=512, default="field")
|
||||||
label = models.CharField(max_length=512)
|
label = models.CharField(max_length=512)
|
||||||
number = models.IntegerField()
|
number = models.IntegerField()
|
||||||
type = models.CharField(max_length=128)
|
type = models.CharField(max_length=128)
|
||||||
completed = models.BooleanField(default=False)
|
completed = models.BooleanField(default=False)
|
||||||
data_bool = models.BooleanField(default=False)
|
data_bool = models.BooleanField(default=False)
|
||||||
data_decimal = models.DecimalField(max_digits=9,decimal_places=2, null=True, blank=True)
|
data_decimal = models.DecimalField(max_digits=9, decimal_places=2, null=True, blank=True)
|
||||||
data_date = models.DateField(default=datetime.date.today)
|
data_date = models.DateField(default=datetime.date.today)
|
||||||
data_file = models.FileField(upload_to='uploads/%Y/%m/%d/', max_length=512, null=True, blank=True)
|
data_file = models.FileField(upload_to='uploads/%Y/%m/%d/', max_length=512, null=True, blank=True)
|
||||||
data_string = models.TextField(default='', blank=True)
|
data_string = models.TextField(default='', blank=True)
|
||||||
data_integer = models.IntegerField(default=0, blank=True)
|
data_integer = models.IntegerField(default=0, blank=True)
|
||||||
|
|
||||||
|
# function that prints the string representation
|
||||||
|
# on the api?
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.type == "boolean":
|
if self.type == "boolean":
|
||||||
if self.data_bool:
|
if self.data_bool:
|
||||||
|
@ -53,3 +57,31 @@ class Field(models.Model):
|
||||||
return "{}".format(self.data_string)
|
return "{}".format(self.data_string)
|
||||||
elif self.type == "integer":
|
elif self.type == "integer":
|
||||||
return "{}".format(self.data_integer)
|
return "{}".format(self.data_integer)
|
||||||
|
|
||||||
|
|
||||||
|
# function that gets corresponding
|
||||||
|
# data type
|
||||||
|
def get_datatype(self):
|
||||||
|
if self.type == "boolean":
|
||||||
|
if self.data_bool:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif self.type == "decimal":
|
||||||
|
return self.data_decimal
|
||||||
|
elif self.type == "date":
|
||||||
|
return "{}".format(self.data_date)
|
||||||
|
elif self.type == "file":
|
||||||
|
file_name = self.path_leaf(str(self.data_file))
|
||||||
|
return "{}".format(file_name)
|
||||||
|
elif self.type == "string":
|
||||||
|
return "{}".format(self.data_string)
|
||||||
|
elif self.type == "integer":
|
||||||
|
return self.data_integer
|
||||||
|
|
||||||
|
# function that accommodates if
|
||||||
|
# path has slash at end
|
||||||
|
def path_leaf(self, path):
|
||||||
|
head, tail = ntpath.split(path)
|
||||||
|
return tail or ntpath.basename(head)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,37 @@
|
||||||
# simple_policy.py
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from policy import Policy, Section
|
|
||||||
|
|
||||||
# - For the rules, should one refer to fields by 'section.fields.x'
|
#### Classes for policy, sections.
|
||||||
# or by the section name eg. 'general_section.fields.x'?
|
|
||||||
|
|
||||||
|
class Policy():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.sections = []
|
||||||
|
|
||||||
|
def add_section(self, section):
|
||||||
|
self.sections.append(section)
|
||||||
|
|
||||||
|
class Section():
|
||||||
|
|
||||||
|
def __init__(self, title="Section", html_description="", required=False,
|
||||||
|
auto_submit=False, fields={}):
|
||||||
|
self.title = title
|
||||||
|
self.html_description = html_description
|
||||||
|
self.required = required
|
||||||
|
self.auto_submit = auto_submit
|
||||||
|
self.fields = fields
|
||||||
|
self.rules = []
|
||||||
|
|
||||||
|
def add_rule(self, title="Rule", rule=None, rule_break_text=""):
|
||||||
|
rule = {
|
||||||
|
"title": title,
|
||||||
|
"rule": rule,
|
||||||
|
"rule_break_text": rule_break_text,
|
||||||
|
}
|
||||||
|
self.rules.append(rule)
|
||||||
|
|
||||||
|
#### Policy configuration begin here
|
||||||
|
|
||||||
|
pol = Policy()
|
||||||
|
|
||||||
#### General
|
#### General
|
||||||
#### Section 0
|
#### Section 0
|
||||||
|
@ -12,7 +39,7 @@ general_section = Section(
|
||||||
title="General Info",
|
title="General Info",
|
||||||
html_description="",
|
html_description="",
|
||||||
fields={
|
fields={
|
||||||
"destination": {"label": "Destination City", "type": "string"}
|
"destination": {"label": "Destination City", "type": "string"},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,7 +49,7 @@ general_section.add_rule(
|
||||||
rule_break_text="What did the cowboy say about Tim, his wild horse?"
|
rule_break_text="What did the cowboy say about Tim, his wild horse?"
|
||||||
)
|
)
|
||||||
|
|
||||||
Policy.add_section(general_section)
|
pol.add_section(general_section)
|
||||||
|
|
||||||
#### Flight
|
#### Flight
|
||||||
#### Section 1
|
#### Section 1
|
||||||
|
@ -34,6 +61,7 @@ flight_section = Section(
|
||||||
"departure_date": {"label": "Departure date", "type": "date"},
|
"departure_date": {"label": "Departure date", "type": "date"},
|
||||||
"return_date": {"label": "Return date", "type": "date"},
|
"return_date": {"label": "Return date", "type": "date"},
|
||||||
"fare": {"label": "Fare", "type": "decimal"},
|
"fare": {"label": "Fare", "type": "decimal"},
|
||||||
|
"layovers": {"label": "Transit wait", "type": "integer"},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,13 +71,14 @@ flight_section.add_rule(
|
||||||
rule_break_text="Fares cannot be more than $500"
|
rule_break_text="Fares cannot be more than $500"
|
||||||
)
|
)
|
||||||
|
|
||||||
Policy.add_section(flight_section)
|
pol.add_section(flight_section)
|
||||||
|
|
||||||
#### Lodging
|
#### Lodging
|
||||||
#### Section 2
|
#### Section 2
|
||||||
lodging_section = Section(
|
lodging_section = Section(
|
||||||
title="Hotel Info",
|
title="Hotel Info",
|
||||||
html_description="<p>Enter hotel info here.\nPer diem rates can be found at <a href='https://www.gsa.gov/travel/plan-book/per-diem-rates'></a></p>",
|
html_description="<p>Enter hotel info here.\nPer diem rates can be found at "
|
||||||
|
"<a href='https://www.gsa.gov/travel/plan-book/per-diem-rates'></a></p>",
|
||||||
fields={
|
fields={
|
||||||
"check-in_date": {"label": "Check-in date", "type": "date"},
|
"check-in_date": {"label": "Check-in date", "type": "date"},
|
||||||
"check-out_date": {"label": "Check-out date", "type": "date"},
|
"check-out_date": {"label": "Check-out date", "type": "date"},
|
||||||
|
@ -64,13 +93,13 @@ def nightly_rate_check(report, section):
|
||||||
duration = checkout_date - checkin_date
|
duration = checkout_date - checkin_date
|
||||||
return section.fields.cost <= duration * section.fields.rate
|
return section.fields.cost <= duration * section.fields.rate
|
||||||
|
|
||||||
section.add_rule(
|
lodging_section.add_rule(
|
||||||
title="",
|
title="",
|
||||||
rule=nightly_rate_check,
|
rule=nightly_rate_check,
|
||||||
rule_break_text="The average nightly rate cannot be more than the USGSA rate."
|
rule_break_text="The average nightly rate cannot be more than the USGSA rate."
|
||||||
)
|
)
|
||||||
|
|
||||||
Policy.add_section(lodging_section)
|
pol.add_section(lodging_section)
|
||||||
|
|
||||||
#### Local Transportation
|
#### Local Transportation
|
||||||
#### Section 3
|
#### Section 3
|
||||||
|
@ -89,7 +118,7 @@ transport_section.add_rule(
|
||||||
rule_break_text="Local transportation costs must be less than $10 per day, on average."
|
rule_break_text="Local transportation costs must be less than $10 per day, on average."
|
||||||
)
|
)
|
||||||
|
|
||||||
Policy.add_section(transport_section)
|
pol.add_section(transport_section)
|
||||||
|
|
||||||
#### Per Diem
|
#### Per Diem
|
||||||
#### Section 4
|
#### Section 4
|
||||||
|
@ -109,24 +138,4 @@ per_diem_section.add_rule(
|
||||||
rule_break_text="The average cost per day for per diem expenses cannot be more than the rate specified by the USGSA."
|
rule_break_text="The average cost per day for per diem expenses cannot be more than the rate specified by the USGSA."
|
||||||
)
|
)
|
||||||
|
|
||||||
Policy.add_section(per_diem_section)
|
pol.add_section(per_diem_section)
|
||||||
|
|
||||||
'''
|
|
||||||
Section(
|
|
||||||
title="",
|
|
||||||
html_description="<p></p>",
|
|
||||||
fields={
|
|
||||||
"": {"label": "", "type": ""}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
section.add_rule(
|
|
||||||
title="",
|
|
||||||
rule=lambda report, section: boolean_statement,
|
|
||||||
rule_break_text=""
|
|
||||||
)
|
|
||||||
|
|
||||||
#// or, for a rule which doesn’t apply to a specific section...
|
|
||||||
#//
|
|
||||||
#// add_general_rule(...)
|
|
||||||
'''
|
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
# Rupika Dikkala
|
|
||||||
# January 23, 2019
|
|
||||||
# File contains serializers needed
|
|
||||||
# to set up API end points
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
from . import models
|
|
||||||
|
|
||||||
# serializer for reports
|
|
||||||
class ReportSerializer(serializers.ModelSerializer):
|
|
||||||
# user id is foreign key
|
|
||||||
user_id = serializers.PrimaryKeyRelatedField(many=False, read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
fields = (
|
|
||||||
'user_id',
|
|
||||||
'title',
|
|
||||||
'date_created',
|
|
||||||
# 'data_submitted',
|
|
||||||
'submitted',
|
|
||||||
)
|
|
||||||
model = models.Report
|
|
||||||
|
|
||||||
|
|
||||||
# section serializer
|
|
||||||
class SectionSerializer(serializers.ModelSerializer):
|
|
||||||
# report id foriegn key
|
|
||||||
report_id = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
fields = (
|
|
||||||
'report_id',
|
|
||||||
'completed',
|
|
||||||
'title',
|
|
||||||
'html_description',
|
|
||||||
'number',
|
|
||||||
)
|
|
||||||
model = models.Section
|
|
||||||
|
|
||||||
|
|
||||||
class FieldSerializer(serializers.ModelSerializer):
|
|
||||||
# section_id is foriegn key
|
|
||||||
section_id = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
fields = (
|
|
||||||
'section_id',
|
|
||||||
'label',
|
|
||||||
'number',
|
|
||||||
'type',
|
|
||||||
'completed',
|
|
||||||
)
|
|
||||||
model = models.Field
|
|
||||||
|
|
||||||
|
|
||||||
class DataSerializer(serializers.ModelSerializer):
|
|
||||||
field_id = serializers.PrimaryKeyRelatedField(many=False, read_only=True)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
3
back/backend/test.py
Normal file
3
back/backend/test.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from policy import pol
|
||||||
|
|
||||||
|
print(pol)
|
|
@ -5,16 +5,10 @@ from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
#path('', views.List.as_view()),
|
|
||||||
#path('<int:pk>/', views.Detail.as_view()),
|
|
||||||
|
|
||||||
path('report', views.report),
|
path('report', views.report),
|
||||||
path('reports', views.reports),
|
path('reports', views.reports),
|
||||||
path('report/<int:report_pk>', views.report_detail),
|
path('report/<int:report_pk>', views.report_detail),
|
||||||
path('report/<int:report_pk>/section/<int:section_pk>', views.section),
|
path('report/<int:report_pk>/section/<int:section_pk>', views.section),
|
||||||
path('account', views.account),
|
|
||||||
path('account/login', views.account_login),
|
|
||||||
path('account/logout', views.account_logout),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||||
|
|
|
@ -1,194 +1,145 @@
|
||||||
from rest_framework import generics
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from django.shortcuts import render
|
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from .models import *
|
from .models import *
|
||||||
from .serializers import *
|
from .policy import pol
|
||||||
|
import ntpath
|
||||||
|
|
||||||
# Sample view using generics
|
|
||||||
|
|
||||||
class List(generics.ListCreateAPIView):
|
# function that prints all the reports
|
||||||
queryset = Report.objects.all()
|
def get_reports(report_pk):
|
||||||
serializer_class = ReportSerializer
|
queryset = Report.objects.filter(id=report_pk)
|
||||||
|
for i in queryset:
|
||||||
|
data = {
|
||||||
|
"report_pk": report_pk,
|
||||||
|
"title": i.title,
|
||||||
|
"date_created": i.date_created,
|
||||||
|
"submitted": i.submitted,
|
||||||
|
"date_submitted": i.date_submitted,
|
||||||
|
}
|
||||||
|
# append the sections for each report
|
||||||
|
data.update(get_sections(i.id))
|
||||||
|
|
||||||
|
# return JsonResponse(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
# function that gets all the sections
|
||||||
|
# takes report_id param
|
||||||
|
def get_sections(r_id):
|
||||||
|
# create a dict of arrays for section
|
||||||
|
section_set = {"sections": []}
|
||||||
|
queryset = Section.objects.filter(report_id=r_id)
|
||||||
|
for i in queryset:
|
||||||
|
data = {
|
||||||
|
"id": i.id,
|
||||||
|
"completed": i.completed,
|
||||||
|
"title": i.title,
|
||||||
|
"html_description": i.html_description,
|
||||||
|
}
|
||||||
|
# append the fields for corresponding section
|
||||||
|
data.update(get_fields(i.id))
|
||||||
|
# append section to the array
|
||||||
|
section_set["sections"].append(data.copy())
|
||||||
|
|
||||||
|
return section_set
|
||||||
|
|
||||||
|
# function that gets all the fields
|
||||||
|
# takes section_id param
|
||||||
|
def get_fields(s_id):
|
||||||
|
# create dict of arrays for fields
|
||||||
|
field_set = {"fields": []}
|
||||||
|
queryset = Field.objects.filter(section_id=s_id).order_by('number')
|
||||||
|
|
||||||
|
for i in queryset:
|
||||||
|
# function that gets the corresponding datatype
|
||||||
|
value = Field.get_datatype(i)
|
||||||
|
data = {
|
||||||
|
"field_name": i.field_name,
|
||||||
|
"label": i.label,
|
||||||
|
"type": i.type,
|
||||||
|
"number": i.number,
|
||||||
|
"value": value
|
||||||
|
}
|
||||||
|
# append the fields to array
|
||||||
|
# use copy() to avoid overwriting
|
||||||
|
field_set["fields"].append(data.copy())
|
||||||
|
|
||||||
|
return field_set
|
||||||
|
|
||||||
class Detail(generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
queryset = Report.objects.all()
|
|
||||||
serializer_class = ReportSerializer
|
|
||||||
|
|
||||||
# API Endpoints
|
# API Endpoints
|
||||||
|
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
def report(request):
|
def report(request):
|
||||||
'''
|
'''
|
||||||
Generate a new empty report and return it
|
Generate a new empty report and return it
|
||||||
'''
|
'''
|
||||||
data = {
|
|
||||||
"title": "2018 Portland trip",
|
# Create the report
|
||||||
"date_created": "2018-05-22T14:56:28.000Z",
|
report = Report.objects.create(user_id=request.user, title=request.data['title'],
|
||||||
"submitted": False,
|
date_created=datetime.date.today())
|
||||||
"date_submitted": "0000-00-00T00:00:00.000Z",
|
report.save()
|
||||||
"sections": [
|
|
||||||
{
|
# Create the sections
|
||||||
"id": 1,
|
for i in range(len(pol.sections)):
|
||||||
"completed": True,
|
section = pol.sections[i]
|
||||||
"title": "Flight Info",
|
s = Section.objects.create(report_id=report, auto_submit=section.auto_submit,
|
||||||
"html_description": "<p>Enter flight details here.</p>",
|
required=section.required, completed=False,
|
||||||
"fields": {
|
title=section.title, html_description=section.html_description,
|
||||||
"international": {
|
number=i)
|
||||||
"label": "International flight",
|
s.save()
|
||||||
"type": "boolean",
|
|
||||||
"value": True
|
# Create the fields
|
||||||
},
|
j = 0
|
||||||
"travel_date": {
|
for key in section.fields:
|
||||||
"label": "Travel start date",
|
field = section.fields[key]
|
||||||
"type": "date",
|
f = Field.objects.create(section_id=s, field_name=key, label=field['label'],
|
||||||
"value": "2016-05-22T14:56:28.000Z"
|
number=j, type=field['type'], completed=False)
|
||||||
},
|
f.save()
|
||||||
"fare": {
|
j = j+1
|
||||||
"label": "Fare",
|
|
||||||
"type": "decimal",
|
# Return the newly created report
|
||||||
"value": "1024.99"
|
data = get_reports(report.id)
|
||||||
},
|
|
||||||
"lowest_fare_screenshot": {
|
|
||||||
"label": "Lowest fare screenshot",
|
|
||||||
"type": "file",
|
|
||||||
"value": "e92h842jiu49f8..."
|
|
||||||
},
|
|
||||||
"plane_ticket_invoice": {
|
|
||||||
"label": "Plane ticket invoice PDF",
|
|
||||||
"type": "file",
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rule_violations": [
|
|
||||||
{
|
|
||||||
"error_text": "Plane ticket invoice must be submitted."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"completed": False,
|
|
||||||
"title": "Hotel info",
|
|
||||||
"html_description": "<p>If you used a hotel, please enter the details.</p>",
|
|
||||||
"fields": {
|
|
||||||
"total": {
|
|
||||||
"label": "Total cost",
|
|
||||||
"type": "decimal"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rule_violations": [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
# View the list of reports
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
def reports(request):
|
def reports(request):
|
||||||
data = {
|
report_set = {"reports": []}
|
||||||
"reports": [
|
queryset = Report.objects.all().filter(user_id=request.user.id).order_by('date_created')
|
||||||
{
|
for i in queryset:
|
||||||
"report_pk": 1,
|
data = {
|
||||||
"title": "2018 Portland trip",
|
"user_id": request.user.id,
|
||||||
"date_created": "2018-05-22T14:56:28.000Z",
|
"report_pk": i.id,
|
||||||
"state": "created",
|
"title": i.title,
|
||||||
"date_submitted": "0000-00-00T00:00:00.000Z"
|
"date_created": i.date_created,
|
||||||
},
|
"submitted": i.submitted,
|
||||||
{
|
"date_submitted": i.date_submitted,
|
||||||
"report_pk": 2,
|
}
|
||||||
"title": "2017 Los Angeles trip",
|
# append the sections for each report
|
||||||
"date_created": "2017-05-22T14:56:28.000Z",
|
report_set["reports"].append(data.copy())
|
||||||
"state": "submitted",
|
|
||||||
"date_submitted": "2017-07-22T14:56:28.000Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"report_pk": 3,
|
|
||||||
"title": "2017 Denver trip",
|
|
||||||
"date_created": "2015-04-22T14:56:28.000Z",
|
|
||||||
"state": "accepted",
|
|
||||||
"date_submitted": "2015-06-22T14:56:28.000Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return JsonResponse(data)
|
|
||||||
|
|
||||||
|
return JsonResponse(report_set)
|
||||||
|
|
||||||
|
|
||||||
|
# actions for an individual report
|
||||||
@api_view(['GET', 'PUT', 'DELETE'])
|
@api_view(['GET', 'PUT', 'DELETE'])
|
||||||
def report_detail(request, report_pk):
|
def report_detail(request, report_pk):
|
||||||
|
# view the report
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
data = {
|
data = get_reports(report_pk)
|
||||||
"report_pk": report_pk,
|
|
||||||
"title": "2018 Portland trip",
|
|
||||||
"date_created": "2018-05-22T14:56:28.000Z",
|
|
||||||
"submitted": False,
|
|
||||||
"date_submitted": "0000-00-00T00:00:00.000Z",
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"completed": True,
|
|
||||||
"title": "Flight Info",
|
|
||||||
"html_description": "<p>Enter flight details here.</p>",
|
|
||||||
"fields": {
|
|
||||||
"international": {
|
|
||||||
"label": "International flight",
|
|
||||||
"type": "boolean",
|
|
||||||
"value": True
|
|
||||||
},
|
|
||||||
"travel_date": {
|
|
||||||
"label": "Travel start date",
|
|
||||||
"type": "date",
|
|
||||||
"value": "2016-05-22T14:56:28.000Z"
|
|
||||||
},
|
|
||||||
"fare": {
|
|
||||||
"label": "Fare",
|
|
||||||
"type": "decimal",
|
|
||||||
"value": "1024.99"
|
|
||||||
},
|
|
||||||
"lowest_fare_screenshot": {
|
|
||||||
"label": "Lowest fare screenshot",
|
|
||||||
"type": "file",
|
|
||||||
"value": "e92h842jiu49f8..."
|
|
||||||
},
|
|
||||||
"plane_ticket_invoice": {
|
|
||||||
"label": "Plane ticket invoice PDF",
|
|
||||||
"type": "file",
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rule_violations": [
|
|
||||||
{
|
|
||||||
"error_text": "Plane ticket invoice must be submitted."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"completed": False,
|
|
||||||
"title": "Hotel info",
|
|
||||||
"html_description": "<p>If you used a hotel, please enter the details.</p>",
|
|
||||||
"fields": {
|
|
||||||
"total": {
|
|
||||||
"label": "Total cost",
|
|
||||||
"type": "decimal"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rule_violations": [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
# submit the report
|
||||||
elif request.method == 'PUT':
|
elif request.method == 'PUT':
|
||||||
return JsonResponse({"message": "Report submitted."})
|
return JsonResponse({"message": "Report submitted."})
|
||||||
|
|
||||||
|
# Delete the report
|
||||||
elif request.method == 'DELETE':
|
elif request.method == 'DELETE':
|
||||||
return JsonResponse({"message": "Deleted report {0}.".format(report_pk)})
|
return JsonResponse({"message": "Deleted report {0}.".format(report_pk)})
|
||||||
|
|
||||||
|
|
||||||
|
# update a section with new data
|
||||||
@api_view(['PUT'])
|
@api_view(['PUT'])
|
||||||
def section(request, report_pk, section_pk):
|
def section(request, report_pk, section_pk):
|
||||||
'''
|
|
||||||
Update a section with new data.
|
|
||||||
'''
|
|
||||||
data = {
|
data = {
|
||||||
"message": "Updated report {0}, section {1}.".format(report_pk, section_pk),
|
"message": "Updated report {0}, section {1}.".format(report_pk, section_pk),
|
||||||
"fields": {
|
"fields": {
|
||||||
|
@ -198,25 +149,5 @@ def section(request, report_pk, section_pk):
|
||||||
"lowest_fare_screenshot": "image",
|
"lowest_fare_screenshot": "image",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
@api_view(['POST'])
|
|
||||||
def account(request):
|
|
||||||
'''
|
|
||||||
Create a new user account
|
|
||||||
'''
|
|
||||||
return JsonResponse({"message": "Account creation successful."})
|
|
||||||
|
|
||||||
@api_view(['POST'])
|
|
||||||
def account_login(request):
|
|
||||||
'''
|
|
||||||
Log in to a user account
|
|
||||||
'''
|
|
||||||
return JsonResponse({"message": "Successfully logged in."})
|
|
||||||
|
|
||||||
@api_view(['DELETE'])
|
|
||||||
def account_logout(request):
|
|
||||||
'''
|
|
||||||
Log out from a user account
|
|
||||||
'''
|
|
||||||
return JsonResponse({"message": "User logged out."})
|
|
||||||
|
|
BIN
back/db.sqlite3
BIN
back/db.sqlite3
Binary file not shown.
9
back/reimbursinator/custom_auth.py
Normal file
9
back/reimbursinator/custom_auth.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from rest_framework.authentication import TokenAuthentication
|
||||||
|
|
||||||
|
class BearerAuthentication(TokenAuthentication):
|
||||||
|
"""
|
||||||
|
This class simply changes the expected token keyword to 'Bearer'
|
||||||
|
from the Django rest authentication default 'Token'. This allows
|
||||||
|
applications like Postman to work with token authentication.
|
||||||
|
"""
|
||||||
|
keyword = "Bearer"
|
|
@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
#from reimbursinator.custom_auth import BearerAuthentication
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
@ -38,8 +39,15 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.sites',
|
||||||
# 3rd party
|
# 3rd party
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
'rest_framework.authtoken',
|
||||||
|
'allauth',
|
||||||
|
'allauth.account',
|
||||||
|
'allauth.socialaccount',
|
||||||
|
'rest_auth',
|
||||||
|
'rest_auth.registration',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
# local
|
# local
|
||||||
'users',
|
'users',
|
||||||
|
@ -48,8 +56,12 @@ INSTALLED_APPS = [
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_PERMISSION_CLASSES': [
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
'rest_framework.permissions.AllowAny',
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
]
|
],
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'reimbursinator.custom_auth.BearerAuthentication',
|
||||||
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -142,3 +154,25 @@ USE_TZ = True
|
||||||
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# Email Config
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
# Registration
|
||||||
|
|
||||||
|
#ACCOUNT_USER_MODEL_USERNAME_FIELD = 'email'
|
||||||
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
|
ACCOUNT_USERNAME_REQUIRED = False
|
||||||
|
ACCOUNT_AUTHENTICATION_METHOD = 'email'
|
||||||
|
|
||||||
|
REST_AUTH_REGISTER_SERIALIZERS = {
|
||||||
|
'REGISTER_SERIALIZER': 'users.serializers.RegisterSerializer',
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
'allauth.account.auth_backends.AuthenticationBackend',
|
||||||
|
)
|
||||||
|
|
|
@ -12,4 +12,8 @@ from django.urls import path, include
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('api/v1/', include("backend.urls")),
|
path('api/v1/', include("backend.urls")),
|
||||||
]
|
path('api/v1/account/', include('rest_auth.urls')),
|
||||||
|
path('api/v1/account/register/', include('rest_auth.registration.urls')),
|
||||||
|
# path('api/v1/account/register/', NameRegistrationView.as_view()),
|
||||||
|
path('api-auth/', include('rest_framework.urls')),
|
||||||
|
]
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 68 KiB |
BIN
back/uploads/2019/02/09/Supreme-logo-newyork-1920x1080.jpg
Normal file
BIN
back/uploads/2019/02/09/Supreme-logo-newyork-1920x1080.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
0
back/users/__init__.py
Normal file
0
back/users/__init__.py
Normal file
47
back/users/serializers.py
Normal file
47
back/users/serializers.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from allauth.account import app_settings as allauth_settings
|
||||||
|
from allauth.utils import email_address_exists
|
||||||
|
from allauth.account.adapter import get_adapter
|
||||||
|
from allauth.account.utils import setup_user_email
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
class RegisterSerializer(serializers.Serializer):
|
||||||
|
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
|
||||||
|
first_name = serializers.CharField(required=True, write_only=True)
|
||||||
|
last_name = serializers.CharField(required=True, write_only=True)
|
||||||
|
password1 = serializers.CharField(required=True, write_only=True)
|
||||||
|
password2 = serializers.CharField(required=True, write_only=True)
|
||||||
|
|
||||||
|
def validate_email(self, email):
|
||||||
|
email = get_adapter().clean_email(email)
|
||||||
|
if allauth_settings.UNIQUE_EMAIL:
|
||||||
|
if email and email_address_exists(email):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
_("A user is already registered with this e-mail address."))
|
||||||
|
return email
|
||||||
|
|
||||||
|
def validate_password1(self, password):
|
||||||
|
return get_adapter().clean_password(password)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
if data['password1'] != data['password2']:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
_("The two password fields didn't match."))
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_cleaned_data(self):
|
||||||
|
return {
|
||||||
|
'first_name': self.validated_data.get('first_name', ''),
|
||||||
|
'last_name': self.validated_data.get('last_name', ''),
|
||||||
|
'password1': self.validated_data.get('password1', ''),
|
||||||
|
'email': self.validated_data.get('email', ''),
|
||||||
|
}
|
||||||
|
|
||||||
|
def save(self, request):
|
||||||
|
adapter = get_adapter()
|
||||||
|
user = adapter.new_user(request)
|
||||||
|
self.cleaned_data = self.get_cleaned_data()
|
||||||
|
adapter.save_user(request, user, self)
|
||||||
|
setup_user_email(request, user, [])
|
||||||
|
user.save()
|
||||||
|
return user
|
|
@ -1,3 +1 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container pt-5">
|
<div class="container pt-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-8 mx-auto">
|
<div class="col-sm-8 mx-auto">
|
||||||
<div class="card bg-light text-dark">
|
<div class="card bg-light text-dark">
|
||||||
|
@ -81,6 +81,20 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal" id="viewReportModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="viewReportModalLabel"></h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-view">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script src="js/logout.js"></script>
|
<script src="js/logout.js"></script>
|
||||||
<script src="js/viewHistory.js"></script>
|
<script src="js/viewHistory.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container">
|
<div class="container pt-3">
|
||||||
<p>Welcome to Reimbursinator</p>
|
<p>Welcome to Reimbursinator</p>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/logout.js"></script>
|
<script src="js/logout.js"></script>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container">
|
<div class="container pt-3">
|
||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
<h1>Reimbursinator</h1>
|
<h1>Reimbursinator</h1>
|
||||||
<p class="lead">An open source expense management solution sponsored by the Software Freedom Conservancy</p>
|
<p class="lead">An open source expense management solution sponsored by the Software Freedom Conservancy</p>
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
function displayErrorMessage(errorMessage) {
|
|
||||||
const errorReport = document.querySelector("#errorReport");
|
|
||||||
errorReport.innerHTML = JSON.parse(errorMessage).error;
|
|
||||||
}
|
|
||||||
|
|
||||||
function postToLoginEndpoint(event) {
|
function postToLoginEndpoint(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const credentials = {
|
const credentials = {
|
||||||
"username" : this.elements.username.value,
|
"email" : this.elements.email.value,
|
||||||
"password" : this.elements.password.value
|
"password" : this.elements.password.value
|
||||||
}
|
}
|
||||||
const url = "https://reqres.in/api/login" // mock api service
|
const url = "https://" + window.location.hostname + ":8444/api/v1/account/login/";
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
console.log("Attempting a connection to the following endpoint: " + url);
|
||||||
console.log("User credentials:\n" + JSON.stringify(credentials));
|
console.log("User credentials:\n" + JSON.stringify(credentials));
|
||||||
|
|
||||||
xhr.open("POST", url, true);
|
xhr.open("POST", url, true);
|
||||||
|
@ -22,14 +18,14 @@ function postToLoginEndpoint(event) {
|
||||||
if (this.status === 200) {
|
if (this.status === 200) {
|
||||||
console.log("LOGIN SUCCESS!");
|
console.log("LOGIN SUCCESS!");
|
||||||
console.log("Server response:\n" + this.response);
|
console.log("Server response:\n" + this.response);
|
||||||
token = JSON.parse(this.response).token;
|
token = JSON.parse(this.response).key;
|
||||||
localStorage.setItem("token", token);
|
localStorage.setItem("token", token);
|
||||||
window.location.replace("home.html");
|
window.location.replace("home.html");
|
||||||
} else {
|
} else {
|
||||||
|
document.getElementById("errorLogin").innerHTML = "Incorrect user name or password";
|
||||||
console.error("LOGIN FAILURE!");
|
console.error("LOGIN FAILURE!");
|
||||||
console.error("Server status: " + this.status);
|
console.error("Server status: " + this.status);
|
||||||
console.error("Server response:\n" + this.response);
|
console.error("Server response:\n" + this.response);
|
||||||
displayErrorMessage(this.response);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,18 +2,18 @@ function postToLogoutEndpoint(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
const url = "https://reqres.in/api/logout" // mock api service
|
const url = "https://" + window.location.hostname + ":8444/api/v1/account/logout/";
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
xhr.open("POST", url, true);
|
xhr.open("POST", url, true);
|
||||||
xhr.setRequestHeader("Authorization", "Token " + token);
|
xhr.setRequestHeader("Authorization", "Bearer " + token);
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (this.readyState === 4) {
|
if (this.readyState === 4) {
|
||||||
if (this.status === 200) {
|
if (this.status === 200) {
|
||||||
console.log("LOGOUT SUCCESS!");
|
console.log("LOGOUT SUCCESS!");
|
||||||
console.log("Server response:\n" + this.response);
|
console.log("Server response:\n" + this.response);
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
window.location.replace("index.html");
|
window.location.replace("/");
|
||||||
} else {
|
} else {
|
||||||
console.error("LOGOUT FAILURE!");
|
console.error("LOGOUT FAILURE!");
|
||||||
console.error("Server status: " + this.status);
|
console.error("Server status: " + this.status);
|
||||||
|
|
|
@ -1,20 +1,64 @@
|
||||||
const password = document.getElementById("password");
|
const password1 = document.getElementById("password1");
|
||||||
const confirm_password = document.getElementById("confirmPassword");
|
const password2 = document.getElementById("password2");
|
||||||
function validatePassword(){
|
|
||||||
if(password.value != confirm_password.value) {
|
function validatePassword() {
|
||||||
confirm_password.setCustomValidity("Passwords Don't Match");
|
if (password1.value != password2.value) {
|
||||||
}
|
password2.setCustomValidity("Passwords don't match");
|
||||||
else {
|
} else {
|
||||||
confirm_password.setCustomValidity('');
|
password2.setCustomValidity('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
password.onchange = validatePassword;
|
password1.onchange = validatePassword;
|
||||||
confirm_password.onkeyup = validatePassword;
|
password2.onkeyup = validatePassword;
|
||||||
|
|
||||||
function validateEmail(email)
|
function validateEmail(email) {
|
||||||
{
|
if (email.validity.patternMismatch) {
|
||||||
if(email.validity.patternMismatch)
|
|
||||||
email.setCustomValidity('Please input correct email');
|
email.setCustomValidity('Please input correct email');
|
||||||
else
|
} else {
|
||||||
email.setCustomValidity('');
|
email.setCustomValidity('');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function postToRegistrationEndpoint(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const credentials = {
|
||||||
|
"email" : this.elements.email.value,
|
||||||
|
"first_name" : this.elements.first_name.value,
|
||||||
|
"last_name" : this.elements.last_name.value,
|
||||||
|
"password1" : this.elements.password1.value,
|
||||||
|
"password2" : this.elements.password2.value
|
||||||
|
}
|
||||||
|
const url = "https://" + window.location.hostname + ":8444/api/v1/account/register/";
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
console.log("Attempting a connection to the following endpoint: " + url);
|
||||||
|
console.log("User credentials:\n" + JSON.stringify(credentials));
|
||||||
|
|
||||||
|
xhr.open("POST", url, true);
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/json");
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (this.readyState === 4) {
|
||||||
|
if (this.status === 201) {
|
||||||
|
console.log("REGISTRATION SUCCESS!");
|
||||||
|
console.log("Server response:\n" + this.response);
|
||||||
|
token = JSON.parse(this.response).key;
|
||||||
|
localStorage.setItem("token", token);
|
||||||
|
window.location.replace("home.html");
|
||||||
|
} else {
|
||||||
|
console.error("REGISTRATION FAILURE!");
|
||||||
|
console.error("Server status: " + this.status);
|
||||||
|
console.error("Server response:\n" + this.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function() {
|
||||||
|
alert("Error connecting to the authentication server!");
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(JSON.stringify(credentials));
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = document.querySelector("form");
|
||||||
|
form.addEventListener("submit", postToRegistrationEndpoint);
|
||||||
|
|
|
@ -11,6 +11,7 @@ function getDataFromEndpoint(url, callback, optional) {
|
||||||
console.log("Attempting a connection to the following endpoint: " + url);
|
console.log("Attempting a connection to the following endpoint: " + url);
|
||||||
|
|
||||||
xhr.open("GET", url, true);
|
xhr.open("GET", url, true);
|
||||||
|
xhr.setRequestHeader("Authorization", "Bearer " + token);
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (this.readyState === 4) {
|
if (this.readyState === 4) {
|
||||||
if (this.status === 200) {
|
if (this.status === 200) {
|
||||||
|
@ -33,22 +34,55 @@ function getDataFromEndpoint(url, callback, optional) {
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Make a POST request to url and pass response to callback function
|
||||||
|
function postDataToEndpoint(url, payload, callback, optional) {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
console.log("Attempting a connection to the following endpoint: " + url);
|
||||||
|
|
||||||
|
xhr.open("POST", url, true);
|
||||||
|
xhr.setRequestHeader("Authorization", "Bearer " + token);
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/json");
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (this.readyState === 4) {
|
||||||
|
if (this.status === 200) {
|
||||||
|
console.log("POST SUCCESS!");
|
||||||
|
console.log("Server response:\n" + this.response);
|
||||||
|
let parsedData = JSON.parse(this.response);
|
||||||
|
optional === undefined ? callback(parsedData) : callback(parsedData, optional);
|
||||||
|
} else {
|
||||||
|
console.error("POST FAILURE!");
|
||||||
|
console.error("Server status: " + this.status);
|
||||||
|
console.error("Server response:\n" + this.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function() {
|
||||||
|
alert("Connection error!");
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(payload);
|
||||||
|
}
|
||||||
|
|
||||||
// Wraps a Bootstrap form group around a field
|
// Wraps a Bootstrap form group around a field
|
||||||
function createFormGroup(key, field) {
|
function createFormGroup(field) {
|
||||||
const formGroup = document.createElement("div")
|
const formGroup = document.createElement("div")
|
||||||
formGroup.classList.add("form-group", "row");
|
formGroup.classList.add("form-group", "row");
|
||||||
|
|
||||||
const label = document.createElement("label");
|
const label = document.createElement("label");
|
||||||
label.classList.add("col-sm-4", "col-form");
|
label.classList.add("col-sm-4", "col-form");
|
||||||
label.innerHTML = field.label + ": ";
|
label.innerHTML = field.label + ": ";
|
||||||
label.setAttribute("for", key);
|
label.setAttribute("for", field.field_name);
|
||||||
|
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.classList.add("col-sm-6");
|
div.classList.add("col-sm-6");
|
||||||
|
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.name = key;
|
input.name = field.field_name;
|
||||||
input.id = key;
|
input.id = field.field_name;
|
||||||
|
|
||||||
switch(field.type) {
|
switch(field.type) {
|
||||||
case "boolean":
|
case "boolean":
|
||||||
|
@ -58,6 +92,7 @@ function createFormGroup(key, field) {
|
||||||
input.classList.add("form-check-input");
|
input.classList.add("form-check-input");
|
||||||
label.className = "";
|
label.className = "";
|
||||||
label.classList.add("form-check-label");
|
label.classList.add("form-check-label");
|
||||||
|
label.innerHTML = field.label;
|
||||||
outerLabel = document.createElement("div");
|
outerLabel = document.createElement("div");
|
||||||
outerLabel.classList.add("col-sm-4");
|
outerLabel.classList.add("col-sm-4");
|
||||||
outerLabel.innerHTML = "Flight type: ";
|
outerLabel.innerHTML = "Flight type: ";
|
||||||
|
@ -70,10 +105,29 @@ function createFormGroup(key, field) {
|
||||||
formGroup.appendChild(div);
|
formGroup.appendChild(div);
|
||||||
break;
|
break;
|
||||||
case "date":
|
case "date":
|
||||||
|
case "string":
|
||||||
|
input.type = "text";
|
||||||
|
input.value = field.value;
|
||||||
|
input.classList.add("form-control");
|
||||||
|
formGroup.appendChild(label);
|
||||||
|
div.appendChild(input)
|
||||||
|
formGroup.appendChild(div);
|
||||||
|
break;
|
||||||
case "decimal":
|
case "decimal":
|
||||||
input.type = "text";
|
input.type = "text";
|
||||||
input.value = field.value;
|
input.value = field.value;
|
||||||
input.classList.add("form-control");
|
input.classList.add("form-control");
|
||||||
|
input.pattern = "\\d+(\\.\\d{2})?";
|
||||||
|
formGroup.appendChild(label);
|
||||||
|
div.appendChild(input)
|
||||||
|
formGroup.appendChild(div);
|
||||||
|
break;
|
||||||
|
case "integer":
|
||||||
|
input.type = "number";
|
||||||
|
input.value = field.value;
|
||||||
|
input.classList.add("form-control");
|
||||||
|
input.step = 1;
|
||||||
|
input.min = 0;
|
||||||
formGroup.appendChild(label);
|
formGroup.appendChild(label);
|
||||||
div.appendChild(input)
|
div.appendChild(input)
|
||||||
formGroup.appendChild(div);
|
formGroup.appendChild(div);
|
||||||
|
@ -158,10 +212,12 @@ function createReportForm(parsedData, type) {
|
||||||
accordion.classList.add("accordion");
|
accordion.classList.add("accordion");
|
||||||
|
|
||||||
if (type === reportType.EDIT) {
|
if (type === reportType.EDIT) {
|
||||||
|
console.log("reportType.EDIT");
|
||||||
modalBody = document.querySelector("#editReportModalBody");
|
modalBody = document.querySelector("#editReportModalBody");
|
||||||
modalLabel = document.querySelector("#editReportModalLabel");
|
modalLabel = document.querySelector("#editReportModalLabel");
|
||||||
accordion.id = "editReportAccordion";
|
accordion.id = "editReportAccordion";
|
||||||
} else if (type === reportType.NEW) {
|
} else if (type === reportType.NEW) {
|
||||||
|
console.log("reportType.NEW");
|
||||||
modalBody = document.querySelector("#newReportModalBody");
|
modalBody = document.querySelector("#newReportModalBody");
|
||||||
modalLabel = document.querySelector("#newReportModalLabel");
|
modalLabel = document.querySelector("#newReportModalLabel");
|
||||||
accordion.id = "newReportAccordion";
|
accordion.id = "newReportAccordion";
|
||||||
|
@ -199,7 +255,7 @@ function createReportForm(parsedData, type) {
|
||||||
console.log("Field value: " + field.value);
|
console.log("Field value: " + field.value);
|
||||||
|
|
||||||
// Create a form group for each field and add it to the form
|
// Create a form group for each field and add it to the form
|
||||||
let formGroup = createFormGroup(key, field);
|
let formGroup = createFormGroup(field);
|
||||||
form.appendChild(formGroup);
|
form.appendChild(formGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +291,7 @@ function displayListOfReports(parsedData) {
|
||||||
for (let i = 0; i < reports.length; i++) {
|
for (let i = 0; i < reports.length; i++) {
|
||||||
let title = reports[i].title;
|
let title = reports[i].title;
|
||||||
let dateCreated = new Date(reports[i].date_created).toLocaleDateString("en-US");
|
let dateCreated = new Date(reports[i].date_created).toLocaleDateString("en-US");
|
||||||
let state = reports[i].state;
|
let state = reports[i].submitted;
|
||||||
let dateSubmitted;
|
let dateSubmitted;
|
||||||
let rid = reports[i].report_pk;
|
let rid = reports[i].report_pk;
|
||||||
|
|
||||||
|
@ -253,7 +309,7 @@ function displayListOfReports(parsedData) {
|
||||||
actionButton.setAttribute("data-rid", rid);
|
actionButton.setAttribute("data-rid", rid);
|
||||||
actionButton.classList.add("btn");
|
actionButton.classList.add("btn");
|
||||||
|
|
||||||
if (state === "created") {
|
if (state === false) {
|
||||||
// Edit button
|
// Edit button
|
||||||
dateSubmitted = "TBD";
|
dateSubmitted = "TBD";
|
||||||
actionButton.classList.add("btn-primary", "edit-report-button"); // Add event listener class
|
actionButton.classList.add("btn-primary", "edit-report-button"); // Add event listener class
|
||||||
|
@ -263,8 +319,10 @@ function displayListOfReports(parsedData) {
|
||||||
} else {
|
} else {
|
||||||
// View button
|
// View button
|
||||||
dateSubmitted = new Date(reports[i].date_submitted).toLocaleDateString("en-US");
|
dateSubmitted = new Date(reports[i].date_submitted).toLocaleDateString("en-US");
|
||||||
actionButton.classList.add("btn-success");
|
actionButton.classList.add("btn-success", "view-report-button");
|
||||||
actionButton.innerHTML = "View";
|
actionButton.innerHTML = "View";
|
||||||
|
actionButton.setAttribute("data-toggle", "modal");
|
||||||
|
actionButton.setAttribute("data-target", "#viewReportModal");
|
||||||
}
|
}
|
||||||
|
|
||||||
let dateSubmittedCell = bodyRow.insertCell(3);
|
let dateSubmittedCell = bodyRow.insertCell(3);
|
||||||
|
@ -277,6 +335,63 @@ function displayListOfReports(parsedData) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayReport(parsedData){
|
||||||
|
//Able to get the correct report ID now just needs to display the
|
||||||
|
//report as an modual
|
||||||
|
const modalBody = document.querySelector(".modal-view");
|
||||||
|
const modalLabel = document.querySelector("#viewReportModalLabel");
|
||||||
|
|
||||||
|
while (modalBody.firstChild) {
|
||||||
|
modalBody.removeChild(modalBody.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add report title and date
|
||||||
|
const reportTitle = parsedData.title;
|
||||||
|
const dateCreated = new Date(parsedData.date_created).toLocaleDateString("en-US");
|
||||||
|
modalLabel.innerHTML = reportTitle + " " + dateCreated;
|
||||||
|
|
||||||
|
const card = document.createElement("div");
|
||||||
|
card.classList.add("card");
|
||||||
|
|
||||||
|
const cardHeader = document.createElement("div");
|
||||||
|
cardHeader.classList.add("card-header");
|
||||||
|
|
||||||
|
const cardBody = document.createElement("div");
|
||||||
|
cardBody.classList.add("card-body");
|
||||||
|
|
||||||
|
/*
|
||||||
|
const displayTable = document.createElement("table");
|
||||||
|
displayTable.classList.add("table table-striped table-responsive-sm");
|
||||||
|
displayTable.style.visibility = "visible";
|
||||||
|
cardBody.appendChild(displayTable);
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
const sections = parsedData.sections;
|
||||||
|
for (let key in sections) {
|
||||||
|
let section = sections[key];
|
||||||
|
if(section.completed) {
|
||||||
|
const h4 = document.createElement("h4");
|
||||||
|
const value = document.createTextNode(section.title);
|
||||||
|
|
||||||
|
h4.appendChild(value);
|
||||||
|
cardBody.appendChild(h4);
|
||||||
|
let fields = section.fields;
|
||||||
|
for (let key in fields) {
|
||||||
|
let field = fields[key];
|
||||||
|
const p1 = document.createElement("p");
|
||||||
|
const p1Value = document.createTextNode(field.label + ": " + field.value);
|
||||||
|
p1.appendChild(p1Value);
|
||||||
|
cardBody.appendChild(p1);
|
||||||
|
}
|
||||||
|
cardHeader.appendChild(cardBody);
|
||||||
|
card.appendChild(cardHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modalBody.appendChild(card);
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function(event) {
|
document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
if (window.location.pathname === "/edit_report.html") {
|
if (window.location.pathname === "/edit_report.html") {
|
||||||
const url = getEndpointDomain() + "api/v1/reports";
|
const url = getEndpointDomain() + "api/v1/reports";
|
||||||
|
@ -285,9 +400,9 @@ document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const reportType = {
|
const reportType = {
|
||||||
EDIT : 1,
|
NEW : 1,
|
||||||
VIEW : 2,
|
EDIT : 2,
|
||||||
NEW : 3
|
VIEW : 3
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("click", function(event) {
|
document.addEventListener("click", function(event) {
|
||||||
|
@ -298,13 +413,21 @@ document.addEventListener("click", function(event) {
|
||||||
const url = getEndpointDomain() + "api/v1/report/" + event.target.dataset.rid;
|
const url = getEndpointDomain() + "api/v1/report/" + event.target.dataset.rid;
|
||||||
const type = reportType.EDIT;
|
const type = reportType.EDIT;
|
||||||
getDataFromEndpoint(url, createReportForm, type);
|
getDataFromEndpoint(url, createReportForm, type);
|
||||||
} else if (event.target.classList.contains("new-report-button")) {
|
} else if (event.target.classList.contains("view-report-button")) {
|
||||||
//const url = getEndpointDomain() + "api/v1/report";
|
console.log("View button clicked");
|
||||||
const type = reportType.NEW;
|
const url = getEndpointDomain() + "api/v1/report/" + event.target.dataset.rid;
|
||||||
//getDataFromEndpoint(url, createReportForm, type);
|
getDataFromEndpoint(url, displayReport);
|
||||||
createReportForm(newReport, type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// TODO: Add view report
|
|
||||||
|
document.addEventListener("submit", function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.target.classList.contains("new-report")) {
|
||||||
|
const url = getEndpointDomain() + "api/v1/report";
|
||||||
|
const payload = JSON.stringify({ "title": event.target.elements.title.value });
|
||||||
|
console.log("Payload:\n" + payload);
|
||||||
|
const type = reportType.NEW;
|
||||||
|
postDataToEndpoint(url, payload, createReportForm, type);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||||
<div class="navbar-brand">Reimbursinator</div>
|
<div class="navbar-brand">Reimbursinator</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container pt-5">
|
<div class="container pt-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 mx-auto">
|
<div class="col-sm-6 mx-auto">
|
||||||
<div class="card bg-light text-dark">
|
<div class="card bg-light text-dark">
|
||||||
|
@ -24,13 +24,14 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form class="form" autocomplete="off">
|
<form class="form" autocomplete="off">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="formGroupUsername">Username:</label>
|
<label for="email">Email:</label>
|
||||||
<input class="form-control" id="formGroupUsername" type="text" name="username" required autofocus>
|
<input class="form-control" id="email" type="email" name="email" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="formGroupPassword">Password:</label>
|
<label for="password">Password:</label>
|
||||||
<input class="form-control" id="formGroupPassword" type="password" name="password" required>
|
<input class="form-control" id="password" type="password" name="password" required>
|
||||||
</div>
|
</div>
|
||||||
|
<p id="errorLogin" style="color:red"></p>
|
||||||
<button type="submit" class="btn btn-primary pull-right">Submit</button>
|
<button type="submit" class="btn btn-primary pull-right">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,7 +42,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p id="errorReport"><p>
|
|
||||||
<script src="js/login.js"></script>
|
<script src="js/login.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -36,8 +36,25 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container">
|
<div class="container pt-3">
|
||||||
<button type="button" class="btn btn-primary new-report-button" data-toggle="modal" data-target="#newReportModal">Create New</button>
|
<div class="row">
|
||||||
|
<div class="col-sm-6 mx-auto">
|
||||||
|
<div class="card bg-light text-dark">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Create a new report</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="form new-report" autocomplete="off">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="title">Report title:</label>
|
||||||
|
<input type="text" class="form-control" name="title" id="title">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" data-toggle="modal" data-target="#newReportModal">Create</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade" id="newReportModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="newReportModal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
@ -51,7 +68,6 @@
|
||||||
<div class="modal-body" id="newReportModalBody">
|
<div class="modal-body" id="newReportModalBody">
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-danger">Delete Report</button>
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
<button type="button" class="btn btn-primary">Submit Report</button>
|
<button type="button" class="btn btn-primary">Submit Report</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,7 +75,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/logout.js"></script>
|
<script src="js/logout.js"></script>
|
||||||
<script src="js/newReport.js"></script>
|
|
||||||
<script src="js/viewHistory.js"></script>
|
<script src="js/viewHistory.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||||
<div class="navbar-brand">Reimbursinator</div>
|
<div class="navbar-brand">Reimbursinator</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container pt-5">
|
<div class="container pt-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 mx-auto">
|
<div class="col-sm-6 mx-auto">
|
||||||
<div class="card bg-light text-dark">
|
<div class="card bg-light text-dark">
|
||||||
|
@ -23,21 +23,25 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form class="form signup" autocomplete="off" action="index.html">
|
<form class="form signup" autocomplete="off" action="index.html">
|
||||||
<div class="form-group">
|
|
||||||
<label for="userName">Username:</label>
|
|
||||||
<input class="form-control" id="userName" type="text" name="username" minlength="4" size="10" required autofocus>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Email:</label>
|
<label for="email">Email:</label>
|
||||||
<input class="form-control" type="email" id="email" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$" oninput="validateEmail(this);" required>
|
<input class="form-control" type="email" id="email" pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$" oninput="validateEmail(this);" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Password:</label>
|
<label for="first_name">First Name:</label>
|
||||||
<input class="form-control" type="password" id="password" minlength="4" size="10" required>
|
<input class="form-control" id="first_name" type="text" name="first_name" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="confirmPassword">Confirm Password:</label>
|
<label for="last_name">Last Name:</label>
|
||||||
<input class="form-control" type="password" id="confirmPassword" required>
|
<input class="form-control" id="last_name" type="text" name="last_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password1">Password:</label>
|
||||||
|
<input class="form-control" type="password" id="password1" name="password1" minlength="8" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password2">Confirm Password:</label>
|
||||||
|
<input class="form-control" type="password" id="password2" name="password2" minlength="8" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -51,5 +55,4 @@
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script src="js/signupPage.js"></script>
|
<script src="js/signupPage.js"></script>
|
||||||
<!--Still need to check if user exist and if email exist test-->
|
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue