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,6 +27,7 @@ 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) | ||||||
|  | @ -37,6 +39,8 @@ class Field(models.Model): | ||||||
|     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): | ||||||
|  |     report_set = {"reports": []} | ||||||
|  |     queryset = Report.objects.all().filter(user_id=request.user.id).order_by('date_created') | ||||||
|  |     for i in queryset: | ||||||
|         data = { |         data = { | ||||||
|         "reports": [ |             "user_id": request.user.id, | ||||||
|             { |             "report_pk": i.id, | ||||||
|                 "report_pk": 1, |             "title": i.title, | ||||||
|                 "title": "2018 Portland trip", |             "date_created": i.date_created, | ||||||
|                 "date_created": "2018-05-22T14:56:28.000Z", |             "submitted": i.submitted, | ||||||
|                 "state": "created", |             "date_submitted": i.date_submitted, | ||||||
|                 "date_submitted": "0000-00-00T00:00:00.000Z" |  | ||||||
|             }, |  | ||||||
|             { |  | ||||||
|                 "report_pk": 2, |  | ||||||
|                 "title": "2017 Los Angeles trip", |  | ||||||
|                 "date_created": "2017-05-22T14:56:28.000Z", |  | ||||||
|                 "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" |  | ||||||
|         } |         } | ||||||
|         ] |         # append the sections for each report | ||||||
|     } |         report_set["reports"].append(data.copy()) | ||||||
|     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) { |  | ||||||
|         confirm_password.setCustomValidity("Passwords Don't Match"); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         confirm_password.setCustomValidity(''); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| password.onchange = validatePassword; |  | ||||||
| confirm_password.onkeyup = validatePassword; |  | ||||||
| 
 | 
 | ||||||
| function validateEmail(email) | function validatePassword() { | ||||||
| { |     if (password1.value != password2.value) { | ||||||
|     if(email.validity.patternMismatch) |         password2.setCustomValidity("Passwords don't match"); | ||||||
|  |     } else { | ||||||
|  |         password2.setCustomValidity(''); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | password1.onchange = validatePassword; | ||||||
|  | password2.onkeyup = validatePassword; | ||||||
|  | 
 | ||||||
|  | function validateEmail(email) { | ||||||
|  |     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…
	
	Add table
		
		Reference in a new issue
	
	 Preston Doman
						Preston Doman