Handle login/logout flows, more navigation, and add notification emails
This commit is contained in:
@@ -132,6 +132,14 @@ SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.environ['SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET'
|
|||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = ['andrew.cmu.edu']
|
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = ['andrew.cmu.edu']
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {'hd': 'andrew.cmu.edu' }
|
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {'hd': 'andrew.cmu.edu' }
|
||||||
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/items/'
|
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/items/'
|
||||||
|
LOGIN_URL = '/login/google-oauth2/'
|
||||||
|
|
||||||
|
# email settings
|
||||||
|
EMAIL_HOST = 'smtp.gmail.com'
|
||||||
|
EMAIL_PORT = '465'
|
||||||
|
EMAIL_USE_SSL = True
|
||||||
|
EMAIL_HOST_USER = 'fincom.bot@gmail.com'
|
||||||
|
EMAIL_HOST_PASSWORD = os.environ['EMAIL_PASSWORD']
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from . import views
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.index, name='index'),
|
url(r'^$', views.index, name='index'),
|
||||||
|
url(r'^logout$', views.user_logout, name='logout'),
|
||||||
url(r'^items/', include('items.urls')),
|
url(r'^items/', include('items.urls')),
|
||||||
url('', include('social_django.urls', namespace='social')),
|
url('', include('social_django.urls', namespace='social')),
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
from django.contrib.auth import logout
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
|
if request.user.is_authenticated():
|
||||||
|
return HttpResponseRedirect('/items')
|
||||||
|
|
||||||
template = loader.get_template('fincom/index.html')
|
template = loader.get_template('fincom/index.html')
|
||||||
return HttpResponse(template.render({}, request))
|
return HttpResponse(template.render({}, request))
|
||||||
|
|
||||||
|
def user_logout(request):
|
||||||
|
logout(request)
|
||||||
|
|
||||||
|
return HttpResponseRedirect('/')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.core.mail import send_mail
|
||||||
from storages.backends.s3boto3 import S3Boto3Storage
|
from storages.backends.s3boto3 import S3Boto3Storage
|
||||||
from stdimage.models import StdImageField
|
from stdimage.models import StdImageField
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
@@ -59,6 +60,49 @@ class Item(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.committee.name + ": " + self.event + " " + self.desc
|
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
|
@staticmethod
|
||||||
def parseDate(date_str):
|
def parseDate(date_str):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ def authError():
|
|||||||
|
|
||||||
def myItems(user):
|
def myItems(user):
|
||||||
if (user.groups.filter(name='Fincom').exists()):
|
if (user.groups.filter(name='Fincom').exists()):
|
||||||
return Items.objects.order_by('-date_filed', 'desc')
|
return Item.objects.order_by('-date_filed', 'desc')
|
||||||
|
|
||||||
comms = []
|
comms = []
|
||||||
for c in Committee.objects.all():
|
for c in Committee.objects.all():
|
||||||
@@ -42,13 +42,15 @@ def list(request):
|
|||||||
@login_required
|
@login_required
|
||||||
def details(request, item_id):
|
def details(request, item_id):
|
||||||
I = Item.objects.get(pk=item_id)
|
I = Item.objects.get(pk=item_id)
|
||||||
if (not isAuthorised(request, I)):
|
if (not isAuthorised(request, I)
|
||||||
return HttpResponseRedirect('/items/' + str(item_id) + '/edit')
|
and request.user != I.created_by):
|
||||||
|
return authError()
|
||||||
|
|
||||||
template = loader.get_template('items/details.html')
|
template = loader.get_template('items/details.html')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'I': I,
|
'I': I,
|
||||||
|
'approve': isAuthorised(request, I),
|
||||||
}
|
}
|
||||||
return HttpResponse(template.render(context, request))
|
return HttpResponse(template.render(context, request))
|
||||||
|
|
||||||
@@ -62,6 +64,7 @@ def approve(request, item_id):
|
|||||||
if (I.committee.chair == request.user and
|
if (I.committee.chair == request.user and
|
||||||
I.status == Item.NEW):
|
I.status == Item.NEW):
|
||||||
I.status = Item.PREAPPROVED
|
I.status = Item.PREAPPROVED
|
||||||
|
I.mail_fincom()
|
||||||
elif (request.user.groups.filter(name='Fincom').exists()):
|
elif (request.user.groups.filter(name='Fincom').exists()):
|
||||||
I.status = Item.PROCESSED
|
I.status = Item.PROCESSED
|
||||||
|
|
||||||
@@ -147,6 +150,8 @@ def new_form(request):
|
|||||||
|
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
|
item.mail_com_chair()
|
||||||
|
|
||||||
return HttpResponseRedirect('/items')
|
return HttpResponseRedirect('/items')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -26,3 +26,17 @@
|
|||||||
font-weight: normal;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,9 +38,23 @@ body {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
float: right;
|
||||||
|
color: $gold;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pull-right {
|
||||||
|
float: right;
|
||||||
|
margin: $pad-l;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
margin-bottom: $pad-xl;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
@@ -50,6 +64,7 @@ body {
|
|||||||
border: solid 1px $midgray;
|
border: solid 1px $midgray;
|
||||||
box-shadow: 0px 3px 9px lighten($shadowgray, 20%);
|
box-shadow: 0px 3px 9px lighten($shadowgray, 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
|
<a href="/logout">logout</a>
|
||||||
<h3>Fincom Web App</h3>
|
<h3>Fincom Web App</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -6,13 +6,21 @@
|
|||||||
{% include "./item.html" %}
|
{% include "./item.html" %}
|
||||||
|
|
||||||
<div class="approve">
|
<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="status">Status: <span>{{ I.statusText }}</span></div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
{% if approve %}
|
||||||
<a href="/items/{{ I.pk }}/approve" class="btn">Approve</a>
|
<a href="/items/{{ I.pk }}/approve" class="btn">Approve</a>
|
||||||
<a href="/items/{{ I.pk }}/reject" class="btn">Reject</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 }}/delete" class="btn">Delete</a>
|
||||||
<a href="/items/{{ I.pk }}/edit" class="btn">Edit</a>
|
<a href="/items/{{ I.pk }}/edit" class="btn">Edit</a>
|
||||||
|
<a href="/items" class="btn">Back</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="{{ I.image.url }}"><img src="{{ I.image.thumbnail.url }}"></a>
|
<a href="{{ I.image.url }}"><img src="{{ I.image.thumbnail.url }}"></a>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<a href="/items" class="btn pull-right">Back</a>
|
||||||
<h1>Edit Reimbursements</h1>
|
<h1>Edit Reimbursements</h1>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="rcol">
|
<div class="rcol">
|
||||||
Make sure all information is accurate, you submit and itemized receipt, and
|
Make sure all information is accurate, you submit an itemized receipt, and
|
||||||
you select the correct committee.
|
you select the correct committee.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user