get app ready for heroku
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from models import Committee
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Committee)
|
||||
@@ -1,7 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CommitteeConfig(AppConfig):
|
||||
name = 'committee'
|
||||
@@ -1,11 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from django.db import models
|
||||
|
||||
class Committee(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
chair = models.ForeignKey(User, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -1,5 +0,0 @@
|
||||
from django.contrib import admin
|
||||
from models import Item
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(Item)
|
||||
@@ -1,7 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ItemsConfig(AppConfig):
|
||||
name = 'items'
|
||||
@@ -1,179 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.mail import send_mail
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
from stdimage.models import StdImageField
|
||||
from datetime import datetime, date
|
||||
|
||||
from committee.models import Committee
|
||||
|
||||
from django.db import models
|
||||
|
||||
class Item(models.Model):
|
||||
S3 = S3Boto3Storage()
|
||||
|
||||
NEW = 'N'
|
||||
PREAPPROVED = 'C'
|
||||
PROCESSED = 'P'
|
||||
REJECTED = 'R'
|
||||
|
||||
STATUS = (
|
||||
(NEW, 'New'),
|
||||
(PREAPPROVED, 'Committee Approved'),
|
||||
(PROCESSED, 'Processed'),
|
||||
(REJECTED, 'Rejected'),
|
||||
)
|
||||
|
||||
desc = models.CharField(max_length=200)
|
||||
event = models.CharField(max_length=200)
|
||||
committee = models.ForeignKey(Committee)
|
||||
details = models.TextField()
|
||||
cost = models.DecimalField(max_digits=7, decimal_places=2)
|
||||
date_purchased = models.DateField('date purchased')
|
||||
image = StdImageField(upload_to='images/%Y/%m/%d',
|
||||
variations={'thumbnail': (600, 600)}
|
||||
)
|
||||
|
||||
created_by = models.ForeignKey(User, related_name='created_by')
|
||||
approved_by = models.ManyToManyField(User, blank=True, related_name='approved_by')
|
||||
date_filed = models.DateTimeField('date filed')
|
||||
status = models.CharField(max_length=2, choices=STATUS)
|
||||
|
||||
def approved(self):
|
||||
return self.status == Item.PREAPPROVED
|
||||
|
||||
def processed(self):
|
||||
return self.status == Item.PROCESSED
|
||||
|
||||
def rejected(self):
|
||||
return self.status == Item.REJECTED
|
||||
|
||||
def new(self):
|
||||
return self.status == Item.NEW
|
||||
|
||||
def statusText(self):
|
||||
return dict(Item.STATUS)[self.status]
|
||||
|
||||
def comName(self):
|
||||
return self.committee.name
|
||||
|
||||
def __str__(self):
|
||||
return self.committee.name + ": " + self.event + " " + self.desc
|
||||
|
||||
def mail_com_chair(self):
|
||||
send_mail(
|
||||
'New Reimbursement - ' + self.event + ': ' + self.desc,
|
||||
'Hey ' + self.committee.chair.first_name + ',\n\n' + \
|
||||
'Please take a moment to review http://fincom.delt.space/items/' + \
|
||||
str(self.pk) + ' and approve or reject the request from ' + \
|
||||
self.created_by.first_name + ' ' + self.created_by.last_name + \
|
||||
'. If you have any questions, contact the treasurer.\n\n' + \
|
||||
'Have a fiscally responsible day,\n Fincom Bot',
|
||||
'fincom.bot@gmail.com',
|
||||
[self.committee.chair.email],
|
||||
html_message='Hey ' + self.committee.chair.first_name+',<br><br>' +\
|
||||
'Please take a moment to review ' + \
|
||||
'<a href="http://fincom.delt.space/items/' + \
|
||||
str(self.pk) + '">http://fincom.delt.space/items/' + str(self.pk) +\
|
||||
'</a> and approve or reject the request from ' + \
|
||||
self.created_by.first_name + ' ' + self.created_by.last_name + \
|
||||
'. If you have any questions, contact the treasurer.<br><br>' + \
|
||||
'Have a fiscally responsible day,<br><br><em>Fincom Bot</em>'
|
||||
)
|
||||
|
||||
def mail_fincom(self):
|
||||
emails = [s.email for s in User.objects.filter(groups__name='Fincom')]
|
||||
|
||||
send_mail(
|
||||
'Preapproved Reimbursement - ' + self.event + ': ' + self.desc,
|
||||
'Hey Fincom,\n\n' + \
|
||||
'Please take a moment to review http://fincom.delt.space/items/' + \
|
||||
str(self.pk) + ' and approve or reject the request from ' + \
|
||||
self.created_by.first_name + ' ' + self.created_by.last_name + \
|
||||
'.\n\nHave a fiscally responsible day,\n Fincom Bot',
|
||||
'fincom.bot@gmail.com',
|
||||
emails,
|
||||
html_message='Hey Fincom,<br><br>' + \
|
||||
'Please take a moment to review ' + \
|
||||
'<a href="http://fincom.delt.space/items/' + \
|
||||
str(self.pk) + '">http://fincom.delt.space/items/' + str(self.pk) +\
|
||||
'</a> and approve or reject the request from ' + \
|
||||
self.created_by.first_name + ' ' + self.created_by.last_name + \
|
||||
'.<br><br>' + \
|
||||
'Have a fiscally responsible day,<br><br> <em>Fincom Bot</em>'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def parseDate(date_str):
|
||||
try:
|
||||
return datetime.strptime(date_str, "%m/%d/%y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%m/%d/%Y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%m-%d-%y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%m-%d-%Y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%m %d %y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%m %d %Y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%y %m %d").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%Y %m %d").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%y-%m-%d").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%Y-%m-%d").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%y/%m/%d").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%Y/%m/%d").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%d %m %y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%d %m %Y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%d/%m/%y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%d/%m/%Y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%d-%m-%y").date().isoformat()
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return datetime.strptime(date_str, "%d-%m-%Y").date().isoformat()
|
||||
except ValueError:
|
||||
return date.today().isoformat()
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -1,12 +0,0 @@
|
||||
from django.conf.urls import url
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.list, name='list'),
|
||||
url(r'^(?P<item_id>\d+)/$', views.details, name='details'),
|
||||
url(r'^(?P<item_id>\d+)/approve$', views.approve, name='approve'),
|
||||
url(r'^(?P<item_id>\d+)/reject$', views.reject, name='reject'),
|
||||
url(r'^(?P<item_id>\d+)/edit$', views.edit, name='edit'),
|
||||
url(r'^(?P<item_id>\d+)/delete$', views.delete, name='delete'),
|
||||
url(r'^new$', views.new_form, name='new_form'),
|
||||
]
|
||||
@@ -1,181 +0,0 @@
|
||||
from django.shortcuts import HttpResponse, HttpResponseRedirect
|
||||
from django.template import loader
|
||||
from django.utils import timezone
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
from models import Item
|
||||
from committee.models import Committee
|
||||
|
||||
def isAuthorised(request, item):
|
||||
return (request.user == item.committee.chair
|
||||
or request.user.groups.filter(name='Fincom').exists())
|
||||
|
||||
def authError():
|
||||
return HttpResponseRedirect('/items')
|
||||
|
||||
def myItems(user):
|
||||
if (user.groups.filter(name='Fincom').exists()):
|
||||
return Item.objects.order_by('-date_filed', 'desc')
|
||||
|
||||
comms = []
|
||||
for c in Committee.objects.all():
|
||||
if (c.chair == user):
|
||||
comms.append(c)
|
||||
|
||||
return Item.objects.filter(Q(created_by=user) | Q(committee__in=comms)) \
|
||||
.order_by('-date_filed', 'desc')
|
||||
|
||||
def pageItems(request, items, status):
|
||||
if status != 'M':
|
||||
p = Paginator(items.filter(status=status), 8)
|
||||
else:
|
||||
p = Paginator(items, 8)
|
||||
page = request.GET.get(status)
|
||||
|
||||
try:
|
||||
return p.page(page)
|
||||
except PageNotAnInteger:
|
||||
return p.page(1)
|
||||
except EmptyPage:
|
||||
return p.page(p.num_pages)
|
||||
|
||||
@login_required
|
||||
def list(request):
|
||||
template = loader.get_template('items/list.html')
|
||||
items = myItems(request.user)
|
||||
mine = Item.objects.filter(created_by=request.user).order_by('-date_filed')
|
||||
|
||||
|
||||
context = {
|
||||
'preapproved': pageItems(request, items, Item.PREAPPROVED),
|
||||
'processed': pageItems(request, items, Item.PROCESSED),
|
||||
'newitems': pageItems(request, items, Item.NEW),
|
||||
'rejected': pageItems(request, items, Item.REJECTED),
|
||||
'mine': pageItems(request, mine, 'M'),
|
||||
}
|
||||
|
||||
return HttpResponse(template.render(context, request))
|
||||
|
||||
@login_required
|
||||
def details(request, item_id):
|
||||
I = Item.objects.get(pk=item_id)
|
||||
if (not isAuthorised(request, I)
|
||||
and request.user != I.created_by):
|
||||
return authError()
|
||||
|
||||
template = loader.get_template('items/details.html')
|
||||
|
||||
context = {
|
||||
'I': I,
|
||||
'approve': isAuthorised(request, I),
|
||||
}
|
||||
return HttpResponse(template.render(context, request))
|
||||
|
||||
def approve(request, item_id):
|
||||
I = Item.objects.get(pk=item_id)
|
||||
|
||||
if (not isAuthorised(request, I)):
|
||||
return authError()
|
||||
|
||||
I.approved_by.add(request.user)
|
||||
if (I.committee.chair == request.user and
|
||||
I.status == Item.NEW):
|
||||
I.status = Item.PREAPPROVED
|
||||
I.mail_fincom()
|
||||
elif (request.user.groups.filter(name='Fincom').exists()):
|
||||
I.status = Item.PROCESSED
|
||||
|
||||
I.save()
|
||||
|
||||
return HttpResponseRedirect('/items')
|
||||
|
||||
def reject(request, item_id):
|
||||
I = Item.objects.get(pk=item_id)
|
||||
|
||||
if (not isAuthorised(request, I)):
|
||||
return authError()
|
||||
|
||||
I.status = Item.REJECTED
|
||||
I.save()
|
||||
|
||||
return HttpResponseRedirect('/items')
|
||||
|
||||
def delete(request, item_id):
|
||||
I = Item.objects.get(pk=item_id)
|
||||
|
||||
if (not isAuthorised(request, I)):
|
||||
return authError()
|
||||
|
||||
I.delete()
|
||||
|
||||
return HttpResponseRedirect('/items')
|
||||
|
||||
def edit(request, item_id):
|
||||
I = Item.objects.get(pk=item_id)
|
||||
|
||||
if (not isAuthorised(request, I)
|
||||
and request.user != I.created_by):
|
||||
return authError()
|
||||
|
||||
if request.method == 'POST':
|
||||
if (request.POST.get('desc', None)):
|
||||
I.desc = request.POST['desc']
|
||||
if (request.POST.get('event', None)):
|
||||
I.event = request.POST['event']
|
||||
if (request.POST.get('committee', None)):
|
||||
I.committee = Committee.objects.get(name=request.POST['committee'])
|
||||
if (request.POST.get('cost', None)):
|
||||
I.cost = request.POST['cost']
|
||||
if (request.POST.get('date', None)):
|
||||
I.date_purchased = Item.parseDate(request.POST['date']),
|
||||
if (request.POST.get('details', None)):
|
||||
I.details = request.POST['details']
|
||||
|
||||
I.save()
|
||||
|
||||
return HttpResponseRedirect('/items/' + str(item_id))
|
||||
else:
|
||||
template = loader.get_template('items/edit.html')
|
||||
|
||||
context = {
|
||||
'I': I,
|
||||
'committees': Committee.objects.order_by('name'),
|
||||
}
|
||||
return HttpResponse(template.render(context, request))
|
||||
|
||||
def new_form(request):
|
||||
if request.method == 'POST':
|
||||
if request.FILES['image'].size > 10 * (1 << 20):
|
||||
template = loader.get_template('items/new.html')
|
||||
context = {
|
||||
'committees': Committee.objects.order_by('name'),
|
||||
'error': 'Your image file is too large. Maximum size is 20MB',
|
||||
}
|
||||
return HttpResponse(template.render(context, request))
|
||||
item = Item(
|
||||
desc = request.POST['desc'],
|
||||
event = request.POST['event'],
|
||||
committee = Committee.objects.get(name=request.POST['committee']),
|
||||
cost = request.POST['cost'],
|
||||
date_purchased = Item.parseDate(request.POST['date']),
|
||||
details = request.POST['details'],
|
||||
date_filed = timezone.now(),
|
||||
created_by = request.user,
|
||||
status = Item.NEW,
|
||||
image = request.FILES['image'],
|
||||
)
|
||||
|
||||
item.save()
|
||||
|
||||
item.mail_com_chair()
|
||||
|
||||
return HttpResponseRedirect('/items')
|
||||
|
||||
else:
|
||||
template = loader.get_template('items/new.html')
|
||||
context = {
|
||||
'committees': Committee.objects.order_by('name'),
|
||||
}
|
||||
|
||||
return HttpResponse(template.render(context, request))
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fincom.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError:
|
||||
# The above import may fail for some other reason. Ensure that the
|
||||
# issue is really that Django is missing to avoid masking other
|
||||
# exceptions on Python 2.
|
||||
try:
|
||||
import django
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
)
|
||||
raise
|
||||
execute_from_command_line(sys.argv)
|
||||
@@ -159,6 +159,7 @@ USE_TZ = True
|
||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(BASE_DIR, 'static'),
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
SRC=$(wildcard *.scss)
|
||||
|
||||
all: $(SRC)
|
||||
sass include.scss ../css/site.css
|
||||
@@ -1,42 +0,0 @@
|
||||
.approve {
|
||||
padding: $pad-xl;
|
||||
|
||||
.status {
|
||||
font-size: 16px;
|
||||
color: $midgray;
|
||||
font-weight: lighter;
|
||||
|
||||
span {
|
||||
color: $text;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
border: 1px solid $midgray;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: $pad-m 0;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
color: $text;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: $pad-m 0;
|
||||
padding: $pad-l;
|
||||
border-radius: $pad-s;
|
||||
border: 1px solid $midgray;
|
||||
|
||||
span {
|
||||
color: $midgray;
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
$purple: #563d7c;
|
||||
$gold: #fdd017;
|
||||
$lightgray: #f8f8f8;
|
||||
$midgray: #969499;
|
||||
$darkgray: #4b4a4d;
|
||||
$text: #252526;
|
||||
$shadowgray: rgba(0, 0, 0, .8);
|
||||
|
||||
// size constants
|
||||
$pad-s: 2px;
|
||||
$pad-m: 8px;
|
||||
$pad-l: 16px;
|
||||
$pad-xl: 36;
|
||||
|
||||
body {
|
||||
color: $text;
|
||||
font-family: sans-serif;
|
||||
background-color: $lightgray;
|
||||
padding-bottom: 0px;
|
||||
margin: 0px $pad-l;
|
||||
}
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50px;;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
background-color: $purple;
|
||||
color: $gold;
|
||||
box-shadow: 0px 3px 12px $shadowgray;
|
||||
z-index: 10;
|
||||
|
||||
h3 {
|
||||
font-weight: lighter;
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
float: right;
|
||||
color: $gold;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right;
|
||||
margin: $pad-l;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-bottom: $pad-xl;
|
||||
|
||||
& > div {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 64px;
|
||||
max-width: 960;
|
||||
background-color: #fff;
|
||||
border: solid 1px $midgray;
|
||||
box-shadow: 0px 3px 9px lighten($shadowgray, 20%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: $pad-s;
|
||||
border: 1px solid $midgray;
|
||||
color: $purple;
|
||||
|
||||
display: inline-block;
|
||||
padding: $pad-m $pad-l;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($purple, 35%);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
@import 'globals';
|
||||
@import 'details';
|
||||
@import 'items';
|
||||
@import 'login';
|
||||
@import 'new';
|
||||
@@ -1,144 +0,0 @@
|
||||
section {
|
||||
color: $midgray;
|
||||
font-weight: lighter;
|
||||
font-size: 16px;
|
||||
margin-top: 2*$pad-xl;
|
||||
margin-bottom: -2*$pad-xl + $pad-l;
|
||||
max-width: 960px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.items {
|
||||
a.page {
|
||||
display: inline-block;
|
||||
margin: $pad-m;
|
||||
text-decoration: none;
|
||||
color: $midgray;
|
||||
font-size: 12px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
a.page:hover {
|
||||
text-decoration: underline;
|
||||
color: $purple;
|
||||
}
|
||||
|
||||
a.page.empty:hover {
|
||||
color: $midgray;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: $pad-m $pad-xl;
|
||||
margin: 0px;
|
||||
border-bottom: 1px solid $midgray;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
|
||||
.committee {
|
||||
color: $midgray;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.details-row {
|
||||
margin: $pad-m 0;
|
||||
font-size: 16px;
|
||||
font-weight: lighter;
|
||||
color: $text;
|
||||
padding: 0px;
|
||||
|
||||
b {
|
||||
color: $darkgray;
|
||||
}
|
||||
|
||||
em {
|
||||
color: $midgray;
|
||||
margin: 0 $pad-s;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
color: $midgray;
|
||||
font-size: 12px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.cost {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
color: $midgray;
|
||||
border-radius: $pad-s;
|
||||
border: 1px solid $midgray;
|
||||
box-sizing: border-box;
|
||||
padding: 2*$pad-s;
|
||||
margin: 2*$pad-s 0;
|
||||
}
|
||||
|
||||
.approved {
|
||||
background-color: #ffd9d9;
|
||||
}
|
||||
|
||||
.processed {
|
||||
background-color: #d9ffd9;
|
||||
}
|
||||
|
||||
.rejected {
|
||||
background-color: $midgray;
|
||||
color: $lightgray;
|
||||
}
|
||||
|
||||
.newItem {
|
||||
background-color: #fff7d9;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.item .cost {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin: $pad-l;
|
||||
color: $midgray;
|
||||
font-weight: lighter;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
$button-size: 24px;
|
||||
|
||||
.btn-floating {
|
||||
position: fixed;
|
||||
bottom: $button-size;
|
||||
right: $button-size;
|
||||
padding: $button-size/2;
|
||||
color: $gold;
|
||||
background-color: $purple;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
|
||||
z-index: 1000;
|
||||
border-radius: 50%;
|
||||
|
||||
text-align: center;
|
||||
box-shadow: 0 0 6px rgba(0,0,0,.16),0 6px 12px rgba(0,0,0,.32);
|
||||
|
||||
span {
|
||||
display: block;
|
||||
width: 36;
|
||||
height: 36;
|
||||
font-size: 28;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
body.login {
|
||||
background-color: $purple;
|
||||
color: $gold;
|
||||
}
|
||||
|
||||
|
||||
.login {
|
||||
text-align: center;
|
||||
color: $gold;
|
||||
|
||||
div {
|
||||
margin-top: 30%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
margin-bottom: $pad-m;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
margin-top: -$pad-l;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: $gold;
|
||||
color: $purple;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($gold, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
font-weight: lighter;
|
||||
color: $purple;
|
||||
margin: $pad-l;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: $pad-l;
|
||||
color: $midgray;
|
||||
}
|
||||
|
||||
|
||||
form {
|
||||
div {
|
||||
margin: $pad-l;
|
||||
|
||||
* {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
label {
|
||||
width: 180px;
|
||||
color: $text;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
&.clear {
|
||||
margin: -$pad-l 0;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
&.clear:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rcol {
|
||||
margin: $pad-l;
|
||||
color: $midgray;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
@media (min-width: 430px) {
|
||||
form {
|
||||
div {
|
||||
width: 376px;
|
||||
|
||||
|
||||
label {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
width: 180px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.rcol {
|
||||
float: right;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}Fincom Webapp{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
{% load staticfiles %}
|
||||
<link href="{% static "css/site.css" %}" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav">
|
||||
<a href="/logout">logout</a>
|
||||
<h3>Fincom Web App</h3>
|
||||
</div>
|
||||
<div class="container">
|
||||
{% block main %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% block bottom %}
|
||||
{% endblock %}
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-62761470-2', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,31 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Login - Fincom</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% load staticfiles %}
|
||||
<link href="{% static "css/site.css" %}" rel="stylesheet">
|
||||
</head>
|
||||
<body class="login">
|
||||
<div>
|
||||
<h3>Delta Beta Chapter</h3>
|
||||
<h2>Fincom Webapp</h2>
|
||||
{% if error %}
|
||||
<p>{{ error }}</p>
|
||||
{% endif %}
|
||||
<a class="btn btn-info btn-lg"
|
||||
href="/login/google-oauth2/?next={{ next }}">
|
||||
Login with your AndrewID
|
||||
</a>
|
||||
</div>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-62761470-2', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{% extends "../boilerplate.html" %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<div>
|
||||
{% include "./item.html" %}
|
||||
|
||||
<div class="approve">
|
||||
{% if I.details %}
|
||||
<div class="details">
|
||||
<span>Details:</span><br>
|
||||
{{ I.details }}</div>
|
||||
{% endif %}
|
||||
<div class="status">Status: <span>{{ I.statusText }}</span></div>
|
||||
|
||||
<div class="actions">
|
||||
{% if approve %}
|
||||
<a href="/items/{{ I.pk }}/approve" class="btn">Approve</a>
|
||||
<a href="/items/{{ I.pk }}/reject" class="btn">Reject</a>
|
||||
{% endif %}
|
||||
<a href="/items/{{ I.pk }}/delete" class="btn">Delete</a>
|
||||
<a href="/items/{{ I.pk }}/edit" class="btn">Edit</a>
|
||||
<a href="/items" class="btn">Back</a>
|
||||
</div>
|
||||
|
||||
<a href="{{ I.image.url }}"><img src="{{ I.image.thumbnail.url }}"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,66 +0,0 @@
|
||||
{% extends "../boilerplate.html" %}
|
||||
|
||||
{% block title %}Edit Reimbursement{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<div>
|
||||
<a href="/items" class="btn pull-right">Back</a>
|
||||
<h1>Edit Reimbursements</h1>
|
||||
<hr>
|
||||
|
||||
<form action="/items/{{ I.pk }}/edit" method="post">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<label for="desc">Item</label>
|
||||
<input id="desc" type="text" name="desc" placeholder="What you bought"
|
||||
value="{{ I.desc }}">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="event">Event</label>
|
||||
<input id="event" type="text" name="event" placeholder="When we need it"
|
||||
value="{{ I.event }}">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="committee">Committee or Budget</label>
|
||||
<select name="committee" id="committee">
|
||||
{% for C in committees %}
|
||||
{% if I.committee == C %}
|
||||
<option selected>{{ C.name }}</option>
|
||||
{% else %}
|
||||
<option>{{ C.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="cost">Cost</label>
|
||||
<input id="cost" type="text" name="cost" placeholder="15.00"
|
||||
value="{{ I.cost }}">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="date">Date Purchased</label>
|
||||
<input id="date" type="date" name="date" placeholder="mm/dd/yyyy"
|
||||
value="{{ I.date_purchased }}">
|
||||
</div>
|
||||
|
||||
<div class="clear">
|
||||
<div>
|
||||
<label for="details">Details</label>
|
||||
<textarea id="details" name="details" rows="4" class="clear">{{ I.details }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clear">
|
||||
<div>
|
||||
<input type="submit">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,38 +0,0 @@
|
||||
<div class="item">
|
||||
<div class="committee">
|
||||
{{ I.comName }}
|
||||
</div>
|
||||
{% if I.approved %}
|
||||
<div class="cost approved">
|
||||
{% elif I.processed %}
|
||||
<div class="cost processed">
|
||||
{% elif I.rejected %}
|
||||
<div class="cost rejected">
|
||||
{% else %}
|
||||
<div class="cost newItem">
|
||||
{% endif %}
|
||||
${{ I.cost|floatformat:"2" }}
|
||||
</div>
|
||||
<a href="/items/{{ I.pk }}">
|
||||
<div class="details-row">
|
||||
<b>{{ I.event }}:</b>
|
||||
{{ I.desc }}
|
||||
<em>by</em>
|
||||
{{ I.created_by.first_name }} {{ I.created_by.last_name }}
|
||||
<em>on</em> {{ I.date_purchased }}
|
||||
<em>(filed {{ I.date_filed }})</em>
|
||||
</div>
|
||||
</a>
|
||||
<div class="status">
|
||||
{% if I.approved or I.processed %}
|
||||
Approved By:
|
||||
{% for u in I.approved_by.all %}
|
||||
{{ u.first_name }} {{ u.last_name }}
|
||||
{% endfor %}
|
||||
{% elif I.rejected %}
|
||||
Reimbursement Declined
|
||||
{% else %}
|
||||
Needs Approval
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,45 +0,0 @@
|
||||
{% extends "../boilerplate.html" %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% if error %}
|
||||
<div>
|
||||
<span class="error">{{ error }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if preapproved %}
|
||||
{% with items=preapproved pagination_key="C" name="Pre-Approved" %}
|
||||
{% include "./section.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if newitems %}
|
||||
{% with items=newitems pagination_key="N" name="New Items" %}
|
||||
{% include "./section.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if processed %}
|
||||
{% with items=processed pagination_key="P" name="Approved" %}
|
||||
{% include "./section.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if rejected %}
|
||||
{% with items=rejected pagination_key="R" name="Rejected" %}
|
||||
{% include "./section.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% with items=mine pagination_key="M" name="My Reimbursements" %}
|
||||
{% include "./section.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block bottom %}
|
||||
<a href="/items/new" title="Submit new item" class="btn-floating">
|
||||
<span>+</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
@@ -1,68 +0,0 @@
|
||||
{% extends "../boilerplate.html" %}
|
||||
|
||||
{% block title %}New Reimbursement{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<div>
|
||||
<h1>Add New Reimbursements</h1>
|
||||
<hr>
|
||||
|
||||
<div class="rcol">
|
||||
Make sure all information is accurate, you submit an itemized receipt, and
|
||||
you select the correct committee.
|
||||
</div>
|
||||
|
||||
<form action="/items/new" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<label for="desc">Item</label>
|
||||
<input id="desc" type="text" name="desc" placeholder="What you bought">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="event">Event</label>
|
||||
<input id="event" type="text" name="event" placeholder="When we need it">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="committee">Committee or Budget</label>
|
||||
<select name="committee" id="committee">
|
||||
<option disabled selected>Select a committee</option>
|
||||
{% for C in committees %}
|
||||
<option>{{ C.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="cost">Cost</label>
|
||||
<input id="cost" type="text" name="cost" placeholder="15.00">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="date">Date Purchased</label>
|
||||
<input id="date" type="date" name="date" placeholder="mm/dd/yyyy">
|
||||
</div>
|
||||
|
||||
<div class="clear">
|
||||
<div>
|
||||
<label for="details">Details</label>
|
||||
<textarea id="details" name="details" rows="4" class="clear"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="image">Receipt</label>
|
||||
<input type="file" name="image" id="image" accept="image/*">
|
||||
</div>
|
||||
|
||||
<div class="clear">
|
||||
<div>
|
||||
<input type="submit">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,22 +0,0 @@
|
||||
<section>{{ name }}</section>
|
||||
<div class="items">
|
||||
{% for I in items %}
|
||||
{% include "./item.html" %}
|
||||
{% empty %}
|
||||
<div class="empty">No Reimbursements</div>
|
||||
{% endfor %}
|
||||
{% if items.has_next %}
|
||||
<a class="page" href="?{{ pagination_key }}={{ items.next_page_number }}">
|
||||
Older →
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="page empty" href="#">Older →</a>
|
||||
{% endif %}
|
||||
{% if items.has_previous %}
|
||||
<a class="page" href="?{{ pagination_key }}={{ items.previous_page_number }}">
|
||||
← Newer
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="page empty" href="#">← Newer</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -10,7 +10,9 @@ https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
from whitenoise.django import DjangoWhiteNoise
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fincom.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
application = DjangoWhiteNoise(application)
|
||||
Reference in New Issue
Block a user