Skip to content

Commit

Permalink
added application look-up form to dashboard
Browse files Browse the repository at this point in the history
part of #20
resolves #29
  • Loading branch information
jesteria committed Feb 15, 2018
1 parent 2888812 commit c2dadca
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/review/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ class FinalDecision(StrEnum):

class Meta:
db_table = 'application'
ordering = ('-created',)

def __str__(self):
return f'{self.applicant} ({self.program_year})'
Expand Down
67 changes: 37 additions & 30 deletions src/review/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ class UnexpectedReviewer(LookupError):


def apps_to_review(reviewer, *, application_id=None, limit=None,
include_reviewed=False):
"""Construct an ordered RawQuerySet of Applications available to
the Reviewer to review.
include_reviewed=False, ordered=True):
"""Construct a query set of Applications available to the Reviewer
to review.
By default, an ordered RawQuerySet is returned. Set `ordered=False`
to construct a typical QuerySet without special ordering.
"""
# Test that reviewer can help with application reviews
Expand Down Expand Up @@ -69,33 +72,37 @@ def apps_to_review(reviewer, *, application_id=None, limit=None,
assert result

# Return stream of applications appropriate to reviewer,
# ordered by appropriateness
#
# This is the QuerySet we want, using Django's ORM:
#
# return models.Application.objects.annotate(
# # extend query with these for filtering/ordering
# page_count=Count('applicationpage', distinct=True),
# review_count=Count('review', distinct=True),
# ).filter(
# # only consider applications ...
# # ... for this program year
# program_year=settings.REVIEW_PROGRAM_YEAR,
# # ... which the applicant completed
# page_count=settings.REVIEW_SURVEY_LENGTH,
# # ... which we haven't culled
# review_decision=True,
# ).exclude(
# # exclude applications which this reviewer has already reviewed
# review__reviewer=reviewer,
# ).order_by(
# # prioritize applications by their lack of reviews
# 'review_count',
# # ... otherwise *randomize* applications to ensure simultaneous
# # reviewers do not review the same application
# '?',
# )
#
# optionally ordered by appropriateness

if not ordered:
# This is the QuerySet we want, using Django's ORM:
applications = models.Application.objects.annotate(
# extend query with these for filtering/ordering
page_count=Count('applicationpage', distinct=True),
).filter(
# only consider applications ...
# ... for this program year
program_year=settings.REVIEW_PROGRAM_YEAR,
# ... which the applicant completed
page_count=settings.REVIEW_SURVEY_LENGTH,
# ... which we haven't culled
review_decision=True,
)

if application_id:
applications = applications.filter(application_id=application_id)

if not include_reviewed:
applications = applications.exclude(
# exclude applications which this reviewer has already reviewed
review__reviewer=reviewer,
)

if limit is not None:
applications = applications[:limit]

return applications

# However, it's an ORM ...
# ... and it has at least this bug:
# https://code.djangoproject.com/ticket/26390
Expand Down
110 changes: 110 additions & 0 deletions src/review/templates/review/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@
DSSG {{ program_year }} Application Reviews
{% endblock %}

{% block head %}
<style>
.clear {
clear: both;
}
#look-up input[type=text] {
width: 450px;
box-sizing: border-box;
}
#look-up input[type=submit] {
width: 50px;
margin-left: -52px;
border: 0;
}
#look-up-results {
margin-top: 0.5em;
border: dotted #c7c7c7 1px;
height: 150px;
overflow-y: auto;
}
#look-up-results > li {
line-height: 2em;
padding: 0 5px;
}
</style>
{% endblock %}

{% block body %}
<h2>DSSG {{ program_year }} Application Reviews</h2>

Expand All @@ -21,6 +48,13 @@ <h2>DSSG {{ program_year }} Application Reviews</h2>
</a>
</p>

<h3>Look up an application</h3>
<form id="look-up" class="short-width">
<input type="text"><input type="submit" value="search">
<ul id="look-up-results" class="basic striped">
</ul>
</form>

{% if reviews %}
<h3>Your submitted application reviews</h3>

Expand All @@ -34,4 +68,80 @@ <h3>Your submitted application reviews</h3>
{% endfor %}
</dl>
{% endif %}

<script type="text/javascript">
(function (window) {
'use strict';

var form = window.document.getElementById('look-up'),
textInput = form.querySelector('input[type=text]'),
resultsList = window.document.getElementById('look-up-results'),
loadingItem = window.document.createElement('li'),
emptyItem = window.document.createElement('li');

loadingItem.innerText = 'loading \u2026';
emptyItem.innerText = 'no results';

function clearResults () {
var child;
while (child = resultsList.firstChild) {
resultsList.removeChild(child);
}
}

function handleResponse (response) {
if (response.ok) return response.json();
throw new Error("Bad response");
}

function handlePayload (payload) {
clearResults();
if (payload.results.length === 0) {
resultsList.appendChild(emptyItem);
} else {
payload.results.forEach(handleResult);
}
}

function handleResult (result) {
var listItem = window.document.createElement('li'),
innerList = window.document.createElement('ul'),
labelItem = window.document.createElement('li'),
anchorItem = window.document.createElement('li'),
anchor = window.document.createElement('a');

innerList.className = 'horizontal clear';

labelItem.innerText = result.applicant__email;

anchor.href = "{% url 'review-application' %}" + result.application_id + "/";
anchor.className = "em-svg em-memo";
anchorItem.appendChild(anchor);
anchorItem.className = 'pull-right';

innerList.appendChild(labelItem);
innerList.appendChild(anchorItem);
listItem.appendChild(innerList);
resultsList.appendChild(listItem);
}

form.addEventListener('submit', function(evt) {
var url,
queryTerm = textInput.value;

evt.preventDefault();

if (queryTerm) {
clearResults();
resultsList.appendChild(loadingItem);

url = '/application.json?q=' + queryTerm;

fetch(url, {
credentials: 'same-origin'
}).then(handleResponse).then(handlePayload);
}
});
})(window);
</script>
{% endblock %}
4 changes: 4 additions & 0 deletions src/review/templates/review/site.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@
animation: fadeout 1s linear 10s 1 normal forwards;
}

ul.striped > li:nth-child(even) {
background-color: #eaeaea;
}

@keyframes fadeout {
from {
opacity: 1;
Expand Down
4 changes: 3 additions & 1 deletion src/review/urls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from django.urls import path, re_path
from django.views.generic import RedirectView

from . import views

Expand All @@ -13,6 +12,9 @@
# path('review/interview/', views.review, name='review-interview'),
# path('review/interview/...', views.review, name='review-interview-detail'),

path('application.json', views.list_applications, {'content_type': 'json'}, name='application-list'),
# path('application/', views.list_applications, name='application-list'),

re_path(r"confirm-email/(?P<key>[-:\w]+)/$",
views.invite_confirm_email,
name="account_confirm_email"),
Expand Down
59 changes: 58 additions & 1 deletion src/review/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.db import connection, transaction
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse
Expand Down Expand Up @@ -108,6 +108,63 @@ def index(request):
})


@require_GET
@login_required
def list_applications(request, content_type='html'):
if content_type != 'json':
raise NotImplementedError

query_raw = request.GET.get('q', '')
applications = query.apps_to_review(request.user,
include_reviewed=True,
ordered=False)

if query_raw:
with connection.cursor() as cursor:
cursor.execute('''\
SELECT "EntryId"
FROM "survey_application_1_2018"
WHERE
LOWER("Field451") IN %(query_terms)s OR
LOWER("Field452") IN %(query_terms)s OR
LOWER("Field461") IN %(query_terms)s
''',
{'query_terms': tuple(query_raw.lower().split())}
)
entry_ids = [row[0] for row in cursor]

applications = applications.filter(
applicationpage__table_name='survey_application_1_2018',
applicationpage__column_name='EntryId',
applicationpage__entity_code__in=entry_ids,
)
elif not request.user.trusted:
return http.JsonResponse(
{
'status': 'forbidden',
'error': 'not allowed',
},
status=403,
)

return http.JsonResponse({
'status': 'ok',
'results': list(
applications
.values(
'application_id',
'applicant_id',
'program_year',
'created',
'applicant__email',
)
.order_by(
'applicant__email',
)
),
})


@require_http_methods(['GET', 'POST'])
@login_required
@unexpected_review
Expand Down

0 comments on commit c2dadca

Please sign in to comment.