Skip to content

Commit

Permalink
Make course attendee editable after certificate is issued (#1438)
Browse files Browse the repository at this point in the history
* Make course attendee editable after certificate is issued

* Fix lint error

* Add test
  • Loading branch information
dimasciput authored Aug 19, 2022
1 parent 2e2b42f commit b9fc0ce
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 7 deletions.
1 change: 1 addition & 0 deletions django_project/certification/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class CertificateAdmin(admin.ModelAdmin):

list_display = ('certificateID', 'course')
search_fields = ('certificateID', 'course__name',)
readonly_fields = ('issue_date',)

def queryset(self, request):
"""Ensure we use the correct manager.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-06-05 04:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('certification', '0022_externalreviewer_name'),
]

operations = [
migrations.AddField(
model_name='certificate',
name='issue_date',
field=models.DateField(auto_now_add=True, null=True),
),
]
19 changes: 19 additions & 0 deletions django_project/certification/migrations/0024_auto_20220605_0612.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.13 on 2022-06-05 04:12

from django.db import migrations


def set_issue_date_to_null(apps, schema_editor):
Ceritificate = apps.get_model('certification', 'Certificate')
Ceritificate.objects.all().update(issue_date=None)


class Migration(migrations.Migration):

dependencies = [
('certification', '0023_certificate_issue_date'),
]

operations = [
migrations.RunPython(set_issue_date_to_null)
]
6 changes: 6 additions & 0 deletions django_project/certification/models/certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ class Certificate(models.Model):
default=False
)

issue_date = models.DateField(
auto_now_add=True,
blank=True,
null=True
)

author = models.ForeignKey(User, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
attendee = models.ForeignKey(Attendee, on_delete=models.CASCADE)
Expand Down
4 changes: 2 additions & 2 deletions django_project/certification/templates/course/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ <h4 class="panel-heading" style="padding-left:10px; padding-right: 10px">
{% if user in course.certifying_organisation.organisation_owners.all or user.is_staff or user == course.course_convener.user or user == project.owner or user in course.certifying_organisation.project.certification_managers.all %}
<td style="text-align: center">
<div class="btn-group pull-right">
{% if course.editable %}
{% if attendee.editable %}
<a class="btn btn-default btn-xs btn-print tooltip-toggle"
href='{% url "attendee-update" project_slug=course.certifying_organisation.project.slug organisation_slug=course.certifying_organisation.slug course_slug=course.slug pk=attendee.attendee.pk %}'
data-title="Edit Attendee">
Expand All @@ -238,7 +238,7 @@ <h4 class="panel-heading" style="padding-left:10px; padding-right: 10px">
{% else %}
<a class="btn btn-default btn-xs btn-print tooltip-toggle" disabled
href='#'
data-title="Edit attendee is only available 7 days after the completion of the course.">
data-title="Edit attendee is only available 7 days after the certificate is issued.">
<span class="glyphicon glyphicon-pencil"></span>
</a>
{% endif %}
Expand Down
52 changes: 52 additions & 0 deletions django_project/certification/tests/views/test_attendee_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,63 @@
CourseF,
CourseConvenerF,
CourseTypeF,
AttendeeF,
CertificateF
)
from core.model_factories import UserF
import logging


class TestAttendeeView(TestCase):

@override_settings(VALID_DOMAIN=['testserver', ])
def setUp(self) -> None:
self.client = Client()
self.client.post(
'/set_language/', data={'language': 'en'})
self.project = ProjectF.create()
self.certifying_organisation = CertifyingOrganisationF.create(
project=self.project)
self.training_center = TrainingCenterF.create(
certifying_organisation=self.certifying_organisation)
self.course_convener = CourseConvenerF.create(
certifying_organisation=self.certifying_organisation)
self.course_type = CourseTypeF.create(
certifying_organisation=self.certifying_organisation)
self.course = CourseF.create(
certifying_organisation=self.certifying_organisation,
training_center=self.training_center,
course_convener=self.course_convener,
course_type=self.course_type
)
self.user = UserF.create(**{
'username': 'user',
'password': 'password',
'is_staff': True
})
self.user.set_password('password')
self.user.save()
self.attendee = AttendeeF.create(
certifying_organisation=self.certifying_organisation)
self.certificate = CertificateF.create(
course=self.course,
attendee=self.attendee,
author=self.user)

@override_settings(VALID_DOMAIN=['testserver', ])
def test_AttendeeUpdateView_with_login(self):
status = self.client.login(username='user', password='password')
self.assertTrue(status)
url = reverse('attendee-update', kwargs={
'project_slug': self.project.slug,
'organisation_slug': self.certifying_organisation.slug,
'course_slug': self.course.slug,
'pk': self.attendee.pk
})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)


class TestCourseAttendeeView(TestCase):
"""Tests that attendee and course attendee view works."""

Expand Down
32 changes: 31 additions & 1 deletion django_project/certification/tests/views/test_course_view.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# coding=utf-8
import datetime
import logging
from bs4 import BeautifulSoup as Soup

Expand All @@ -12,7 +13,7 @@
CertificateTypeF,
ProjectCertificateTypeF,
CourseF,
CourseConvenerF
CourseConvenerF, AttendeeF, CertificateF, CourseAttendeeF
)


Expand Down Expand Up @@ -86,12 +87,41 @@ def test_create_course_must_showing_CertificateTypes(self):
@override_settings(VALID_DOMAIN=['testserver', ])
def test_detail_view(self):
client = Client()
attendee = AttendeeF.create()
CourseAttendeeF.create(
attendee=attendee,
course=self.course
)
CertificateF.create(
attendee=attendee,
course=self.course,
issue_date=datetime.datetime.now()
)

old_attendee = AttendeeF.create()
CourseAttendeeF.create(
attendee=old_attendee,
course=self.course
)
cert = CertificateF.create(
attendee=old_attendee,
course=self.course
)
cert.issue_date = datetime.datetime.now() - datetime.timedelta(days=7)
cert.save()

response = client.get(reverse('course-detail', kwargs={
'project_slug': self.project.slug,
'organisation_slug': self.certifying_organisation.slug,
'slug': self.course.slug
}))
self.assertEqual(response.status_code, 200)
course_attendees = response.context_data['attendees']
for course_attendee in course_attendees:
if course_attendee.attendee_id == attendee.id:
self.assertTrue(course_attendee.editable)
else:
self.assertFalse(course_attendee.editable)

@override_settings(VALID_DOMAIN=['testserver', ])
def test_detail_with_duplicates(self):
Expand Down
17 changes: 14 additions & 3 deletions django_project/certification/views/attendee.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# coding=utf-8
import io
import csv
from datetime import timedelta, datetime

from django.db import transaction
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import (
CreateView, FormView, UpdateView)
from braces.views import LoginRequiredMixin, FormMessagesMixin
from certification.models import (
Attendee, CertifyingOrganisation, CourseAttendee, Course
Attendee, CertifyingOrganisation, CourseAttendee, Course, Certificate
)
from certification.forms import (
AttendeeForm, CsvAttendeeForm, UpdateAttendeeForm)
Expand Down Expand Up @@ -304,6 +306,15 @@ def get_form_kwargs(self):
def get(self, request, *args, **kwargs):
self.course_slug = self.kwargs.get('course_slug', None)
course = Course.objects.get(slug=self.course_slug)
if not course.editable:
return HttpResponseForbidden('Course is not editable.')
certificate = Certificate.objects.filter(
course=course,
attendee=self.get_object()
).first()
if certificate:
if (
not certificate.issue_date or
certificate.issue_date +
timedelta(days=7) <= datetime.today().date()
):
return HttpResponseForbidden('Course is not editable.')
return super(AttendeeUpdateView, self).get(request, *args, **kwargs)
22 changes: 21 additions & 1 deletion django_project/certification/views/course.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# coding=utf-8
from datetime import timedelta, datetime

from django.urls import reverse
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponseRedirect
Expand Down Expand Up @@ -457,8 +459,26 @@ def get_context_data(self, **kwargs):
self.course = Course.objects.get(slug=self.slug)
context = super(
CourseDetailView, self).get_context_data(**kwargs)
context['attendees'] = \

attendees = (
CourseAttendee.objects.filter(course=self.course)
)
for course_attendee in attendees:
course_attendee.editable = False
certificate = Certificate.objects.filter(
course=self.course,
attendee=course_attendee.attendee
).first()
if certificate:
course_attendee.editable = (
certificate.issue_date and
certificate.issue_date +
timedelta(days=7) > datetime.today().date()
)
else:
course_attendee.editable = True
context['attendees'] = attendees

context['certificates'] = dict(
Certificate.objects.filter(
course=self.course
Expand Down

0 comments on commit b9fc0ce

Please sign in to comment.