Added results CRUD and result detailed view
This commit is contained in:
		@@ -64,6 +64,10 @@ class Test:
 | 
			
		||||
        return test_code.replace('—', '')
 | 
			
		||||
    
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        test = db.tests.find_one({'_id': self._id})
 | 
			
		||||
        if 'entries' in test:
 | 
			
		||||
            if test['entries']:
 | 
			
		||||
                return jsonify({'error': 'Cannot delete an exam that has entries submitted to it.'}), 400
 | 
			
		||||
        if self.dataset is None:
 | 
			
		||||
            self.dataset = db.tests.find_one({'_id': self._id})['dataset']
 | 
			
		||||
        if db.tests.delete_one({'_id': self._id}):
 | 
			
		||||
 
 | 
			
		||||
@@ -214,6 +214,11 @@ table.dataTable {
 | 
			
		||||
    font-size: 14pt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-action-buttons {
 | 
			
		||||
    margin: 5px auto;
 | 
			
		||||
    width: fit-content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Fallback for Edge
 | 
			
		||||
-------------------------------------------------- */
 | 
			
		||||
@supports (-ms-ime-align: auto) {
 | 
			
		||||
 
 | 
			
		||||
@@ -153,4 +153,43 @@ $('#dismiss-cookie-alert').click(function(event){
 | 
			
		||||
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// Script for Resutlt Actions
 | 
			
		||||
$('.result-action-buttons').click(function(event){
 | 
			
		||||
 | 
			
		||||
    var _id = $(this).data('_id')
 | 
			
		||||
 | 
			
		||||
    if ($(this).data('result-action') == 'generate') {
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: '/admin/certificate/',
 | 
			
		||||
            type: 'POST',
 | 
			
		||||
            data: JSON.stringify({'_id': _id}),
 | 
			
		||||
            contentType: 'application/json',
 | 
			
		||||
            dataType: 'html',
 | 
			
		||||
            success: function(response) {
 | 
			
		||||
                var display_window = window.open();
 | 
			
		||||
                display_window.document.write(response);
 | 
			
		||||
            },
 | 
			
		||||
            error: function(response){
 | 
			
		||||
                error_response(response);
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        var action = $(this).data('result-action')
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: window.location.href,
 | 
			
		||||
            type: 'POST',
 | 
			
		||||
            data: JSON.stringify({'_id': _id, 'action': action}),
 | 
			
		||||
            contentType: 'application/json',
 | 
			
		||||
            success: function(response) {
 | 
			
		||||
                if (action == 'delete') {
 | 
			
		||||
                    window.location.href = '/admin/results/';
 | 
			
		||||
                } else window.location.reload();
 | 
			
		||||
            },
 | 
			
		||||
            error: function(response){
 | 
			
		||||
                error_response(response);
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
@@ -32,8 +32,10 @@
 | 
			
		||||
            {% block content %}{% endblock %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <footer class="container site-footer">
 | 
			
		||||
            {% include "admin/components/footer.html" %}
 | 
			
		||||
        <footer class="container site-footer mt-5">
 | 
			
		||||
            {% block footer %}
 | 
			
		||||
                {% include "admin/components/footer.html" %}
 | 
			
		||||
            {% endblock %}
 | 
			
		||||
        </footer>
 | 
			
		||||
 | 
			
		||||
        <!-- JQuery, Popper, and Bootstrap js dependencies -->
 | 
			
		||||
@@ -70,5 +72,7 @@
 | 
			
		||||
        ></script>
 | 
			
		||||
        {% block datatable_scripts %}
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
        {% block custom_data_script %}
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										84
									
								
								ref-test/admin/templates/admin/components/certificate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								ref-test/admin/templates/admin/components/certificate.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
{% extends "admin/components/base.html" %}
 | 
			
		||||
{% block title %} SKA Referee Test | Detailed Results {% endblock %}
 | 
			
		||||
{% block navbar %}{% endblock %}
 | 
			
		||||
{% block top_alerts %}{% endblock %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <div class="d-flex justify-content-center">
 | 
			
		||||
        <h1 class="center">SKA Referee Theory Exam Results</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="container mt-5">
 | 
			
		||||
        <div class="row justify-content-center">
 | 
			
		||||
            <div class="col-lg-6">
 | 
			
		||||
                <ul class="list-group">
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Candidate</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <h2>
 | 
			
		||||
                            {{ entry.name.surname}}, {{ entry.name.first_name }}
 | 
			
		||||
                        </h2>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Email Address</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.email }}
 | 
			
		||||
                    </li>
 | 
			
		||||
                    {% if entry['club'] %}
 | 
			
		||||
                        <li class="list-group-item list-group-item-action">
 | 
			
		||||
                            <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                                <h5 class="mb-1">Club</h5>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            {{ entry.club }}
 | 
			
		||||
                        </li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Exam Code</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ '—'.join([entry.test_code[:4], entry.test_code[4:8], entry.test_code[8:]]) }}
 | 
			
		||||
                    </li>
 | 
			
		||||
                    {% if entry['user_code'] %}
 | 
			
		||||
                        <li class="list-group-item list-group-item-action">
 | 
			
		||||
                            <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                                <h5 class="mb-1">User Code</h5>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            {{ entry.user_code }}
 | 
			
		||||
                        </li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Start Time</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.start_time.strftime('%d %b %Y %H:%M:%S') }}
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Submission Time</h5>
 | 
			
		||||
                            {% if entry.status == 'late' %}
 | 
			
		||||
                                <span class="badge bg-danger">Late</span>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.submission_time.strftime('%d %b %Y %H:%M:%S') }}
 | 
			
		||||
                    </li>       
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Score</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.results.score }}%
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="list-group-item list-group-item-action {% if entry.results.grade == 'fail' %}list-group-item-danger {% elif entry.results.grade == 'merit' %} list-group-item-success {% endif %}">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Grade</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.results.grade[0]|upper }}{{ entry.results.grade[1:]}}
 | 
			
		||||
                    </li>
 | 
			
		||||
                </ul>
 | 
			
		||||
                <div class="site-footer mt-5">
 | 
			
		||||
                    These results were generated using the SKA RefTest web app on {{ now.strftime('%d %b %Y at %H:%M:%S') }}.
 | 
			
		||||
                </div>
 | 
			
		||||
                {% block footer %}{% endblock %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedheader/3.2.0/css/fixedHeader.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.6.4/css/keyTable.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/searchbuilder/1.3.0/css/searchBuilder.dataTables.min.css"/>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% block datatable_scripts %}
 | 
			
		||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script>
 | 
			
		||||
@@ -23,5 +24,5 @@
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/keytable/2.6.4/js/dataTables.keyTable.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/responsive.bootstrap5.js"></script>
 | 
			
		||||
    {% block custom_data_script %}{% endblock %}
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/searchbuilder/1.3.0/js/dataTables.searchBuilder.min.js"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										175
									
								
								ref-test/admin/templates/admin/result-detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								ref-test/admin/templates/admin/result-detail.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
{% extends "admin/components/base.html" %}
 | 
			
		||||
{% block title %} SKA Referee Test | Detailed Results {% endblock %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    {% include "admin/components/client-alerts.html" %}
 | 
			
		||||
    <h1>Exam Results</h1>
 | 
			
		||||
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <div class="row justify-content-center">
 | 
			
		||||
            <div class="col-lg-6">          
 | 
			
		||||
                <ul class="list-group">
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Candidate</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <h2>
 | 
			
		||||
                            {{ entry.name.surname}}, {{ entry.name.first_name }}
 | 
			
		||||
                        </h2>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Email Address</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.email }}
 | 
			
		||||
                    </li>
 | 
			
		||||
                    {% if entry['club'] %}
 | 
			
		||||
                        <li class="list-group-item list-group-item-action">
 | 
			
		||||
                            <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                                <h5 class="mb-1">Club</h5>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            {{ entry.club }}
 | 
			
		||||
                        </li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Exam Code</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ '—'.join([entry.test_code[:4], entry.test_code[4:8], entry.test_code[8:]]) }}
 | 
			
		||||
                    </li>
 | 
			
		||||
                    {% if entry['user_code'] %}
 | 
			
		||||
                        <li class="list-group-item list-group-item-action">
 | 
			
		||||
                            <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                                <h5 class="mb-1">User Code</h5>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            {{ entry.user_code }}
 | 
			
		||||
                        </li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Start Time</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.start_time.strftime('%d %b %Y %H:%M:%S') }}
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Submission Time</h5>
 | 
			
		||||
                            {% if entry.status == 'late' %}
 | 
			
		||||
                                <span class="badge bg-danger">Late</span>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.submission_time.strftime('%d %b %Y %H:%M:%S') }}
 | 
			
		||||
                    </li>       
 | 
			
		||||
                    <li class="list-group-item list-group-item-action">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Score</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.results.score }}%
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="list-group-item list-group-item-action {% if entry.results.grade == 'fail' %}list-group-item-danger {% elif entry.results.grade == 'merit' %} list-group-item-success {% endif %}">
 | 
			
		||||
                        <div class="d-flex w-100 justify-content-between">
 | 
			
		||||
                            <h5 class="mb-1">Grade</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {{ entry.results.grade[0]|upper }}{{ entry.results.grade[1:]}}
 | 
			
		||||
                    </li>
 | 
			
		||||
                </ul>
 | 
			
		||||
                <div class="accordion" id="results-breakdown">
 | 
			
		||||
                    <div class="accordion-item">
 | 
			
		||||
                        <h2 class="accordion-header" id="by-category">
 | 
			
		||||
                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#by-category-breakdown" aria-expanded="false" aria-controls="by-category-breakdown">
 | 
			
		||||
                                Score By Categories
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </h2>
 | 
			
		||||
                        <div id="by-category-breakdown" class="accordion-collapse collapse" aria-labelledby="by-category" data-bs-parent="#results-breakdown">
 | 
			
		||||
                            <div class="accordion-body">
 | 
			
		||||
                                <table class="table table-striped">
 | 
			
		||||
                                    <thead>
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <th>
 | 
			
		||||
                                                Category
 | 
			
		||||
                                            </th>
 | 
			
		||||
                                            <th>
 | 
			
		||||
                                                Score
 | 
			
		||||
                                            </th>
 | 
			
		||||
                                            <th>
 | 
			
		||||
                                                Max
 | 
			
		||||
                                            </th>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                    </thead>
 | 
			
		||||
                                    <tbody>
 | 
			
		||||
                                        {% for tag, scores in entry.results.tags.items() %}
 | 
			
		||||
                                            <tr>
 | 
			
		||||
                                                <td>
 | 
			
		||||
                                                    {{ tag }}
 | 
			
		||||
                                                </td>
 | 
			
		||||
                                                <td>
 | 
			
		||||
                                                    {{ scores.scored }}
 | 
			
		||||
                                                </td>
 | 
			
		||||
                                                <td>
 | 
			
		||||
                                                    {{scores.max}}
 | 
			
		||||
                                                </td>
 | 
			
		||||
                                            </tr>
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                    </tbody>
 | 
			
		||||
                                </table>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="accordion-item">
 | 
			
		||||
                        <h2 class="accordion-header" id="by-question">
 | 
			
		||||
                            <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#by-question-breakdown" aria-expanded="false" aria-controls="by-question-breakdown">
 | 
			
		||||
                                View All Answers
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </h2>
 | 
			
		||||
                    <div id="by-question-breakdown" class="accordion-collapse collapse" aria-labelledby="by-question" data-bs-parent="#results-breakdown">
 | 
			
		||||
                        <div class="accordion-body">
 | 
			
		||||
                            <table class="table table-striped">
 | 
			
		||||
                                    <thead>
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <th>
 | 
			
		||||
                                                Question
 | 
			
		||||
                                            </th>
 | 
			
		||||
                                            <th>
 | 
			
		||||
                                                Answer
 | 
			
		||||
                                            </th>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                    </thead>
 | 
			
		||||
                                    <tbody>
 | 
			
		||||
                                        {% for question, answer in entry.answers.items() %}
 | 
			
		||||
                                            <tr>
 | 
			
		||||
                                                <td>
 | 
			
		||||
                                                    {{ question }}
 | 
			
		||||
                                                </td>
 | 
			
		||||
                                                <td>
 | 
			
		||||
                                                    {{ answer }}
 | 
			
		||||
                                                </td>
 | 
			
		||||
                                            </tr>
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                    </tbody>
 | 
			
		||||
                            </table>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="container justify-content-center">
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <a href="#" class="btn btn-primary result-action-buttons" data-result-action="generate" data-_id="{{ entry._id }}">
 | 
			
		||||
                            <i class="bi bi-printer-fill button-icon"></i>
 | 
			
		||||
                            Printable Version
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                            {% if entry.status == 'late' %}
 | 
			
		||||
                                <a href="#" class="btn btn-warning result-action-buttons" data-result-action="override" data-_id="{{ entry._id }}">
 | 
			
		||||
                                    <i class="bi bi-clock-history button-icon"></i>
 | 
			
		||||
                                    Allow Late Entry
 | 
			
		||||
                                </a>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        <a href="#" class="btn btn-danger result-action-buttons" data-result-action="delete" data-_id="{{ entry._id }}">
 | 
			
		||||
                            <i class="bi bi-trash-fill button-icon"></i>
 | 
			
		||||
                            Delete Result
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -1 +1,138 @@
 | 
			
		||||
{% extends "admin/components/base.html" %}
 | 
			
		||||
{% extends "admin/components/datatable.html" %}
 | 
			
		||||
{% block title %} SKA Referee Test | View Results {% endblock %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    {% include "admin/components/client-alerts.html" %}
 | 
			
		||||
    <h1>View Results</h1>
 | 
			
		||||
    {% if entries %}
 | 
			
		||||
        <table id="results-table" class="table table-striped" style="width:100%">
 | 
			
		||||
            <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th data-priority="1">
 | 
			
		||||
                        Name
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="4">
 | 
			
		||||
                        Club
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="5">
 | 
			
		||||
                        Exam Code
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="3">
 | 
			
		||||
                        Status
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="4">
 | 
			
		||||
                        Submitted
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="2">
 | 
			
		||||
                        Result
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="3">
 | 
			
		||||
                        Grade
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="1">
 | 
			
		||||
                        Details
 | 
			
		||||
                    </th>
 | 
			
		||||
                </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
                {% for entry in entries %}
 | 
			
		||||
                    <tr class="table-row">
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {{ entry.name.surname }}, {{ entry.name.first_name }}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {% if 'club' in entry %}
 | 
			
		||||
                                {{ entry.club }}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {{ '—'.join([entry.test_code[:4], entry.test_code[4:8], entry.test_code[8:]]) }}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {% if 'status' in entry %}
 | 
			
		||||
                                {{ entry.status }}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {% if 'submission_time' in entry %}
 | 
			
		||||
                                {{ entry.submission_time.strftime('%d %b %Y') }}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {% if 'results' in entry %}
 | 
			
		||||
                                {{ entry.results.score }}%
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {% if 'results' in entry %}
 | 
			
		||||
                                {{ entry.results.grade }}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td class="row-actions">
 | 
			
		||||
                            <a
 | 
			
		||||
                                href="{{ url_for('admin_views.view_entry', _id = entry._id ) }}"
 | 
			
		||||
                                class="btn btn-primary entry-details"
 | 
			
		||||
                                data-_id="{{entry._id}}"
 | 
			
		||||
                                title="View Details"
 | 
			
		||||
                            >
 | 
			
		||||
                                <i class="bi bi-file-medical-fill button-icon"></i>
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
            </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
    {% else %}
 | 
			
		||||
            <div class="alert alert-primary alert-db-empty">
 | 
			
		||||
                <i class="bi bi-info-circle-fill" aria-title="Alert" title="Alert"></i>
 | 
			
		||||
                There are no exam attempts to view.
 | 
			
		||||
            </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% if entries %}
 | 
			
		||||
    {% block custom_data_script %}
 | 
			
		||||
        <script>
 | 
			
		||||
            $(document).ready(function() {
 | 
			
		||||
                $('#results-table').DataTable({
 | 
			
		||||
                    'searching': false,
 | 
			
		||||
                    'columnDefs': [
 | 
			
		||||
                        {'sortable': false, 'targets': [7]},
 | 
			
		||||
                        {'searchable': false, 'targets': [7]}
 | 
			
		||||
                    ],
 | 
			
		||||
                    'order': [[4, 'desc'], [0, 'asc']],
 | 
			
		||||
                    'buttons': [
 | 
			
		||||
                        {
 | 
			
		||||
                            extend: 'print',
 | 
			
		||||
                            exportOptions: {
 | 
			
		||||
                                columns: [0, 1, 3, 4, 5, 6]
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            extend: 'excel',
 | 
			
		||||
                            exportOptions: {
 | 
			
		||||
                                columns: [0, 1, 3, 4, 5, 6]
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            extend: 'pdf',
 | 
			
		||||
                            exportOptions: {
 | 
			
		||||
                                columns: [0, 1, 3, 4, 5, 6]
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    'responsive': 'true',
 | 
			
		||||
                    'colReorder': 'true',
 | 
			
		||||
                    'fixedHeader': 'true',
 | 
			
		||||
                    'searchBuilder': {
 | 
			
		||||
                        depthLimit: 2,
 | 
			
		||||
                        columns: [1, 5, 6],
 | 
			
		||||
                    },
 | 
			
		||||
                    dom: 'BQlfrtip'
 | 
			
		||||
                });
 | 
			
		||||
                // $('.buttons-pdf').html('<span class="glyphicon glyphicon-file" data-toggle="tooltip" title="Export To Excel"/>') -->
 | 
			
		||||
            } );
 | 
			
		||||
            $('#results-table').show();
 | 
			
		||||
            $(window).trigger('resize');
 | 
			
		||||
        </script>
 | 
			
		||||
    {% endblock %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
                        Author
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="3">
 | 
			
		||||
                        Use
 | 
			
		||||
                        Exams
 | 
			
		||||
                    </th>
 | 
			
		||||
                    <th data-priority="1">
 | 
			
		||||
                        Actions
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
{% extends "admin/components/datatable.html" %}
 | 
			
		||||
{% block title %} SKA Referee Test | Manage Exams {% endblock %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    {% include "admin/components/client-alerts.html" %}
 | 
			
		||||
    <h1>Manage Exams</h1>
 | 
			
		||||
    {% include "admin/components/secondary-navs/tests.html" %}
 | 
			
		||||
    <h2>{{ display_title }}</h2>
 | 
			
		||||
@@ -157,7 +158,7 @@
 | 
			
		||||
            });
 | 
			
		||||
            // $('.buttons-pdf').html('<span class="glyphicon glyphicon-file" data-toggle="tooltip" title="Export To Excel"/>') -->
 | 
			
		||||
        } );
 | 
			
		||||
        $('#test-table').show();
 | 
			
		||||
        $('#active-test-table').show();
 | 
			
		||||
        $(window).trigger('resize');
 | 
			
		||||
    </script>
 | 
			
		||||
    {% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from flask import Blueprint, render_template, flash, redirect, request, jsonify, abort
 | 
			
		||||
from flask import Blueprint, render_template, flash, redirect, request, jsonify, abort, make_response
 | 
			
		||||
from flask.helpers import url_for
 | 
			
		||||
from functools import wraps
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
@@ -14,7 +14,7 @@ from main import app, db
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
import secrets
 | 
			
		||||
from main import mail
 | 
			
		||||
from datetime import datetime, date, timedelta
 | 
			
		||||
from datetime import datetime, date
 | 
			
		||||
from .models.tests import Test
 | 
			
		||||
from common.data_tools import get_default_dataset, get_time_options, available_datasets
 | 
			
		||||
 | 
			
		||||
@@ -396,14 +396,65 @@ def create_test():
 | 
			
		||||
        return jsonify({ 'error': errors}), 400
 | 
			
		||||
 | 
			
		||||
@views.route('/tests/delete/', methods=['POST'])
 | 
			
		||||
@admin_account_required
 | 
			
		||||
@login_required
 | 
			
		||||
def delete_test():
 | 
			
		||||
    _id = request.get_json()['_id']
 | 
			
		||||
    if db.tests.find_one({'_id': _id}):
 | 
			
		||||
        return Test(_id = _id).delete()
 | 
			
		||||
    return jsonify({'error': 'Could not find the corresponding test to delete.'}), 404
 | 
			
		||||
 | 
			
		||||
@views.route('/test/<id>/', methods=['GET','POST'])
 | 
			
		||||
@views.route('/test/<_id>/', methods=['GET','POST'])
 | 
			
		||||
@admin_account_required
 | 
			
		||||
@login_required
 | 
			
		||||
def view_test(_id, filter=''):
 | 
			
		||||
    test = db.tests.find_one({'_id':_id})
 | 
			
		||||
    if not test:
 | 
			
		||||
        return abort(404)
 | 
			
		||||
        return abort(404)
 | 
			
		||||
 | 
			
		||||
@views.route('/results/')
 | 
			
		||||
@admin_account_required
 | 
			
		||||
@login_required
 | 
			
		||||
def view_entries():
 | 
			
		||||
    entries = decrypt_find(db.entries, {})
 | 
			
		||||
    return render_template('/admin/results.html', entries = entries)
 | 
			
		||||
 | 
			
		||||
@views.route('/results/<_id>/', methods = ['GET', 'POST'])
 | 
			
		||||
@admin_account_required
 | 
			
		||||
@login_required
 | 
			
		||||
def view_entry(_id=''):
 | 
			
		||||
    entry = decrypt_find_one(db.entries, {'_id': _id})
 | 
			
		||||
    if request.method == 'GET':
 | 
			
		||||
        if not entry:
 | 
			
		||||
            return abort(404)
 | 
			
		||||
        return render_template('/admin/result-detail.html', entry = entry)
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        if not entry:
 | 
			
		||||
            return jsonify({'error': 'A valid entry could no be found.'}), 404
 | 
			
		||||
        action = request.get_json()['action']
 | 
			
		||||
        if action == 'override':
 | 
			
		||||
            late_ignore = db.entries.find_one_and_update({'_id': _id}, {'$set': {'status': 'late (allowed)'}})
 | 
			
		||||
            if late_ignore:
 | 
			
		||||
                flash('Late status for the entry has been allowed.', 'success')
 | 
			
		||||
                return jsonify({'success': 'Late status allowed.'}), 200
 | 
			
		||||
            return jsonify({'error': 'An error occurred.'}), 400
 | 
			
		||||
        if action == 'delete':
 | 
			
		||||
            test_code = entry['test_code']
 | 
			
		||||
            test = db.tests.find_one_and_update({'test_code': test_code}, {'$pull': {'entries': _id}})
 | 
			
		||||
            if not test:
 | 
			
		||||
                return jsonify({'error': 'A valid exam could not be found.'}), 404
 | 
			
		||||
            delete = db.entries.delete_one({'_id': _id})
 | 
			
		||||
            if delete:
 | 
			
		||||
                flash('Entry has been deleted.', 'success')
 | 
			
		||||
                return jsonify({'success': 'Entry has been deleted.'}), 200
 | 
			
		||||
            return jsonify({'error': 'An error occurred.'}), 400
 | 
			
		||||
 | 
			
		||||
@views.route('/certificate/', methods=['POST'])
 | 
			
		||||
@admin_account_required
 | 
			
		||||
@login_required
 | 
			
		||||
def generate_certificate():
 | 
			
		||||
    _id = request.get_json()['_id']
 | 
			
		||||
    entry = decrypt_find_one(db.entries, {'_id': _id})
 | 
			
		||||
    if not entry:
 | 
			
		||||
        return abort(404)
 | 
			
		||||
    return render_template('/admin/components/certificate.html', entry = entry)
 | 
			
		||||
		Reference in New Issue
	
	Block a user