Skip to content

Commit

Permalink
allow first markers to see and copy across last years answers
Browse files Browse the repository at this point in the history
If there are previous questions available then display the answers and
allow the First Markers to copy these across. This might not work for
the options if they have changed but that's ok because if they've
changed then they should be checking them and not copying them.

Fixes #133
  • Loading branch information
struan committed Jun 12, 2024
1 parent 7aa703e commit 2f60c0b
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 20 deletions.
3 changes: 2 additions & 1 deletion crowdsourcer/fixtures/questions.json
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,8 @@
2,
3,
4
]
],
"previous_question": 281
}
}
]
1 change: 1 addition & 0 deletions crowdsourcer/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__(self, *args, **kwargs):

self.authority_obj = self.initial.get("authority", None)
self.question_obj = self.initial.get("question", None)
self.previous_response = self.initial.get("previous_response", None)

self.fields["option"].queryset = Option.objects.filter(
question=self.question_obj
Expand Down
19 changes: 1 addition & 18 deletions crowdsourcer/templates/crowdsourcer/authority_questions.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,7 @@ <h3 class="mb-4 mb-md-5 text-success">
</details>
</div>
<div class="col-md-7 order-md-1">
<label class="form-label" for="{{ q_form.option.if_for_label }}">Answer</label>
{% if q_form.question_obj.question_type == "multiple_choice" %}
{% bootstrap_field q_form.multi_option show_label="skip" %}
{% else %}
{% bootstrap_field q_form.option show_label="skip" %}
{% endif %}

{% bootstrap_field q_form.page_number %}

{% bootstrap_field q_form.evidence %}

{% bootstrap_field q_form.public_notes %}

{% bootstrap_field q_form.private_notes %}

{{ q_form.authority }}
{{ q_form.question }}
{{ q_form.id }}
{% include 'crowdsourcer/includes/first_mark_response_form_fields.html' %}
</div>
</div>
</fieldset>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
{% extends 'crowdsourcer/base.html' %}

{% load django_bootstrap5 %}
{% load neighbourhood_filters %}

{% block content %}
<h1 class="mb-3 mb-md-4">
{% if authority.website %}
<a href="{{ authority.website }}">{{ authority_name }}</a>:
{% else %}
{{ authority_name }}:
{% endif %}
{{section_title}}
</h1>

<div class="sticky-top py-3 bg-white border-bottom" style="margin-bottom: -1px;">
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" id="navbarDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Skip to question
</button>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
{% for q_form in form %}
<li><a class="dropdown-item d-flex" style="max-width: 30em; white-space: normal;" href="#q{{q_form.question_obj.number_and_part}}">
<span style="width: 2em; flex: 0 0 auto;">Q{{q_form.question_obj.number_and_part}}</span>
<span class="text-muted fs-7 ms-3">{{ q_form.question_obj.description }}</span>
</a></li>
{% endfor %}
</ul>
</div>
</div>

{% if message %}
<h3 class="mb-4 mb-md-5 text-success">
{{ message }}
</h3>
{% endif %}

<form method="POST">
{% csrf_token %}
{% if form.total_error_count > 0 %}
<div class="mb-4">
<div class="col-md-7 text-danger">
There were some errors which are highlighted in red below.
</div>
</div>
{% endif %}
{{ form.management_form }}
{% for q_form in form %}
<fieldset class="py-4 py-md-5 border-top">
<legend id="q{{ q_form.question_obj.number_and_part }}" class="mb-3">
<h2 class="h4 mb-0 mw-40em">
{{ q_form.question_obj.number_and_part }}. {{ q_form.question_obj.description }}
{% if q_form.question_obj.how_marked == "foi" %}(FOI){% endif %}
{% if q_form.question_obj.how_marked == "national_data" %}(National Data){% endif %}
</h2>
</legend>
<div class="d-sm-flex mx-n3 text-muted">
<details class="mt-3 mt-sm-0 mx-3 mw-30em">
<summary class="fw-bold mb-2">Criteria</summary>
{% autoescape off %}
{{ q_form.question_obj.criteria|linebreaks }}
{% endautoescape %}
</details>
<details class="mt-3 mt-sm-0 mx-3 mw-30em">
<summary class="fw-bold mb-2">Clarifications</summary>
{% autoescape off %}
{{ q_form.question_obj.clarifications|linebreaks }}
{% endautoescape %}
</details>
</div>
<div class="row gx-lg-5">
<div class="col-lg-6 mt-6">

<h3 class="h5 text-muted mb-3 mb-lg-4">Previous Response</h3>

<h4 class="form-label fs-6">Marker’s answer</h4>
<div class="read-only-answer mb-3 mb-md-4">
{% if q_form.previous_response.multi_option.values %}
<p>
{% for option in q_form.previous_response.multi_option.values %}
{{ option.description }},
{% empty %}
(none)
{% endfor %}
</p>
{% else %}
{{ q_form.previous_response.option|default:"(none)"|linebreaks }}
{% endif %}
</div>

{% if q_form.question_obj.how_marked == 'foi' %}
<h4 class="form-label fs-6">FOI request</h4>
<div class="read-only-answer mb-3 mb-md-4">
{{ q_form.previous_response.evidence|urlize_external }}
</div>
{% else %}
<h4 class="form-label fs-6">Marker’s evidence of criteria met</h4>
<div class="read-only-answer mb-3 mb-md-4">
{{ q_form.previous_response.evidence|default:"(none)"|linebreaks }}
</div>
{% endif %}

{% if q_form.question_obj.how_marked != 'foi' %}
<h4 class="form-label fs-6">Links to evidence</h4>
<div class="read-only-answer mb-3 mb-md-4">
{{ q_form.previous_response.public_notes|default:"(none)"|urlize_external|linebreaks }}
</div>

<h4 class="form-label fs-6">Page number</h4>
<div class="read-only-answer mb-3 mb-md-4">
{{ q_form.previous_response.page_number|default:"(none)" }}<br>
</div>
{% endif %}

<h4 class="form-label fs-6">Marker’s additional notes</h4>
<div class="read-only-answer mb-3 mb-md-4">
{{ q_form.previous_response.private_notes|default:"(none)"|linebreaks }}
</div>

</div>
<div class="col-lg-6 mt-6 js-new-responses">

<h3 class="h5 text-muted mb-3 mb-lg-4">New response</h3>

<div class="mb-3">
<button class="btn btn-sm btn-outline-primary d-flex align-items-center js-copy-response-from-previous" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-2" viewBox="0 0 16 16">
<path d="M9.5 2.672a.5.5 0 1 0 1 0V.843a.5.5 0 0 0-1 0v1.829Zm4.5.035A.5.5 0 0 0 13.293 2L12 3.293a.5.5 0 1 0 .707.707L14 2.707ZM7.293 4A.5.5 0 1 0 8 3.293L6.707 2A.5.5 0 0 0 6 2.707L7.293 4Zm-.621 2.5a.5.5 0 1 0 0-1H4.843a.5.5 0 1 0 0 1h1.829Zm8.485 0a.5.5 0 1 0 0-1h-1.829a.5.5 0 0 0 0 1h1.829ZM13.293 10A.5.5 0 1 0 14 9.293L12.707 8a.5.5 0 1 0-.707.707L13.293 10ZM9.5 11.157a.5.5 0 0 0 1 0V9.328a.5.5 0 0 0-1 0v1.829Zm1.854-5.097a.5.5 0 0 0 0-.706l-.708-.708a.5.5 0 0 0-.707 0L8.646 5.94a.5.5 0 0 0 0 .707l.708.708a.5.5 0 0 0 .707 0l1.293-1.293Zm-3 3a.5.5 0 0 0 0-.706l-.708-.708a.5.5 0 0 0-.707 0L.646 13.94a.5.5 0 0 0 0 .707l.708.708a.5.5 0 0 0 .707 0L8.354 9.06Z"/>
</svg>
Copy from Previous Response
</button>
</div>

<script type="application/json" class="js-previous-json">
{
{% if q_form.previous_response.multi_option.values %}
"multi_option": [
{% for option in q_form.previous_response.multi_option.values %}
"{{ option.description }}"{% if not forloop.last %},{% endif %}
{% endfor %}
],
{% else %}
"option": "{{ q_form.previous_response.option.description }}",
{% endif %}
{% if q_form.question_obj.how_marked == "foi" %}
"evidence": "",
"public_notes": "{{ q_form.previous_response.evidence|default_if_none:''|escapejs }}",
{% else %}
"evidence": "{{ q_form.previous_response.evidence|default_if_none:''|escapejs }}",
"public_notes": "{{ q_form.previous_response.public_notes|default_if_none:''|escapejs }}",
{% endif %}
"page_number": "{{ q_form.previous_response.page_number|default_if_none:''|escapejs }}",
"private_notes": "{{ q_form.previous_response.private_notes|default_if_none:''|escapejs }}"
}
</script>

{% include 'crowdsourcer/includes/first_mark_response_form_fields.html' %}

</div>
</div>
</fieldset>
{% endfor %}

<div class="sticky-bottom py-3 bg-white border-top" style="margin-top: -1px;">
<input type="submit" class="btn btn-primary" value="Save answers">
</div>
</form>

<script>
document.querySelectorAll('.js-copy-response-from-previous').forEach(function(el, i){
el.addEventListener('click', function(e){
e.preventDefault();
var new_responses = el.closest('.js-new-responses');
var previous_responses = JSON.parse(new_responses.querySelector('.js-previous-json').textContent);

["page_number", "evidence", "public_notes", "private_notes"].forEach(function(slug, i){
new_responses.querySelector('[name$="' + slug + '"]').value = previous_responses[slug];
});

/*
Have to do this by comparing the labels as there is a new set of options so the ids
will not match. This will fail if the text is not the same but then it should.
*/
if(previous_responses.multi_option){
new_responses.querySelectorAll('[name$="multi_option"]').forEach(function(checkbox){
checkbox.checked = ( previous_responses.multi_option.indexOf(checkbox.label) > -1 );
});
} else {
new_responses.querySelectorAll('[name$="option"] option').forEach(function(option){
option.selected = option.label = previous_responses.option;
});
}
});
});
</script>

{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% load django_bootstrap5 %}

<label class="form-label" for="{{ q_form.option.if_for_label }}">Answer</label>
{% if q_form.question_obj.section.title == "Transport" and q_form.question_obj.number == 11 and authority.questiongroup.description == "District" %}
<p class="form-text">
<strong>Note:</strong> District councils are not responsible for transport planning, so you should ignore any road projects in this council’s response.
</p>
{% endif %}
{% if q_form.question_obj.question_type == "multiple_choice" %}
{% bootstrap_field q_form.multi_option show_label="skip" %}
{% else %}
{% bootstrap_field q_form.option show_label="skip" %}
{% endif %}

{% bootstrap_field q_form.page_number %}

{% bootstrap_field q_form.evidence %}

{% bootstrap_field q_form.public_notes %}

{% bootstrap_field q_form.private_notes %}

{% if q_form.question_obj.how_marked == "foi" %}
{% bootstrap_field q_form.foi_answer_in_ror %}
{% endif %}

{{ q_form.authority }}
{{ q_form.question }}
{{ q_form.id }}
26 changes: 26 additions & 0 deletions crowdsourcer/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ def test_save(self):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)

self.assertFalse(response.context["has_previous_questions"])

response = self.client.post(
url,
data={
Expand Down Expand Up @@ -754,6 +756,30 @@ def test_multi_save_fix(self):
self.assertNotEquals(last_update, answers[1].last_update)


class TestSaveWithPreviousQuestionsView(BaseTestCase):
fixtures = [
"authorities.json",
"basics.json",
"users.json",
"questions.json",
"options.json",
"assignments.json",
]

def test_save(self):
u = User.objects.get(username="other_marker")
self.client.force_login(u)

url = reverse(
"session_urls:authority_question_edit",
args=("Second Session", "Aberdeenshire Council", "Transport"),
)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)

self.assertTrue(response.context["has_previous_questions"])


class TestAllAuthorityProgressView(BaseTestCase):
def test_non_admin_denied(self):
response = self.client.get(reverse("all_authority_progress"))
Expand Down
2 changes: 2 additions & 0 deletions crowdsourcer/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class BaseQuestionView(TemplateView):
log_start = "marking form"
title_start = ""
how_marked_in = ["volunteer", "national_volunteer"]
has_previous_questions = False

def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
Expand Down Expand Up @@ -160,6 +161,7 @@ def get_context_data(self, **kwargs):
context[
"page_title"
] = f"{self.title_start}{context['authority_name']}: {context['section_title']}"
context["has_previous_questions"] = self.has_previous_questions

return context

Expand Down
40 changes: 39 additions & 1 deletion crowdsourcer/views/marking.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
MarkingSession,
PublicAuthority,
Question,
Response,
ResponseType,
)
from crowdsourcer.views.base import BaseQuestionView, BaseSectionAuthorityList
Expand Down Expand Up @@ -215,6 +216,12 @@ class SectionAuthorityList(BaseSectionAuthorityList):
class AuthoritySectionQuestions(BaseQuestionView):
template_name = "crowdsourcer/authority_questions.html"

def get_template_names(self):
if self.has_previous_questions:
return ["crowdsourcer/authority_questions_with_previous.html"]
else:
return [self.template_name]

def get_initial_obj(self):
if self.kwargs["name"] == "Isles of Scilly":
self.how_marked_in = [
Expand All @@ -224,7 +231,38 @@ def get_initial_obj(self):
"national_data",
]

return super().get_initial_obj()
initial = super().get_initial_obj()

is_previous = Question.objects.filter(
section__marking_session=self.request.current_session,
section__title=self.kwargs["section_title"],
questiongroup=self.authority.questiongroup,
how_marked__in=self.how_marked_in,
previous_question__isnull=False,
).exists()

if is_previous:
self.has_previous_questions = True
question_list = self.questions.values_list(
"previous_question_id", flat=True
)
prev_responses = Response.objects.filter(
authority=self.authority,
question__in=question_list,
response_type=self.rt,
).select_related("question")

response_map = {}
for r in prev_responses:
response_map[r.question.id] = r

for q in self.questions:
data = initial[q.id]
data["previous_response"] = response_map.get(q.previous_question_id)

initial[q.id] = data

return initial

def process_form(self, form):
cleaned_data = form.cleaned_data
Expand Down

0 comments on commit 2f60c0b

Please sign in to comment.