diff --git a/fincom/committee/__init__.py b/fincom/committee/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fincom/committee/admin.py b/fincom/committee/admin.py new file mode 100644 index 0000000..695dc2b --- /dev/null +++ b/fincom/committee/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from models import Committee + +# Register your models here. +admin.site.register(Committee) diff --git a/fincom/committee/apps.py b/fincom/committee/apps.py new file mode 100644 index 0000000..6b52e56 --- /dev/null +++ b/fincom/committee/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class CommitteeConfig(AppConfig): + name = 'committee' diff --git a/fincom/committee/models.py b/fincom/committee/models.py new file mode 100644 index 0000000..c3b4d8d --- /dev/null +++ b/fincom/committee/models.py @@ -0,0 +1,11 @@ +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) + chair = models.ForeignKey(User, null=True) + + def __str__(self): + return self.name diff --git a/fincom/committee/tests.py b/fincom/committee/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/fincom/committee/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/fincom/committee/views.py b/fincom/committee/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/fincom/committee/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/fincom/fincom/__init__.py b/fincom/fincom/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fincom/fincom/settings.py b/fincom/fincom/settings.py new file mode 100644 index 0000000..de7c5c3 --- /dev/null +++ b/fincom/fincom/settings.py @@ -0,0 +1,143 @@ +""" +Django settings for fincom project. + +Generated by 'django-admin startproject' using Django 1.10.5. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.10/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ['SECRET_KEY'] + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +AUTHENTICATION_BACKENDS = [ + 'social_core.backends.google.GoogleOAuth2', + 'django.contrib.auth.backends.ModelBackend', +] + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'items', + 'committee', + 'social_django', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'fincom.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'social_django.context_processors.backends', + 'social_django.context_processors.login_redirect', + ], + }, + }, +] + +WSGI_APPLICATION = 'fincom.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'dbu9ujb6cnhc3n', + 'USER': 'dexgqmkgoabqyd', + 'PASSWORD': os.environ['DBPASSWD'], + 'HOST': 'ec2-50-19-89-124.compute-1.amazonaws.com', + 'PORT': '5432', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# social_auth configuration +# http://python-social-auth.readthedocs.io/en/latest/configuration/settings.html + +SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.environ['SOCIAL_AUTH_GOOGLE_OAUTH2_KEY'] +SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.environ['SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET'] +SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = ['andrew.cmu.edu'] +SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/items/' + +# Internationalization +# https://docs.djangoproject.com/en/1.10/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.10/howto/static-files/ + +STATIC_URL = '/static/' +STATICFILES_DIRS = ( + os.path.join(BASE_DIR, 'static'), +) diff --git a/fincom/fincom/urls.py b/fincom/fincom/urls.py new file mode 100644 index 0000000..b6f47b4 --- /dev/null +++ b/fincom/fincom/urls.py @@ -0,0 +1,25 @@ +"""fincom URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import include, url +from django.contrib import admin +from . import views + +urlpatterns = [ + url(r'^$', views.index, name='index'), + url(r'^items/', include('items.urls')), + url('', include('social_django.urls', namespace='social')), + url(r'^admin/', admin.site.urls), +] diff --git a/fincom/fincom/views.py b/fincom/fincom/views.py new file mode 100644 index 0000000..8e567b5 --- /dev/null +++ b/fincom/fincom/views.py @@ -0,0 +1,6 @@ +from django.http import HttpResponse +from django.template import loader + +def index(request): + template = loader.get_template('fincom/index.html') + return HttpResponse(template.render({}, request)) diff --git a/fincom/fincom/wsgi.py b/fincom/fincom/wsgi.py new file mode 100644 index 0000000..268f207 --- /dev/null +++ b/fincom/fincom/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for fincom project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fincom.settings") + +application = get_wsgi_application() diff --git a/fincom/items/__init__.py b/fincom/items/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fincom/items/admin.py b/fincom/items/admin.py new file mode 100644 index 0000000..8a49933 --- /dev/null +++ b/fincom/items/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from models import Item + +# Register your models here. +admin.site.register(Item) diff --git a/fincom/items/apps.py b/fincom/items/apps.py new file mode 100644 index 0000000..15b1381 --- /dev/null +++ b/fincom/items/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class ItemsConfig(AppConfig): + name = 'items' diff --git a/fincom/items/models.py b/fincom/items/models.py new file mode 100644 index 0000000..f0c8a2f --- /dev/null +++ b/fincom/items/models.py @@ -0,0 +1,125 @@ +from __future__ import unicode_literals +from django.contrib.auth.models import User + +from committee.models import Committee + +from django.db import models + +class Item(models.Model): + 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') + + 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) + task_id = models.CharField(max_length=30) + + def approved(self): + return self.status == 'P' + + def processed(self): + return self.status == 'C' + + def rejected(self): + return self.status == 'R' + + def new(self): + return self.status == 'N' + + def comName(self): + return self.committee.name + + def __str__(self): + return self.committee.name + ": " + self.event + " " + self.desc + + @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() diff --git a/fincom/items/tests.py b/fincom/items/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/fincom/items/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/fincom/items/urls.py b/fincom/items/urls.py new file mode 100644 index 0000000..db174f6 --- /dev/null +++ b/fincom/items/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import url +from . import views + +urlpatterns = [ + url(r'^$', views.list, name='list'), +] diff --git a/fincom/items/views.py b/fincom/items/views.py new file mode 100644 index 0000000..047e002 --- /dev/null +++ b/fincom/items/views.py @@ -0,0 +1,13 @@ +from django.shortcuts import HttpResponse +from django.template import loader +from models import Item + + +# Create your views here. +def list(request): + template = loader.get_template('items/list.html') + context = { + 'items': Item.objects.all(), + } + + return HttpResponse(template.render(context, request)) diff --git a/fincom/manage.py b/fincom/manage.py new file mode 100755 index 0000000..b56a36e --- /dev/null +++ b/fincom/manage.py @@ -0,0 +1,22 @@ +#!/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) diff --git a/fincom/static/sass/Makefile b/fincom/static/sass/Makefile new file mode 100644 index 0000000..0d2a3fb --- /dev/null +++ b/fincom/static/sass/Makefile @@ -0,0 +1,4 @@ +SRC=$(wildcard *.scss) + +all: $(SRC) + sass include.scss ../css/site.css diff --git a/fincom/static/sass/globals.scss b/fincom/static/sass/globals.scss new file mode 100644 index 0000000..feb33fb --- /dev/null +++ b/fincom/static/sass/globals.scss @@ -0,0 +1,68 @@ +$purple: #563d7c; +$gold: #fdd017; +$lightgray: #fcfcfc; +$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; + } +} + +.container { + height: 100%; + margin-left: auto; + margin-right: auto; + margin-top: 64px; + max-width: 960; + background-color: #fff; + border: solid 1px $midgray; +} + + +.btn { + border-radius: $pad-s; + color: $purple; + + display: inline-block; + padding: $pad-m $pad-l; + text-decoration: none; + vertical-align: middle; + white-space: nowrap; + + + &:hover { + background-color: darken($purple, 15%); + } +} diff --git a/fincom/static/sass/include.scss b/fincom/static/sass/include.scss new file mode 100644 index 0000000..2dbeae3 --- /dev/null +++ b/fincom/static/sass/include.scss @@ -0,0 +1,2 @@ +@import 'items'; +@import 'login'; diff --git a/fincom/static/sass/items.scss b/fincom/static/sass/items.scss new file mode 100644 index 0000000..661a663 --- /dev/null +++ b/fincom/static/sass/items.scss @@ -0,0 +1,80 @@ +@import 'globals'; + +.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; + } + + .details-row { + margin: $pad-m 0; + font-size: 16px; + color: $text; + padding: 0px; + + b { + color: $darkgray; + } + + em { + color: $midgray; + margin: 0 $pad-s; + } + } + + .status { + color: $midgray; + font-size: 12px; + margin: 0px; + } + + .cost { + float: right; + 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; + } + + .newItem { + background-color: #fff7d9; + } +} + +$button-size: 24px; + +.btn-floating { + position: fixed; + bottom: $button-size; + right: $button-size; + padding: $button-size; + + 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); +} diff --git a/fincom/static/sass/login.scss b/fincom/static/sass/login.scss new file mode 100644 index 0000000..157127c --- /dev/null +++ b/fincom/static/sass/login.scss @@ -0,0 +1,35 @@ +@import 'globals'; + +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%); + } + } +} diff --git a/fincom/templates/boilerplate.html b/fincom/templates/boilerplate.html new file mode 100644 index 0000000..ef2270a --- /dev/null +++ b/fincom/templates/boilerplate.html @@ -0,0 +1,26 @@ + + + {% block title %}Fincom Webapp{% endblock %} + + {% load staticfiles %} + + + + +
+ {% block main %} + {% endblock %} +
+ + + diff --git a/fincom/templates/fincom/index.html b/fincom/templates/fincom/index.html new file mode 100644 index 0000000..fa265d6 --- /dev/null +++ b/fincom/templates/fincom/index.html @@ -0,0 +1,31 @@ + + + Login - Fincom + + {% load staticfiles %} + + + +
+

Delta Beta Chapter

+

Fincom Webapp

+ {% if error %} +

{{ error }}

+ {% endif %} + + Login with your AndrewID + +
+ + + + diff --git a/fincom/templates/items/item.html b/fincom/templates/items/item.html new file mode 100644 index 0000000..d419899 --- /dev/null +++ b/fincom/templates/items/item.html @@ -0,0 +1,32 @@ +
+
+ {{ I.comName }} +
+ {% if I.approved %} +
+ {% elif I.processed %} +
+ {% else %} +
+ {% endif %} + ${{ I.cost|floatformat:"2" }} +
+
+ {{ I.event }}: + {{ I.desc }} + by + {{ I.created_by.first_name }} {{ I.created_by.last_name }} + on {{ I.date_purchased }} + (filed {{ I.date_filed }}) +
+
+ {% if I.approved or I.processed %} + Approved By: + {% for u in I.approved_by.all %} + {{ u.first_name }} {{ u.last_name }} + {% endfor %} + {% else %} + Needs Approval + {% endif %} +
+
diff --git a/fincom/templates/items/list.html b/fincom/templates/items/list.html new file mode 100644 index 0000000..d296b51 --- /dev/null +++ b/fincom/templates/items/list.html @@ -0,0 +1,11 @@ +{% extends "../boilerplate.html" %} + +{% block main %} + +{% for I in items %} + {% include "./item.html" %} +{% empty %} +
No Reimbursements
+{% endfor %} + +{% endblock %}