Added docstring comments to all functions and classes. Renamed 'get_reports' to 'get_report'.

This commit is contained in:
kououken 2019-02-16 12:11:37 -08:00
parent c5b50715d3
commit d1bdab0701
5 changed files with 149 additions and 37 deletions

View file

@ -4,6 +4,10 @@ import datetime
import ntpath import ntpath
class Report(models.Model): class Report(models.Model):
"""
This model represents an expense report that can be
created, updated and submitted by a user.
"""
user_id = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) user_id = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=128) title = models.CharField(max_length=128)
date_created = models.DateTimeField('date created', default=datetime.date.today) date_created = models.DateTimeField('date created', default=datetime.date.today)
@ -11,9 +15,17 @@ class Report(models.Model):
submitted = models.BooleanField(default=False) submitted = models.BooleanField(default=False)
def __str__(self): def __str__(self):
"""
For debugging and display in admin view.
"""
return self.title return self.title
class Section(models.Model): class Section(models.Model):
"""
This model represents a logical division of a report,
containing its own fields and rules that apply to those
fields.
"""
report_id = models.ForeignKey(Report, on_delete=models.CASCADE) report_id = models.ForeignKey(Report, on_delete=models.CASCADE)
auto_submit = models.BooleanField(default=False) auto_submit = models.BooleanField(default=False)
required = models.BooleanField(default=False) required = models.BooleanField(default=False)
@ -23,9 +35,18 @@ class Section(models.Model):
number = models.IntegerField() number = models.IntegerField()
def __str__(self): def __str__(self):
"""
For debugging and display in admin view.
"""
return "{0}(#{1})".format(self.title, self.number) return "{0}(#{1})".format(self.title, self.number)
class Field(models.Model): class Field(models.Model):
"""
This model contains a piece of data entered by the user.
Depending on the type of the data ( boolean, decimal,
date, file, string or integer), different table columns
will be used to store the data.
"""
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") field_name = models.CharField(max_length=512, default="field")
label = models.CharField(max_length=512) label = models.CharField(max_length=512)
@ -39,9 +60,10 @@ 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):
"""
For debugging and display in the admin view.
"""
if self.field_type == "boolean": if self.field_type == "boolean":
if self.data_bool: if self.data_bool:
return "True" return "True"
@ -58,10 +80,11 @@ class Field(models.Model):
elif self.field_type == "integer": elif self.field_type == "integer":
return "{}".format(self.data_integer) return "{}".format(self.data_integer)
# function that gets corresponding
# data type
def get_datatype(self): def get_datatype(self):
"""
Returns the data corresponding to the type of the
field.
"""
if self.field_type == "boolean": if self.field_type == "boolean":
if self.data_bool: if self.data_bool:
return True return True
@ -79,8 +102,9 @@ class Field(models.Model):
elif self.field_type == "integer": elif self.field_type == "integer":
return self.data_integer return self.data_integer
# function that accommodates if
# path has slash at end
def path_leaf(self, path): def path_leaf(self, path):
"""
Function accommodating path with slash at end.
"""
dir_path, name = ntpath.split(path) dir_path, name = ntpath.split(path)
return name or ntpath.basename(dir_path) return name or ntpath.basename(dir_path)

View file

@ -1,19 +1,42 @@
from datetime import date from datetime import date
#### Classes for policy, sections. #### Classes for policy, sections. Do not edit these.
#####################################################
class Policy(): class Policy():
"""
Represents the policy for the company/organization.
"""
def __init__(self): def __init__(self):
"""
Creates a new Policy object.
"""
self.sections = [] self.sections = []
def add_section(self, section): def add_section(self, section):
"""
Appends the specified section to the policy in order.
section -- Section object to append.
"""
self.sections.append(section) self.sections.append(section)
class Section(): class Section():
"""
Represents a logical division of te policy, containing
data fields for users to enter, and rules which
apply to those fields.
"""
def __init__(self, title="Section", html_description="", required=False, auto_submit=False, fields={}):
"""
Creates a new Section object.
def __init__(self, title="Section", html_description="", required=False, title -- This is the name for the section the user sees.
auto_submit=False, fields={}): html_description -- This is html displayed beneath the title.
required -- If True, user must complete before submitting.
auto_submit -- Completing this section notifies the administrator.
fields -- A python object of fields the user should enter.
"""
self.title = title self.title = title
self.html_description = html_description self.html_description = html_description
self.required = required self.required = required
@ -22,6 +45,13 @@ class Section():
self.rules = [] self.rules = []
def add_rule(self, title="Rule", rule=None, rule_break_text=""): def add_rule(self, title="Rule", rule=None, rule_break_text=""):
"""
Assigns a new rule to the section.
title -- Administrative title for the rule.
rule -- lambda expression which must evaluate true to pass the rule.
rule_break_text -- Error message to show the user when rule is broken.
"""
rule = { rule = {
"title": title, "title": title,
"rule": rule, "rule": rule,
@ -29,10 +59,11 @@ class Section():
} }
self.rules.append(rule) self.rules.append(rule)
#### Policy configuration begin here
pol = Policy() pol = Policy()
#### Policy configuration begins here. Edit below this line.
############################################################
#### General #### General
#### Section 0 #### Section 0
general_section = Section( general_section = Section(

View file

@ -6,9 +6,12 @@ import os
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from rest_framework.parsers import FileUploadParser, MultiPartParser from rest_framework.parsers import FileUploadParser, MultiPartParser
def get_report(report_pk):
"""
Returns a python object representation of the specified section.
# function that prints all the reports report_pk -- ID of the report to compile.
def get_reports(report_pk): """
queryset = Report.objects.filter(id=report_pk) queryset = Report.objects.filter(id=report_pk)
for i in queryset: for i in queryset:
data = { data = {
@ -24,9 +27,12 @@ def get_reports(report_pk):
# return JsonResponse(data) # return JsonResponse(data)
return data return data
# function that gets all the sections
# takes report_id param
def get_sections(r_id): def get_sections(r_id):
"""
Returns a python object array of sections for the specified report.
r_id -- ID of the report to compile sections for.
"""
# create a dict of arrays for section # create a dict of arrays for section
section_set = {"sections": []} section_set = {"sections": []}
queryset = Section.objects.filter(report_id=r_id) queryset = Section.objects.filter(report_id=r_id)
@ -60,9 +66,12 @@ def get_sections(r_id):
return section_set return section_set
# function that gets all the fields
# takes section_id param
def get_fields(s_id): def get_fields(s_id):
"""
Returns a python object array of fields for the specified section.
s_id -- ID of the section to compile fields for.
"""
# create dict of arrays for fields # create dict of arrays for fields
field_set = {"fields": []} field_set = {"fields": []}
queryset = Field.objects.filter(section_id=s_id).order_by('number') queryset = Field.objects.filter(section_id=s_id).order_by('number')
@ -87,6 +96,12 @@ def get_fields(s_id):
def generate_named_fields_for_section(fields): def generate_named_fields_for_section(fields):
"""
Prepares a dictionary of key-value pairs based on the raw fields
passed in. Used to pass into the rule lambda functions.
fields -- Python object prepared by get_fields
"""
result = {} result = {}
for field in fields: for field in fields:
key = field['field_name'] key = field['field_name']
@ -94,13 +109,16 @@ def generate_named_fields_for_section(fields):
result[key] = value result[key] = value
return result return result
# API Endpoints
@api_view(['POST']) @api_view(['POST'])
def report(request): def report(request):
''' """
Generate a new empty report and return it Generates a new empty report for the current user and returns it
''' in json format. The title of the report should be provided as
follows:
{
"title": "Report Title Here"
}
"""
# Create the report # Create the report
report = Report.objects.create(user_id=request.user, title=request.data['title'], report = Report.objects.create(user_id=request.user, title=request.data['title'],
date_created=datetime.date.today()) date_created=datetime.date.today())
@ -123,12 +141,15 @@ def report(request):
f.save() f.save()
# Return the newly created report # Return the newly created report
data = get_reports(report.id) data = get_report(report.id)
return JsonResponse(data) return JsonResponse(data)
# View the list of reports
@api_view(['GET']) @api_view(['GET'])
def reports(request): def reports(request):
"""
Returns a condensed version of the current user's reports in json
format.
"""
report_set = {"reports": []} report_set = {"reports": []}
queryset = Report.objects.all().filter(user_id=request.user.id).order_by('date_created') queryset = Report.objects.all().filter(user_id=request.user.id).order_by('date_created')
for i in queryset: for i in queryset:
@ -145,20 +166,26 @@ def reports(request):
return JsonResponse(report_set) 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 """
Handler for individual report actions. Actions are divided into
GET, PUT, and DELETE requests.
report_pk -- ID of the report to carry out the action on.
"""
# GET: Retrieves a json representation of the specified report
if request.method == 'GET': if request.method == 'GET':
data = get_reports(report_pk) data = get_report(report_pk)
return JsonResponse(data) return JsonResponse(data)
# submit the report # PUT: Submits a report to the administrator for review,
# and marks it as "submitted", after which changes may
# not be made.
elif request.method == 'PUT': elif request.method == 'PUT':
return JsonResponse({"message": "Report submitted."}) return JsonResponse({"message": "Report submitted."})
# Delete the report # DELETE: Deletes a report from the user's account.
elif request.method == 'DELETE': elif request.method == 'DELETE':
# get corresponding sections # get corresponding sections
section_set = Section.objects.filter(report_id=report_pk) section_set = Section.objects.filter(report_id=report_pk)
@ -176,11 +203,13 @@ def report_detail(request, report_pk):
r.delete() r.delete()
return JsonResponse({"message": "Deleted report: {0}.".format(title)}) return JsonResponse({"message": "Deleted report: {0}.".format(title)})
# 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):
"""
Updates the specified section with new data.
section_pk -- Section for which the data should be updated.
"""
for key in request.data: for key in request.data:
# get the matching field object # get the matching field object
update = Field.objects.get(section_id=section_pk, field_name=key) update = Field.objects.get(section_id=section_pk, field_name=key)
@ -260,12 +289,33 @@ def section(request, report_pk, section_pk):
"completed": s.completed, "completed": s.completed,
"title": s.title, "title": s.title,
"html_description": s.html_description, "html_description": s.html_description,
"rule_violations": [],
} }
data.update(get_fields(s.id)) data.update(get_fields(s.id))
# process rules from the policy file if the section is completed
if s.completed:
rules = pol.sections[s.number].rules
for rule in rules:
try:
named_fields = generate_named_fields_for_section(data['fields'])
if not rule['rule'](data, named_fields):
info = {
"label": rule['title'],
"rule_break_text": rule['rule_break_text'],
}
data['rule_violations'].append(info)
except Exception as e:
print('Rule "{}" encountered an error. {}'.format(rule['title'], e))
return JsonResponse(data) return JsonResponse(data)
# function checks if a field is complete
def section_complete(section_pk): def section_complete(section_pk):
"""
Returns True if any fields of the specified section have been
entered by the user. This means that entering even one field
will count the entire section as "complete".
section_pk -- ID of the section whose fields you wish to check.
"""
# grab field set # grab field set
check_fields = Field.objects.filter(section_id=section_pk) check_fields = Field.objects.filter(section_id=section_pk)

View file

@ -2,4 +2,7 @@ from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
class CustomUser(AbstractUser): class CustomUser(AbstractUser):
"""
Custom user model to allow for new fields if necessary.
"""
age = models.PositiveIntegerField(null=True, blank=True) age = models.PositiveIntegerField(null=True, blank=True)

View file

@ -6,6 +6,10 @@ from allauth.account.utils import setup_user_email
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
class RegisterSerializer(serializers.Serializer): class RegisterSerializer(serializers.Serializer):
"""
Custom serializer to allow users to register with only
their email address, first and last name, and password.
"""
email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED) email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED)
first_name = serializers.CharField(required=True, write_only=True) first_name = serializers.CharField(required=True, write_only=True)
last_name = serializers.CharField(required=True, write_only=True) last_name = serializers.CharField(required=True, write_only=True)