diff --git a/back/backend/migrations/0003_auto_20190125_1425.py b/back/backend/migrations/0003_auto_20190125_1425.py new file mode 100644 index 0000000..5863d20 --- /dev/null +++ b/back/backend/migrations/0003_auto_20190125_1425.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.5 on 2019-01-25 22:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('backend', '0002_auto_20190123_0038'), + ] + + operations = [ + migrations.AlterField( + model_name='datafile', + name='data', + field=models.FileField(blank=True, null=True, upload_to='uploads/%Y/%m/%d/'), + ), + ] diff --git a/back/backend/migrations/0004_auto_20190125_1434.py b/back/backend/migrations/0004_auto_20190125_1434.py new file mode 100644 index 0000000..9976389 --- /dev/null +++ b/back/backend/migrations/0004_auto_20190125_1434.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.5 on 2019-01-25 22:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('backend', '0003_auto_20190125_1425'), + ] + + operations = [ + migrations.AlterField( + model_name='datafile', + name='data', + field=models.FileField(blank=True, max_length=512, null=True, upload_to='uploads/%Y/%m/%d/'), + ), + ] diff --git a/back/backend/models.py b/back/backend/models.py index afff981..0dff4f6 100644 --- a/back/backend/models.py +++ b/back/backend/models.py @@ -8,6 +8,9 @@ class Report(models.Model): date_submitted = models.DateTimeField('date submitted', null=True, blank=True) submitted = models.BooleanField(default=False) + def __str__(self): + return self.title + class Section(models.Model): report_id = models.ForeignKey(Report, on_delete=models.CASCADE) completed = models.BooleanField() @@ -15,6 +18,9 @@ class Section(models.Model): html_description = models.TextField() number = models.IntegerField() + def __str__(self): + return "{0}(#{1})".format(self.title, self.number) + class Field(models.Model): section_id = models.ForeignKey(Section, on_delete=models.CASCADE) label = models.CharField(max_length=256) @@ -22,26 +28,50 @@ class Field(models.Model): type = models.CharField(max_length=128) completed = models.BooleanField(default=False) + def __str__(self): + return "{0}(#{1})".format(self.label, self.number) + class DataBool(models.Model): field_id = models.ForeignKey(Field, on_delete=models.CASCADE) data = models.BooleanField(default=False) + def __str__(self): + if self.data: + return "True" + else: + return "False" + class DataDecimal(models.Model): field_id = models.ForeignKey(Field, on_delete=models.CASCADE) data = models.DecimalField(max_digits=9,decimal_places=2, null=True, blank=True) + def __str__(self): + return "{0}".format(self.data) + class DataDate(models.Model): field_id = models.ForeignKey(Field, on_delete=models.CASCADE) data = models.DateField(null=True, blank=True) + def __str__(self): + return "{0}".format(self.data) + class DataFile(models.Model): field_id = models.ForeignKey(Field, on_delete=models.CASCADE) - data = models.FileField(null=True, blank=True) + data = models.FileField(upload_to='uploads/%Y/%m/%d/', max_length=512, null=True, blank=True) + def __str__(self): + return "{0}".format(self.data) + class DataString(models.Model): field_id = models.ForeignKey(Field, on_delete=models.CASCADE) data = models.TextField(default='') + def __str__(self): + return "{0}".format(self.data) + class DataInteger(models.Model): field_id = models.ForeignKey(Field, on_delete=models.CASCADE) data = models.IntegerField(null=True, blank=True) + + def __str__(self): + return "{0}".format(self.data) diff --git a/back/backend/urls.py b/back/backend/urls.py index 4697112..8e0cada 100644 --- a/back/backend/urls.py +++ b/back/backend/urls.py @@ -1,23 +1,20 @@ -# Rupika Dikkala -# January 19, 2019 -# Add urls and link to the -# views +# Link report and account functionality to views from django.urls import path +from rest_framework.urlpatterns import format_suffix_patterns from . import views urlpatterns = [ - path('', views.List.as_view()), - path('/', views.Detail.as_view()), + #path('', views.List.as_view()), + #path('/', views.Detail.as_view()), - path('create_report/', views.create_report), - path('delete_report/', views.delete_report), - path('get_report/', views.get_report), - path('list_report/', views.list_report), - path('update_report/', views.update_report), - path('submit_report/', views.submit_report), - path('update_section/', views.update_section), - path('create_account/', views.create_account), - path('login/', views.login), - path('logout/', views.logout), + path('report', views.report), + path('reports', views.reports), + path('report/', views.report_detail), + path('report//section/', views.section), + path('account', views.account), + path('account/login', views.account_login), + path('account/logout', views.account_logout), ] + +urlpatterns = format_suffix_patterns(urlpatterns) \ No newline at end of file diff --git a/back/backend/views.py b/back/backend/views.py index abc3083..1f99059 100644 --- a/back/backend/views.py +++ b/back/backend/views.py @@ -1,17 +1,13 @@ -# Rupika Dikkala -# January 19, 2019 -# Creating views for URL that -# returns JSON data - +from rest_framework import generics +from rest_framework import status +from rest_framework.decorators import api_view from django.shortcuts import render from django.http import JsonResponse from .models import * from .serializers import * -from rest_framework import generics +# Sample view using generics - -# create sample view class List(generics.ListCreateAPIView): queryset = Report.objects.all() serializer_class = ReportSerializer @@ -20,11 +16,13 @@ class Detail(generics.RetrieveUpdateDestroyAPIView): queryset = Report.objects.all() serializer_class = ReportSerializer +# API Endpoints -####################################### - -# Create Report -def create_report(request): +@api_view(['POST']) +def report(request): + ''' + Generate a new empty report and return it + ''' data = { "title": "2018 Portland trip", "date_created": "2018-05-22T14:56:28.000Z", @@ -87,94 +85,26 @@ def create_report(request): } return JsonResponse(data) -# Delete report -def delete_report(request): - data = { - 'name': 'Delete report', - } - return JsonResponse(data) - -# Get report -def get_report(request): - data = { - "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": "

Enter flight details here.

", - "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": "

If you used a hotel, please enter the details.

", - "fields": { - "total": { - "label": "Total cost", - "type": "decimal" - } - }, - "rule_violations": [ - ] - } - ] - } - return JsonResponse(data) - -# List Reports -def list_report(request): +@api_view(['GET']) +def reports(request): data = { "reports": [ { + "report_pk": 1, "title": "2018 Portland trip", "date_created": "2018-05-22T14:56:28.000Z", "state": "created", "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", @@ -184,24 +114,83 @@ def list_report(request): } return JsonResponse(data) -# Update Reports -def update_report(request): - data = { - 'name': 'update report', - 'state': 'SUBMITTED!', - } - return JsonResponse(data) +@api_view(['GET', 'PUT', 'DELETE']) +def report_detail(request, report_pk): + if request.method == 'GET': + data = { + "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": "

Enter flight details here.

", + "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": "

If you used a hotel, please enter the details.

", + "fields": { + "total": { + "label": "Total cost", + "type": "decimal" + } + }, + "rule_violations": [ + ] + } + ] + } + return JsonResponse(data) + elif request.method == 'PUT': + return JsonResponse({"message": "Report submitted."}) + elif request.method == 'DELETE': + return JsonResponse({"message": "Deleted report {0}.".format(report_pk)}) -# Submit Reports -def submit_report(request): - data = { - 'name': 'submit report', - } - return JsonResponse(data) - -# Update section -def update_section(request): +@api_view(['PUT']) +def section(request, report_pk, section_pk): + ''' + Update a section with new data. + ''' data = { + "message": "Updated report {0}, section {1}.".format(report_pk, section_pk), "fields": { "international": True, "travel_date": "2012-04-23T18:25:43.511Z", @@ -211,25 +200,23 @@ def update_section(request): } return JsonResponse(data) +@api_view(['POST']) +def account(request): + ''' + Create a new user account + ''' + return JsonResponse({"message": "Account creation successful."}) -# Create account -def create_account(request): - data = { - 'name': 'create account', - } - return JsonResponse(data) - -# Login -def login(request): - data = { - 'name': 'login', - } - return JsonResponse(data) - -# Logout -def logout(request): - data = { - 'name': 'logout', - } - return JsonResponse(data) +@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."}) diff --git a/back/db.sqlite3 b/back/db.sqlite3 index 40ac467..d7782b7 100644 Binary files a/back/db.sqlite3 and b/back/db.sqlite3 differ diff --git a/back/reimbursinator/policies/hasher.py b/back/reimbursinator/policies/hasher.py index 4500b23..c831986 100644 --- a/back/reimbursinator/policies/hasher.py +++ b/back/reimbursinator/policies/hasher.py @@ -2,26 +2,26 @@ import hashlib hasher = hashlib.md5() with open ('simple_policy.py', 'rb') as afile: - buf = afile.read() - hasher.update(buf) + buf = afile.read() + hasher.update(buf) print("md5 of simple: " + hasher.hexdigest()) hasher = hashlib.md5() with open ('moderate_policy.py', 'rb') as afile: - buf = afile.read() - hasher.update(buf) + buf = afile.read() + hasher.update(buf) print("md5 of moderate: " + hasher.hexdigest()) hasher = hashlib.sha1() with open ('simple_policy.py', 'rb') as afile: - buf = afile.read() - hasher.update(buf) + buf = afile.read() + hasher.update(buf) print("sha1 of simple: " + hasher.hexdigest()) hasher = hashlib.sha1() with open ('moderate_policy.py', 'rb') as afile: - buf = afile.read() - hasher.update(buf) + buf = afile.read() + hasher.update(buf) print("sha1 of moderate: " + hasher.hexdigest()) diff --git a/back/reimbursinator/policies/simple_policy.py b/back/reimbursinator/policies/simple_policy.py index 5092d8f..16f1554 100644 --- a/back/reimbursinator/policies/simple_policy.py +++ b/back/reimbursinator/policies/simple_policy.py @@ -9,22 +9,22 @@ from datetime import date #### General #### Section 0 general_section = Section( - title = "General Info", - html_description = "", - fields = { - "destination": {"label": "Destination City", "type": "string"} - } + title = "General Info", + html_description = "", + fields = { + "destination": {"label": "Destination City", "type": "string"} + } ) general_section.add_rule( - title = "Destination city check", - rule = lambda report, section: - if section.fields.destination == "Timbuktu": - return True - else: - return False - , - rule_break_text = "What did the cowboy say about Tim, his wild horse?" + title = "Destination city check", + rule = lambda report, section: + if section.fields.destination == "Timbuktu": + return True + else: + return False + , + rule_break_text = "What did the cowboy say about Tim, his wild horse?" ) @@ -32,22 +32,22 @@ general_section.add_rule( #### Flight #### Section 1 flight_section = Section( - title = "Flight Info", - html_description = "

Enter flight details here.

", - fields = { - "international": {"label": "Is this an international flight?", "type": "boolean"}, - "departure_date": {"label": "Departure date", "type": "date"}, - "return_date": {"label": "Return date", "type": "date"}, - "fare": {"label": "Fare", "type": "decimal"}, - } + title = "Flight Info", + html_description = "

Enter flight details here.

", + fields = { + "international": {"label": "Is this an international flight?", "type": "boolean"}, + "departure_date": {"label": "Departure date", "type": "date"}, + "return_date": {"label": "Return date", "type": "date"}, + "fare": {"label": "Fare", "type": "decimal"}, + } ) flight_section.add_rule( - title = "Airline fare pre-approval check", - rule = lambda report, section: - return section.fields.fare < 500 - , - rule_break_text = "Fares cannot be more than $500" + title = "Airline fare pre-approval check", + rule = lambda report, section: + return section.fields.fare < 500 + , + rule_break_text = "Fares cannot be more than $500" ) @@ -55,25 +55,25 @@ flight_section.add_rule( #### Lodging #### Section 2 lodging_section = Section( - title = "Hotel Info", - html_description = "

Enter hotel info here.\nPer diem rates can be found at

", - fields = { - "check-in_date": {"label": "Check-in date", "type": "date"}, - "check-out_date": {"label": "Check-out date", "type": "date"}, - "rate": {"label": "Per diem nightly rate", "type": "decimal"}, - "cost": {"label": "Total Cost", "type": "decimal"} - } + title = "Hotel Info", + html_description = "

Enter hotel info here.\nPer diem rates can be found at

", + fields = { + "check-in_date": {"label": "Check-in date", "type": "date"}, + "check-out_date": {"label": "Check-out date", "type": "date"}, + "rate": {"label": "Per diem nightly rate", "type": "decimal"}, + "cost": {"label": "Total Cost", "type": "decimal"} + } ) section.add_rule( - title = "", - rule = lambda report, section: - check-in_date = date(section.fields.check-in_date) - check-out_date = date(section.fields.check-out_date) - duration = check-out_date - check-in_date - return section.fields.cost <= duration * section.fields.rate - , - rule_break_text = "The average nightly rate cannot be more than the USGSA rate." + title = "", + rule = lambda report, section: + check-in_date = date(section.fields.check-in_date) + check-out_date = date(section.fields.check-out_date) + duration = check-out_date - check-in_date + return section.fields.cost <= duration * section.fields.rate + , + rule_break_text = "The average nightly rate cannot be more than the USGSA rate." ) @@ -82,20 +82,20 @@ section.add_rule( #### Local Transportation #### Section 3 transport_section = Section( - title = "Local Transportation", - html_description = "

How much did you spend on local transportation, in total?

", - fields = { - "duration": {"label": "How many days was your trip?", "type": "decimal"}, - "cost": {"label": "Total cost", "type": "decimal"} - } + title = "Local Transportation", + html_description = "

How much did you spend on local transportation, in total?

", + fields = { + "duration": {"label": "How many days was your trip?", "type": "decimal"}, + "cost": {"label": "Total cost", "type": "decimal"} + } ) transport_section.add_rule( - title = "Total cost check", - rule = lambda report, section: - return section.fields.cost <= section.fields.duration * 10 - , - rule_break_text = "Local transportation costs must be less than $10 per day, on average." + title = "Total cost check", + rule = lambda report, section: + return section.fields.cost <= section.fields.duration * 10 + , + rule_break_text = "Local transportation costs must be less than $10 per day, on average." ) @@ -104,38 +104,38 @@ transport_section.add_rule( #### Per Diem #### Section 4 per_diem_section = Section( - title = "Per Diem", - html_description = "

Enter info about meals and incidentals here.\nPer diem rates can be found at

", - fields = { - "duration": {"label": "How many days was your trip?", "type": "decimal"}, - "rate": {"label": "What is the per diem rate for your destination?", "type": "decimal"}, - "cost": {"label": "Total Cost for meals and incidentals", "type": "decimal"} - } + title = "Per Diem", + html_description = "

Enter info about meals and incidentals here.\nPer diem rates can be found at

", + fields = { + "duration": {"label": "How many days was your trip?", "type": "decimal"}, + "rate": {"label": "What is the per diem rate for your destination?", "type": "decimal"}, + "cost": {"label": "Total Cost for meals and incidentals", "type": "decimal"} + } ) per_diem_section.add_rule( - title = "Per Diem Cost Check", - rule = lambda report, section: - return section.fields.cost <= section.fields.duration * section.fields.rate - , - rule_break_text = "The average cost per day for per diem expenses cannot be more than the rate specified by the USGSA." + title = "Per Diem Cost Check", + rule = lambda report, section: + return section.fields.cost <= section.fields.duration * section.fields.rate + , + rule_break_text = "The average cost per day for per diem expenses cannot be more than the rate specified by the USGSA." ) ''' Section( - title = "", - html_description = "

", - fields = { - "": {"label": "", "type": ""} - } + title = "", + html_description = "

", + fields = { + "": {"label": "", "type": ""} + } ) section.add_rule( - title = "", - rule = lambda report, section: return boolean_statement, - rule_break_text = "" + title = "", + rule = lambda report, section: return boolean_statement, + rule_break_text = "" ) #// or, for a rule which doesn’t apply to a specific section... diff --git a/back/reimbursinator/urls.py b/back/reimbursinator/urls.py index 2d01ca6..53beb58 100644 --- a/back/reimbursinator/urls.py +++ b/back/reimbursinator/urls.py @@ -1,17 +1,5 @@ -"""reimbursinator URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/2.1/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +reimbursinator URL Configuration """ # Rupika Dikkala @@ -23,5 +11,5 @@ from django.urls import path, include # add urls to this array urlpatterns = [ path('admin/', admin.site.urls), - path('backend/', include("backend.urls")), -] + path('api/v1/', include("backend.urls")), +] \ No newline at end of file diff --git a/back/uploads/2019/01/25/CUqADRaW4AAb1QI.jpg_large.jpg b/back/uploads/2019/01/25/CUqADRaW4AAb1QI.jpg_large.jpg new file mode 100644 index 0000000..151c5b5 Binary files /dev/null and b/back/uploads/2019/01/25/CUqADRaW4AAb1QI.jpg_large.jpg differ diff --git a/back/uploads/2019/01/25/CUqADRaW4AAb1QI.jpg_large_hKfT3Wx.jpg b/back/uploads/2019/01/25/CUqADRaW4AAb1QI.jpg_large_hKfT3Wx.jpg new file mode 100644 index 0000000..151c5b5 Binary files /dev/null and b/back/uploads/2019/01/25/CUqADRaW4AAb1QI.jpg_large_hKfT3Wx.jpg differ