From c77e25c0e4303a62b3f5ab183040cc1af721c069 Mon Sep 17 00:00:00 2001 From: suman Date: Mon, 17 May 2021 10:57:54 +0800 Subject: [PATCH 01/27] extract zipfile inside zipfile in sample data lesson --- .../lesson/templates/worksheet/detail.html | 2 +- django_project/lesson/urls.py | 7 +++ django_project/lesson/views/worksheet.py | 48 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/django_project/lesson/templates/worksheet/detail.html b/django_project/lesson/templates/worksheet/detail.html index a2b9592d3..6575fd323 100644 --- a/django_project/lesson/templates/worksheet/detail.html +++ b/django_project/lesson/templates/worksheet/detail.html @@ -558,7 +558,7 @@

{% if worksheet.external_data %}

- {% blocktrans with url=worksheet.external_data.url %}Download the sample data for the lesson.{% endblocktrans %} + {% blocktrans %}Download the sample data for the lesson.{% endblocktrans %}

{% elif not worksheet.published %}

diff --git a/django_project/lesson/urls.py b/django_project/lesson/urls.py index e49bdf171..28d442908 100644 --- a/django_project/lesson/urls.py +++ b/django_project/lesson/urls.py @@ -39,6 +39,7 @@ WorksheetUpdateView, WorksheetDeleteView, WorksheetDetailView, + WorksheetDownloadSampleDataView, WorksheetPrintView, WorksheetOrderView, WorksheetOrderSubmitView, @@ -221,6 +222,12 @@ view=AnswerDeleteView.as_view(), name='answer-delete'), + # download sample data + url(regex='^(?P[\w-]+)/lesson/' + '(?P[\w-]+)/sample-data/(?P[\w-]+)/$', + view=WorksheetDownloadSampleDataView.as_view(), + name='worksheet-sampledata'), + # Json invalid Further reading Link url(regex='(?P[\w-]+)/lessons/invalid_further_reading/$', view=get_invalid_FurtherReading_links, diff --git a/django_project/lesson/views/worksheet.py b/django_project/lesson/views/worksheet.py index 3eedb0be4..a175fc6f6 100644 --- a/django_project/lesson/views/worksheet.py +++ b/django_project/lesson/views/worksheet.py @@ -218,6 +218,54 @@ def render_to_response(self, context, **response_kwargs): return zip_response +class WorksheetDownloadSampleDataView(WorksheetDetailView): + def render_to_response(self, context, **response_kwargs): + context = self.get_context_data() + file_title = (f"{context['worksheet'].section.name}" + f"-{context['worksheet'].module}") + s = BytesIO() + zf = zipfile.ZipFile(s, "w") + + if context['worksheet'].external_data: + data_path = context['worksheet'].external_data.url + zip_data_path = settings.MEDIA_ROOT + data_path[6:] + + try: + external_file_zf = zipfile.ZipFile(zip_data_path) + for name in external_file_zf.namelist(): + if name.endswith('.zip'): + try: + filebytes = BytesIO(external_file_zf.read(name)) + subzip = zipfile.ZipFile(filebytes) + for name_subzip in subzip.namelist(): + if name_subzip.endswith('/'): + continue + if name_subzip.startswith('__MACOSX'): + continue + f_subzip = subzip.read(name_subzip) + zf.writestr(name_subzip, f_subzip) + except Exception: + pass + else: + if name.endswith('/'): + continue + if name.startswith('__MACOSX'): + continue + f = external_file_zf.read(name) + zf.writestr(name, f) + + except Exception: + zf.write(zip_data_path, file_title) + + zf.close() + + zip_response = HttpResponse( + s.getvalue(), content_type="application/x-zip-compressed") + zip_response['Content-Disposition'] = \ + 'attachment; filename={}.zip'.format(file_title) + return zip_response + + class WorksheetCreateView(LoginRequiredMixin, WorksheetMixin, CreateView): """Create view for Section.""" From d09aeb5eafbe2acec057349fa096dc31a21af51c Mon Sep 17 00:00:00 2001 From: suman Date: Mon, 17 May 2021 12:19:08 +0800 Subject: [PATCH 02/27] update unit test for download sample data lesson --- .../lesson/tests/test_worksheet_views.py | 58 +++++++++++++++++++ django_project/lesson/views/worksheet.py | 4 +- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/django_project/lesson/tests/test_worksheet_views.py b/django_project/lesson/tests/test_worksheet_views.py index da7f13322..1e4edcbfd 100644 --- a/django_project/lesson/tests/test_worksheet_views.py +++ b/django_project/lesson/tests/test_worksheet_views.py @@ -73,6 +73,38 @@ def setUp(self): ) self.image_uploaded = SimpleUploadedFile( 'gif.gif', gif_byte, content_type='image/gif') + # Create zipfile in zipfile + # inside zipfile : test_1.txt and test_inside_zip.zip + # inside test_inside_zip.zip: test_inside_zip.txt + zip_byte = ( + b'PK\x03\x04\x14\x00\x08\x00\x08\x00H]\xb1R\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x02\x00\x00\x00\n\x00 \x00test_1.txtUT\r\x00\x07' + b'\x19\xe6\xa1`\x19\xe6\xa1`\x19\xe6\xa1`ux\x0b\x00\x01\x04\xe8' + b'\x03\x00\x00\x04\xe8\x03\x00\x003\xe4\x02\x00PK\x07\x08S\xfcQg' + b'\x04\x00\x00\x00\x02\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08' + b'\x00Y]\xb1R\x00\x00\x00\x00\x00\x00\x00\x00\xdc\x00\x00\x00\x13' + b'\x00 \x00test_inside_zip.zipUT\r\x00\x07;\xe6\xa1`;\xe6\xa1`;' + b'\xe6\xa1`ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00' + b'\x0b\xf0ff\x11a\xe0\x00\xc2\x90\xd8\x8dA\x0cP\xc0\x04\xc4\xc2' + b'\x0c\n\x0c%\xa9\xc5%\xf1\x99y\xc5\x99)\xa9\xf1U\x99\x05z%\x15%' + b'\xa1!\xbc\x0c\xec\x86\xcf\x16&\xc0pi\x057\x03#\xcb\x0bf\x06' + b'\x060a\xfc\x88\x89!\xc0\x9b\x9dc\xc2\xfa\x1a\x1f\x16\xa8Q\x01' + b'\xde\x8cL"\xcc\x08k\x90\xe5@\xd6\xc0\xc0\x96F\x10I\x86\xa5\x01' + b'\xde\xacl \xad\x8c@\x98\x08\xa4S\xc1\xc6\x01\x00PK\x07\x08h\x06' + b'\x07\x1as\x00\x00\x00\xdc\x00\x00\x00PK\x01\x02\x14\x03\x14\x00' + b'\x08\x00\x08\x00H]\xb1RS\xfcQg\x04\x00\x00\x00\x02\x00\x00\x00' + b'\n\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\x00\x00\x00' + b'\x00test_1.txtUT\r\x00\x07\x19\xe6\xa1`\x19\xe6\xa1`\x19\xe6' + b'\xa1`ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00' + b'PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00Y]\xb1Rh\x06\x07\x1as' + b'\x00\x00\x00\xdc\x00\x00\x00\x13\x00 \x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\xb4\x81\\\x00\x00\x00test_inside_zip.zipUT\r\x00' + b'\x07;\xe6\xa1`;\xe6\xa1`;\xe6\xa1`ux\x0b\x00\x01\x04\xe8\x03\x00' + b'\x00\x04\xe8\x03\x00\x00PK\x05\x06\x00\x00\x00\x00\x02\x00\x02' + b'\x00\xb9\x00\x00\x000\x01\x00\x00\x00\x00' + ) + self.zip_uploaded = SimpleUploadedFile( + 'ziptest.zip', zip_byte, content_type='application/zip') @override_settings(VALID_DOMAIN = ['testserver', ]) def test_WorksheetCreateView(self): @@ -248,6 +280,32 @@ def test_WorksheetPDFZipView(self): zip_file.close() + @override_settings(VALID_DOMAIN=['testserver']) + def test_WorksheetDownloadSampledataView(self): + self.test_section.name = 'Test section zip' + self.test_section.save() + self.test_worksheet.summary_image = self.image_uploaded + self.test_worksheet.more_about_image = self.image_uploaded + self.test_worksheet.module = 'Test module zip' + self.test_worksheet.external_data = self.zip_uploaded + self.test_worksheet.save() + response = self.client.get(reverse( + 'worksheet-sampledata', kwargs=self.kwargs_worksheet_full)) + self.assertEqual(response.status_code, 200) + self.assertEquals( + response.get('Content-Disposition'), + 'attachment; filename=Test section zip-Test module zip.zip' + ) + with io.BytesIO(response.content) as file: + zip_file = zipfile.ZipFile(file, 'r') + self.assertIsNone(zip_file.testzip()) + # zipfile must not contain any zipfile + self.assertEqual( + ['test_1.txt', 'test_inside_zip.txt'], + zip_file.namelist()) + zip_file.close() + + @override_settings(VALID_DOMAIN=['testserver']) def test_download_multiple_worksheets(self): self.test_project.name = 'Test project name multiple zip' diff --git a/django_project/lesson/views/worksheet.py b/django_project/lesson/views/worksheet.py index a175fc6f6..a006c31a8 100644 --- a/django_project/lesson/views/worksheet.py +++ b/django_project/lesson/views/worksheet.py @@ -8,7 +8,7 @@ from collections import OrderedDict from django.conf import settings from django.urls import reverse -from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.views.generic import ( DetailView, CreateView, @@ -264,6 +264,8 @@ def render_to_response(self, context, **response_kwargs): zip_response['Content-Disposition'] = \ 'attachment; filename={}.zip'.format(file_title) return zip_response + else: + raise Http404("Sample data does not exist") class WorksheetCreateView(LoginRequiredMixin, WorksheetMixin, CreateView): From 0092fff2f4f86694fe203c3f543412c638577281 Mon Sep 17 00:00:00 2001 From: suman Date: Mon, 17 May 2021 16:00:29 +0800 Subject: [PATCH 03/27] update unit test --- django_project/lesson/tests/test_worksheet_views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django_project/lesson/tests/test_worksheet_views.py b/django_project/lesson/tests/test_worksheet_views.py index 1e4edcbfd..d28f7ebf9 100644 --- a/django_project/lesson/tests/test_worksheet_views.py +++ b/django_project/lesson/tests/test_worksheet_views.py @@ -287,6 +287,12 @@ def test_WorksheetDownloadSampledataView(self): self.test_worksheet.summary_image = self.image_uploaded self.test_worksheet.more_about_image = self.image_uploaded self.test_worksheet.module = 'Test module zip' + self.test_worksheet.save() + + response = self.client.get(reverse( + 'worksheet-sampledata', kwargs=self.kwargs_worksheet_full)) + self.assertEqual(response.status_code, 404) + self.test_worksheet.external_data = self.zip_uploaded self.test_worksheet.save() response = self.client.get(reverse( From 7de9b96aec92986c1086c39b33d7f28c0d14a5a0 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Wed, 24 Nov 2021 01:24:25 +0800 Subject: [PATCH 04/27] fixed dockerfile for development and the documentation --- README-dev.md | 3 ++- deployment/docker/Dockerfile | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README-dev.md b/README-dev.md index da2398322..8e16bb4bb 100644 --- a/README-dev.md +++ b/README-dev.md @@ -33,9 +33,9 @@ When it's done, you can continue with this command: Linux and MacOS: ``` +cp docker-compose.override.example.yml docker-compose.override.yml make build-devweb make devweb -cp docker-compose.override.example.yml docker-compose.override.yml ``` In case you don't get some not installed packages, you can run this @@ -54,6 +54,7 @@ repeatable steps: Windows: ``` +copy docker-compose.override.example.yml docker-compose.override.yml make-devbuild.bat make-devweb.bat ``` diff --git a/deployment/docker/Dockerfile b/deployment/docker/Dockerfile index d02260a7e..5e0e5c5cc 100644 --- a/deployment/docker/Dockerfile +++ b/deployment/docker/Dockerfile @@ -6,7 +6,7 @@ MAINTAINER Tim Sutton #RUN ln -s /bin/true /sbin/initctl # Pandoc needed to generate rst dumps, uic compressor needed for django-pipeline -RUN apt-get update -y; apt-get -y --force-yes install yui-compressor gettext +RUN apt-get update -y && apt-get -y --force-yes install yui-compressor gettext RUN wget https://github.com/jgm/pandoc/releases/download/1.17.1/pandoc-1.17.1-2-amd64.deb RUN dpkg -i pandoc-1.17.1-2-amd64.deb && rm pandoc-1.17.1-2-amd64.deb @@ -18,7 +18,7 @@ ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 ADD deployment/docker/REQUIREMENTS.txt /REQUIREMENTS.txt ADD deployment/docker/uwsgi.conf /uwsgi.conf ADD django_project /home/web/django_project -RUN pip install -r /REQUIREMENTS.txt +RUN pip install --upgrade pip && pip install -r /REQUIREMENTS.txt RUN pip install uwsgi @@ -50,7 +50,7 @@ RUN echo "export VISIBLE=now" >> /etc/profile # End of cut & paste section ADD deployment/docker/REQUIREMENTS-dev.txt /REQUIREMENTS-dev.txt -RUN pip install -r /REQUIREMENTS-dev.txt +RUN pip install --upgrade pip && pip install -r /REQUIREMENTS-dev.txt ADD deployment/docker/bashrc /root/.bashrc # -------------------------------------------------------- From a35044f36f872dbe6f9e476362596ee6d8cee3a0 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 26 Nov 2021 12:56:43 +0800 Subject: [PATCH 05/27] modified delete convener mechanism --- django_project/certification/forms.py | 2 + .../0007_courseconvener_is_active.py | 18 +++++++ .../certification/models/course_convener.py | 6 +++ .../certifying_organisation/detail.html | 32 ++---------- .../includes/convenor_list.html | 50 +++++++++++++++++++ .../certification/tests/test_models.py | 1 + .../views/certifying_organisation.py | 2 +- 7 files changed, 82 insertions(+), 29 deletions(-) create mode 100644 django_project/certification/migrations/0007_courseconvener_is_active.py create mode 100644 django_project/certification/templates/certifying_organisation/includes/convenor_list.html diff --git a/django_project/certification/forms.py b/django_project/certification/forms.py index 96f3d4fb2..a56c1c7bf 100644 --- a/django_project/certification/forms.py +++ b/django_project/certification/forms.py @@ -156,6 +156,7 @@ class Meta: 'user', 'degree', 'signature', + 'is_active', ) def __init__(self, *args, **kwargs): @@ -171,6 +172,7 @@ def __init__(self, *args, **kwargs): Field('user', css_class='form-control chosen-select'), Field('degree', css_class='form-control'), Field('signature', css_class='form-control'), + Field('is_active', css_class='checkbox-primary'), ) ) self.helper.layout = layout diff --git a/django_project/certification/migrations/0007_courseconvener_is_active.py b/django_project/certification/migrations/0007_courseconvener_is_active.py new file mode 100644 index 000000000..427a58442 --- /dev/null +++ b/django_project/certification/migrations/0007_courseconvener_is_active.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.18 on 2021-11-26 04:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('certification', '0006_auto_20210730_0615'), + ] + + operations = [ + migrations.AddField( + model_name='courseconvener', + name='is_active', + field=models.BooleanField(default=True, help_text='Inactive Convener will not be available in your organisation list.'), + ), + ] diff --git a/django_project/certification/models/course_convener.py b/django_project/certification/models/course_convener.py index 0a7c07eed..6dc2c0750 100644 --- a/django_project/certification/models/course_convener.py +++ b/django_project/certification/models/course_convener.py @@ -52,6 +52,12 @@ class CourseConvener(models.Model): null=True ) + is_active = models.BooleanField( + help_text=_('Inactive Convener will not be available in your ' + 'organisation list.'), + default=True + ) + class Meta: ordering = ['user'] diff --git a/django_project/certification/templates/certifying_organisation/detail.html b/django_project/certification/templates/certifying_organisation/detail.html index a9343cad3..b565bccc0 100644 --- a/django_project/certification/templates/certifying_organisation/detail.html +++ b/django_project/certification/templates/certifying_organisation/detail.html @@ -85,6 +85,9 @@

Certifying Organisation (all)

color: darkblue !important; text-decoration: none !important; } + .btn-disabled, .btn-disabled:hover { + color: #d3d3d3 + } {% if messages %} @@ -536,34 +539,7 @@
No course convener are defined, but you can {% for courseconvener in courseconveners %} - - -
- {% if courseconvener.user.first_name %} - {{ courseconvener.user.first_name }} {{ courseconvener.user.last_name }} - {% else %} - {{ courseconvener.user }} - {% endif %} -
- - - {% if user_can_delete %} - -
- - {% endif %} - + {% include 'certifying_organisation/includes/convenor_list.html' %} {% endfor %}
diff --git a/django_project/certification/templates/certifying_organisation/includes/convenor_list.html b/django_project/certification/templates/certifying_organisation/includes/convenor_list.html new file mode 100644 index 000000000..05ce9008f --- /dev/null +++ b/django_project/certification/templates/certifying_organisation/includes/convenor_list.html @@ -0,0 +1,50 @@ + + +
+ {% if courseconvener.is_active %} + {% if courseconvener.user.first_name %} + {{ courseconvener.user.first_name }} {{ courseconvener.user.last_name }} + {% else %} + {{ courseconvener.user }} + {% endif %} + {% elif user_can_delete %} + + {% if courseconvener.user.first_name %} + {{ courseconvener.user.first_name }} {{ courseconvener.user.last_name }} + {% else %} + {{ courseconvener.user }} + {% endif %} + + [inactive] + {% endif %} +
+ + {% if user_can_delete %} + +
+ {% if courseconvener.course_set.exists %} + + + + {% else %} + + + + {% endif %} + + + +
+ + {% endif %} + \ No newline at end of file diff --git a/django_project/certification/tests/test_models.py b/django_project/certification/tests/test_models.py index c7e723990..eedccafd5 100644 --- a/django_project/certification/tests/test_models.py +++ b/django_project/certification/tests/test_models.py @@ -336,6 +336,7 @@ def test_Course_Convener_create(self): self.assertEqual(model.__dict__.get(key), val) self.assertTrue(model.title == 'new Course Convener Title') self.assertTrue(model.degree == 'new Course Convener Degree') + self.assertTrue(model.is_active) # check if PK exists. self.assertTrue(model.pk is not None) diff --git a/django_project/certification/views/certifying_organisation.py b/django_project/certification/views/certifying_organisation.py index 3a8899ebb..0ef42705a 100644 --- a/django_project/certification/views/certifying_organisation.py +++ b/django_project/certification/views/certifying_organisation.py @@ -213,7 +213,7 @@ def get_context_data(self, **kwargs): certifying_organisation=certifying_organisation) context['num_coursetype'] = context['coursetypes'].count() context['courseconveners'] = CourseConvener.objects.filter( - certifying_organisation=certifying_organisation) + certifying_organisation=certifying_organisation).prefetch_related('course_set') context['num_courseconvener'] = context['courseconveners'].count() context['courses'] = Course.objects.filter( certifying_organisation=certifying_organisation).order_by( From d5427d7a5a338a5846ac62453432156236c1699d Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 26 Nov 2021 14:17:38 +0800 Subject: [PATCH 06/27] add unit test --- django_project/certification/forms.py | 2 +- .../tests/views/test_course_view.py | 108 +++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/django_project/certification/forms.py b/django_project/certification/forms.py index a56c1c7bf..34371b145 100644 --- a/django_project/certification/forms.py +++ b/django_project/certification/forms.py @@ -331,7 +331,7 @@ def __init__(self, *args, **kwargs): super(CourseForm, self).__init__(*args, **kwargs) self.fields['course_convener'].queryset = \ CourseConvener.objects.filter( - certifying_organisation=self.certifying_organisation) + certifying_organisation=self.certifying_organisation, is_active=True) self.fields['course_convener'].label_from_instance = \ lambda obj: "%s <%s>" % (obj.user.get_full_name(), obj) self.fields['course_type'].queryset = \ diff --git a/django_project/certification/tests/views/test_course_view.py b/django_project/certification/tests/views/test_course_view.py index 3a1a25693..274bab933 100644 --- a/django_project/certification/tests/views/test_course_view.py +++ b/django_project/certification/tests/views/test_course_view.py @@ -7,7 +7,8 @@ ProjectF, UserF, CertifyingOrganisationF, - CourseF + CourseF, + CourseConvenerF ) @@ -162,3 +163,108 @@ def test_delete_with_duplicates(self): 'slug': self.certifying_organisation.slug, }) self.assertRedirects(response, expected_url) + + @override_settings(VALID_DOMAIN=['testserver', ]) + def test_inactive_convener_should_not_be_in_the_course_convener_list(self): + convener = UserF.create(**{ + 'username': 'convener', + 'password': 'password', + 'first_name': 'Pretty', + 'last_name': 'Smart', + 'is_staff': True + }) + convener_inactive = UserF.create(**{ + 'username': 'inactive_convener', + 'password': 'password', + 'first_name': 'Wonder', + 'last_name': 'Woman', + 'is_staff': True + }) + CourseConvenerF.create( + user=convener, + certifying_organisation=self.certifying_organisation + ) + CourseConvenerF.create( + user=convener_inactive, + certifying_organisation=self.certifying_organisation, + is_active=False + ) + self.client.login(username='anita', password='password') + response = self.client.get(reverse('course-create', kwargs={ + 'project_slug': self.project.slug, + 'organisation_slug': self.certifying_organisation.slug, + })) + self.assertContains(response, 'Pretty Smart') + self.assertNotContains(response, 'Wonder Woman') + self.assertNotContains(response, 'inactive_convener') + + @override_settings(VALID_DOMAIN=['testserver', ]) + def test_inactive_convener_should_not_be_in_normal_user_detail_page(self): + convener = UserF.create(**{ + 'username': 'convener', + 'password': 'password', + 'first_name': 'Pretty', + 'last_name': 'Smart', + 'is_staff': True + }) + convener_inactive = UserF.create(**{ + 'username': 'inactive_convener', + 'password': 'password', + 'first_name': 'Wonder', + 'last_name': 'Woman', + 'is_staff': True + }) + CourseConvenerF.create( + user=convener, + certifying_organisation=self.certifying_organisation + ) + CourseConvenerF.create( + user=convener_inactive, + certifying_organisation=self.certifying_organisation, + is_active=False + ) + response = self.client.get( + reverse('certifyingorganisation-detail', kwargs={ + 'project_slug': self.project.slug, + 'slug': self.certifying_organisation.slug, + }) + ) + self.assertContains(response, 'Pretty Smart') + self.assertNotContains(response, 'Wonder Woman') + self.assertNotContains(response, '[inactive]') + + @override_settings(VALID_DOMAIN=['testserver', ]) + def test_inactive_convener_should_be_in_normal_user_detail_page(self): + convener = UserF.create(**{ + 'username': 'convener', + 'password': 'password', + 'first_name': 'Pretty', + 'last_name': 'Smart', + 'is_staff': True + }) + convener_inactive = UserF.create(**{ + 'username': 'inactive_convener', + 'password': 'password', + 'first_name': 'Wonder', + 'last_name': 'Woman', + 'is_staff': True + }) + CourseConvenerF.create( + user=convener, + certifying_organisation=self.certifying_organisation + ) + CourseConvenerF.create( + user=convener_inactive, + certifying_organisation=self.certifying_organisation, + is_active=False + ) + self.client.login(username='anita', password='password') + response = self.client.get( + reverse('certifyingorganisation-detail', kwargs={ + 'project_slug': self.project.slug, + 'slug': self.certifying_organisation.slug, + }) + ) + self.assertContains(response, 'Pretty Smart') + self.assertContains(response, 'Wonder Woman') + self.assertContains(response, '[inactive]') From 745b191a9872ee613255b243c5e0d290f5d76053 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 26 Nov 2021 14:33:28 +0800 Subject: [PATCH 07/27] fix typo flake8 --- django_project/certification/forms.py | 3 ++- django_project/certification/views/certifying_organisation.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/django_project/certification/forms.py b/django_project/certification/forms.py index 34371b145..6c3249e38 100644 --- a/django_project/certification/forms.py +++ b/django_project/certification/forms.py @@ -331,7 +331,8 @@ def __init__(self, *args, **kwargs): super(CourseForm, self).__init__(*args, **kwargs) self.fields['course_convener'].queryset = \ CourseConvener.objects.filter( - certifying_organisation=self.certifying_organisation, is_active=True) + certifying_organisation=self.certifying_organisation, + is_active=True) self.fields['course_convener'].label_from_instance = \ lambda obj: "%s <%s>" % (obj.user.get_full_name(), obj) self.fields['course_type'].queryset = \ diff --git a/django_project/certification/views/certifying_organisation.py b/django_project/certification/views/certifying_organisation.py index 0ef42705a..be274029f 100644 --- a/django_project/certification/views/certifying_organisation.py +++ b/django_project/certification/views/certifying_organisation.py @@ -213,7 +213,8 @@ def get_context_data(self, **kwargs): certifying_organisation=certifying_organisation) context['num_coursetype'] = context['coursetypes'].count() context['courseconveners'] = CourseConvener.objects.filter( - certifying_organisation=certifying_organisation).prefetch_related('course_set') + certifying_organisation=certifying_organisation + ).prefetch_related('course_set') context['num_courseconvener'] = context['courseconveners'].count() context['courses'] = Course.objects.filter( certifying_organisation=certifying_organisation).order_by( From 547869cd8cee5406492d447776567c7e0601bf8c Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 26 Nov 2021 16:35:57 +0800 Subject: [PATCH 08/27] fixed raising ValidationError in validate_email_address --- .../models/certifying_organisation.py | 7 ++++--- django_project/certification/tests/test_models.py | 13 +++++++++++++ django_project/changes/models/sponsor.py | 7 ++++--- django_project/changes/tests/test_models.py | 14 ++++++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/django_project/certification/models/certifying_organisation.py b/django_project/certification/models/certifying_organisation.py index 8ca634d14..07332a295 100644 --- a/django_project/certification/models/certifying_organisation.py +++ b/django_project/certification/models/certifying_organisation.py @@ -71,10 +71,11 @@ def validate_email_address(value): try: validate_email(value) return True - except ValidationError( + except ValidationError: + raise ValidationError( _('%(value)s is not a valid email address'), - params={'value': value},): - return False + params={'value': value}, + ) class CertifyingOrganisation(models.Model): diff --git a/django_project/certification/tests/test_models.py b/django_project/certification/tests/test_models.py index c7e723990..902feeefb 100644 --- a/django_project/certification/tests/test_models.py +++ b/django_project/certification/tests/test_models.py @@ -1,6 +1,7 @@ # coding=utf-8 """Test for models.""" +from django.core.exceptions import ValidationError from django.test import TestCase from certification.tests.model_factories import ( CertificateF, @@ -415,3 +416,15 @@ def test_Status_update(self): for key, val in new_model_data.items(): self.assertEqual(model.__dict__.get(key), val) self.assertTrue(model.name == 'new Status name') + + +class TestValidateEmailAddress(TestCase): + """Test validate_email_address function.""" + + def test_validation_failed_must_raise_ValidationError(self): + from certification.models import validate_email_address + email = 'email@wrongdomain' + msg = f'{email} is not a valid email address' + with self.assertRaisesMessage(ValidationError, msg): + validate_email_address(email) + diff --git a/django_project/changes/models/sponsor.py b/django_project/changes/models/sponsor.py index d7c9d87b2..88b715138 100644 --- a/django_project/changes/models/sponsor.py +++ b/django_project/changes/models/sponsor.py @@ -38,10 +38,11 @@ def validate_email_address(value): try: validate_email(value) return True - except ValidationError( + except ValidationError: + raise ValidationError( _('%(value)s is not a valid email address'), - params={'value': value},): - return False + params={'value': value}, + ) class ApprovedSponsorManager(models.Manager): diff --git a/django_project/changes/tests/test_models.py b/django_project/changes/tests/test_models.py index 6584f4bc6..24a338a53 100644 --- a/django_project/changes/tests/test_models.py +++ b/django_project/changes/tests/test_models.py @@ -1,7 +1,10 @@ # coding=utf-8 """Tests for models.""" from datetime import datetime + +from django.core.exceptions import ValidationError from django.test import TestCase + from changes.tests.model_factories import ( CategoryF, EntryF, @@ -460,3 +463,14 @@ def test_SponsorshipPeriod_delete(self): # check if deleted self.assertTrue(model.pk is None) + + +class TestValidateEmailAddress(TestCase): + """Test validate_email_address function.""" + + def test_validation_failed_must_raise_ValidationError(self): + from changes.models import validate_email_address + email = 'email@wrongdomain' + msg = f'{email} is not a valid email address' + with self.assertRaisesMessage(ValidationError, msg): + validate_email_address(email) From 79e0987ecd5feedfd27cacb50cfefb75994b2374 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 26 Nov 2021 16:49:53 +0800 Subject: [PATCH 09/27] fixed typo flake8 --- django_project/certification/tests/test_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django_project/certification/tests/test_models.py b/django_project/certification/tests/test_models.py index 902feeefb..27d691c88 100644 --- a/django_project/certification/tests/test_models.py +++ b/django_project/certification/tests/test_models.py @@ -427,4 +427,3 @@ def test_validation_failed_must_raise_ValidationError(self): msg = f'{email} is not a valid email address' with self.assertRaisesMessage(ValidationError, msg): validate_email_address(email) - From 613dbe728b391f5e6e93a20566859b72b388db5d Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 3 Dec 2021 10:22:41 +0800 Subject: [PATCH 10/27] added CertificateType model --- .../migrations/0007_certificatetype.py | 23 ++++++++ .../certification/models/__init__.py | 1 + .../certification/models/certificate_type.py | 39 +++++++++++++ .../certification/tests/model_factories.py | 13 +++++ .../certification/tests/test_models.py | 57 +++++++++++++++++++ 5 files changed, 133 insertions(+) create mode 100644 django_project/certification/migrations/0007_certificatetype.py create mode 100644 django_project/certification/models/certificate_type.py diff --git a/django_project/certification/migrations/0007_certificatetype.py b/django_project/certification/migrations/0007_certificatetype.py new file mode 100644 index 000000000..fc7fa0066 --- /dev/null +++ b/django_project/certification/migrations/0007_certificatetype.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.18 on 2021-12-03 00:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('certification', '0006_auto_20210730_0615'), + ] + + operations = [ + migrations.CreateModel( + name='CertificateType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Certificate type.', max_length=200)), + ('description', models.TextField(blank=True, help_text='Certificate type description - 1000 characters limit.', max_length=1000, null=True)), + ('printed_text', models.CharField(help_text='Wording that will be placed on certificate. e.g. "Has attended and completed".', max_length=500)), + ('order', models.IntegerField(blank=True, null=True, unique=True)), + ], + ), + ] diff --git a/django_project/certification/models/__init__.py b/django_project/certification/models/__init__.py index 10e9bf2df..79c82dd0c 100644 --- a/django_project/certification/models/__init__.py +++ b/django_project/certification/models/__init__.py @@ -10,5 +10,6 @@ from certification.models.course_attendee import * from certification.models.course_type import * from certification.models.course_convener import * +from certification.models.certificate_type import * from certification.models.certificate import * from certification.models.organisation_certificate import * diff --git a/django_project/certification/models/certificate_type.py b/django_project/certification/models/certificate_type.py new file mode 100644 index 000000000..c62f36993 --- /dev/null +++ b/django_project/certification/models/certificate_type.py @@ -0,0 +1,39 @@ +"""Certificate type model for certification app""" + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class CertificateType(models.Model): + name = models.CharField( + help_text=_('Certificate type.'), + max_length=200, + null=False, + blank=False + ) + + description = models.TextField( + help_text=_('Certificate type description - 1000 characters limit.'), + max_length=1000, + null=True, + blank=True, + ) + + printed_text = models.CharField( + help_text=_( + 'Wording that will be placed on certificate. ' + 'e.g. "Has attended and completed".' + ), + max_length=500, + null=False, + blank=False + ) + + order = models.IntegerField( + blank=True, + null=True, + unique=True + ) + + def __str__(self): + return self.name diff --git a/django_project/certification/tests/model_factories.py b/django_project/certification/tests/model_factories.py index 557b61797..2feb2f45e 100644 --- a/django_project/certification/tests/model_factories.py +++ b/django_project/certification/tests/model_factories.py @@ -5,6 +5,7 @@ from certification.models import ( Certificate, + CertificateType, Attendee, Course, CourseType, @@ -81,6 +82,18 @@ class Meta: author = factory.SubFactory(UserF) +class CertificateTypeF(factory.django.DjangoModelFactory): + class Meta: + model = CertificateType + + name = factory.sequence(lambda n: 'Test certificate type name %s' % n) + description = factory.sequence( + lambda n: 'Description certificate type %s' % n) + printed_text = factory.sequence( + lambda n: 'Wording certificate type %s' % n) + order = factory.sequence(lambda n: n) + + class CourseF(factory.django.DjangoModelFactory): """Course model factory.""" diff --git a/django_project/certification/tests/test_models.py b/django_project/certification/tests/test_models.py index 27d691c88..befbecba7 100644 --- a/django_project/certification/tests/test_models.py +++ b/django_project/certification/tests/test_models.py @@ -1,10 +1,12 @@ # coding=utf-8 """Test for models.""" +from django.db.utils import IntegrityError from django.core.exceptions import ValidationError from django.test import TestCase from certification.tests.model_factories import ( CertificateF, + CertificateTypeF, AttendeeF, CourseF, CourseTypeF, @@ -121,6 +123,61 @@ def test_Certificate_delete(self): self.assertTrue(model.pk is None) +class TestCertificateType(TestCase): + """Test Certificate models.""" + + def test_CRUD_CertificateType(self): + from certification.models.certificate_type import CertificateType + CertificateType.objects.all().delete() + + # initial + self.assertEqual(CertificateType.objects.all().count(), 0) + + # create model + model = CertificateTypeF.create() + self.assertEqual(CertificateType.objects.all().count(), 1) + + # read model + self.assertIsNotNone(model.id) + self.assertIn('Test certificate type name', model.name) + self.assertIn('Description certificate type', model.description) + self.assertIn('Wording certificate type', model.printed_text) + self.assertIsNotNone(model.order) + self.assertEqual(model.__str__(), model.name) + + # + model.name = 'Update certificate type name' + model.save() + self.assertEqual(model.name, 'Update certificate type name') + + model.delete() + self.assertIsNone(model.id) + self.assertEqual(CertificateType.objects.all().count(), 0) + + def test_order_field_must_be_unique(self): + model_1 = CertificateTypeF.create(order=1) + msg = ('duplicate key value violates unique constraint ' + '"certification_certificatetype_order_key"') + with self.assertRaisesMessage(IntegrityError, msg): + CertificateTypeF.create(order=1) + + def test_order_field_can_be_null(self): + model_1 = CertificateTypeF.create(order=1) + model_2 = CertificateTypeF.create(order=2) + + self.assertEqual(model_1.order, 1) + self.assertEqual(model_2.order, 2) + + model_1.order = None + model_1.save() + + model_2.order = 1 + model_2.save() + + self.assertEqual(model_1.order, None) + self.assertEqual(model_2.order, 1) + + class TestAttendee(TestCase): """Test attendee model.""" From a1c5b721435717ab5f9886b61d0360f8491b8c8c Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 3 Dec 2021 11:03:52 +0800 Subject: [PATCH 11/27] added admin class --- django_project/certification/admin.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/django_project/certification/admin.py b/django_project/certification/admin.py index e753b304e..24c15c393 100644 --- a/django_project/certification/admin.py +++ b/django_project/certification/admin.py @@ -4,6 +4,7 @@ from django.contrib.gis import admin from simple_history.admin import SimpleHistoryAdmin from certification.models.certificate import Certificate +from certification.models.certificate_type import CertificateType from certification.models.course import Course from certification.models.training_center import TrainingCenter from certification.models.course_convener import CourseConvener @@ -34,6 +35,13 @@ def queryset(self, request): return query_set +class CertificateTypeAdmin(admin.ModelAdmin): + """CertificateType admin model.""" + + list_display = ('name', 'printed_text') + search_fields = ('name', 'printed_text') + + class AttendeeAdmin(admin.ModelAdmin): """Attendee admin model.""" list_display = ('firstname', 'surname', 'email', 'certifying_organisation') @@ -163,6 +171,7 @@ class StatusAdmin(admin.ModelAdmin): admin.site.register(Certificate, CertificateAdmin) +admin.site.register(CertificateType, CertificateTypeAdmin) admin.site.register(Attendee, AttendeeAdmin) admin.site.register(Course, CourseAdmin) admin.site.register(CourseType, CourseTypeAdmin) From 3fb6b64bff97dd1f48756e10210b578f38dd84ee Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 3 Dec 2021 15:53:59 +0800 Subject: [PATCH 12/27] added CertificateType as foreign key in Certificate models and updated admin --- django_project/certification/admin.py | 4 +- .../migrations/0007_certificatetype.py | 13 ++++- .../0008_certificate_certificate_type.py | 47 +++++++++++++++++++ .../certification/models/certificate.py | 3 ++ .../certification/models/certificate_type.py | 3 +- .../certification/tests/test_models.py | 9 +++- 6 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 django_project/certification/migrations/0008_certificate_certificate_type.py diff --git a/django_project/certification/admin.py b/django_project/certification/admin.py index 24c15c393..ec79e8a83 100644 --- a/django_project/certification/admin.py +++ b/django_project/certification/admin.py @@ -38,8 +38,10 @@ def queryset(self, request): class CertificateTypeAdmin(admin.ModelAdmin): """CertificateType admin model.""" - list_display = ('name', 'printed_text') + list_display = ('name', 'printed_text', 'order') + list_editable = ('order', ) search_fields = ('name', 'printed_text') + ordering = ('order', ) class AttendeeAdmin(admin.ModelAdmin): diff --git a/django_project/certification/migrations/0007_certificatetype.py b/django_project/certification/migrations/0007_certificatetype.py index fc7fa0066..636076242 100644 --- a/django_project/certification/migrations/0007_certificatetype.py +++ b/django_project/certification/migrations/0007_certificatetype.py @@ -3,6 +3,15 @@ from django.db import migrations, models +def add_certification_type_as_existing_value(apps, schema_editor): + CertificateType = apps.get_model('certification', 'CertificateType') + CertificateType.objects.create( + name='attendance and completion', + printed_text='Has attended and completed the course:', + order=0 + ) + + class Migration(migrations.Migration): dependencies = [ @@ -14,10 +23,12 @@ class Migration(migrations.Migration): name='CertificateType', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='Certificate type.', max_length=200)), + ('name', models.CharField(help_text='Certificate type.', max_length=200, unique=True)), ('description', models.TextField(blank=True, help_text='Certificate type description - 1000 characters limit.', max_length=1000, null=True)), ('printed_text', models.CharField(help_text='Wording that will be placed on certificate. e.g. "Has attended and completed".', max_length=500)), ('order', models.IntegerField(blank=True, null=True, unique=True)), ], ), + + migrations.RunPython(add_certification_type_as_existing_value, reverse_code=migrations.RunPython.noop), ] diff --git a/django_project/certification/migrations/0008_certificate_certificate_type.py b/django_project/certification/migrations/0008_certificate_certificate_type.py new file mode 100644 index 000000000..d27f9225a --- /dev/null +++ b/django_project/certification/migrations/0008_certificate_certificate_type.py @@ -0,0 +1,47 @@ +# Generated by Django 2.2.18 on 2021-12-03 07:09 + +from django.db import migrations, models +import django.db.models.deletion + + +def set_existing_certificate_type_value(apps, shcema_editor): + CertificateType = apps.get_model('certification', 'CertificateType') + Certificate = apps.get_model('certification', 'Certificate') + certificate_type = CertificateType.objects.filter( + name='attendance and completion').first() + certificates = Certificate.objects.all() + + for cer in certificates: + cer.certificate_type = certificate_type + cer.save(update_fields=['certificate_type']) + + + +class Migration(migrations.Migration): + + dependencies = [ + ('certification', '0007_certificatetype'), + ] + + operations = [ + migrations.AddField( + model_name='certificate', + name='certificate_type', + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to='certification.CertificateType'), + ), + + migrations.RunPython(set_existing_certificate_type_value, reverse_code=migrations.RunPython.noop), + + migrations.AlterField( + model_name='certificate', + name='certificate_type', + field=models.ForeignKey( + null=False, + on_delete=django.db.models.deletion.PROTECT, + to='certification.CertificateType'), + preserve_default=False, + ), + ] diff --git a/django_project/certification/models/certificate.py b/django_project/certification/models/certificate.py index a218731a8..e48bcdc06 100644 --- a/django_project/certification/models/certificate.py +++ b/django_project/certification/models/certificate.py @@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _ from .course import Course from .attendee import Attendee +from .certificate_type import CertificateType def increment_id(project): @@ -54,6 +55,8 @@ class Certificate(models.Model): author = models.ForeignKey(User, on_delete=models.CASCADE) course = models.ForeignKey(Course, on_delete=models.CASCADE) attendee = models.ForeignKey(Attendee, on_delete=models.CASCADE) + certificate_type = models.ForeignKey(CertificateType, + on_delete=models.PROTECT) objects = models.Manager() class Meta: diff --git a/django_project/certification/models/certificate_type.py b/django_project/certification/models/certificate_type.py index c62f36993..1e4310bf5 100644 --- a/django_project/certification/models/certificate_type.py +++ b/django_project/certification/models/certificate_type.py @@ -9,7 +9,8 @@ class CertificateType(models.Model): help_text=_('Certificate type.'), max_length=200, null=False, - blank=False + blank=False, + unique=True, ) description = models.TextField( diff --git a/django_project/certification/tests/test_models.py b/django_project/certification/tests/test_models.py index befbecba7..e0b309c36 100644 --- a/django_project/certification/tests/test_models.py +++ b/django_project/certification/tests/test_models.py @@ -154,8 +154,15 @@ def test_CRUD_CertificateType(self): self.assertIsNone(model.id) self.assertEqual(CertificateType.objects.all().count(), 0) + def test_name_field_must_be_unique(self): + CertificateTypeF.create(name="We are twin") + msg = ('duplicate key value violates unique constraint ' + '"certification_certificatetype_name_key"') + with self.assertRaisesMessage(IntegrityError, msg): + CertificateTypeF.create(name="We are twin") + def test_order_field_must_be_unique(self): - model_1 = CertificateTypeF.create(order=1) + CertificateTypeF.create(order=1) msg = ('duplicate key value violates unique constraint ' '"certification_certificatetype_order_key"') with self.assertRaisesMessage(IntegrityError, msg): From af6b58255619a1ae28eb2ce889f604062e5b90d8 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 3 Dec 2021 16:34:05 +0800 Subject: [PATCH 13/27] updated unittest --- .../certification/tests/model_factories.py | 25 +++++++------- .../certification/tests/test_models.py | 33 ++++++++++++------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/django_project/certification/tests/model_factories.py b/django_project/certification/tests/model_factories.py index 2feb2f45e..40f43240a 100644 --- a/django_project/certification/tests/model_factories.py +++ b/django_project/certification/tests/model_factories.py @@ -82,18 +82,6 @@ class Meta: author = factory.SubFactory(UserF) -class CertificateTypeF(factory.django.DjangoModelFactory): - class Meta: - model = CertificateType - - name = factory.sequence(lambda n: 'Test certificate type name %s' % n) - description = factory.sequence( - lambda n: 'Description certificate type %s' % n) - printed_text = factory.sequence( - lambda n: 'Wording certificate type %s' % n) - order = factory.sequence(lambda n: n) - - class CourseF(factory.django.DjangoModelFactory): """Course model factory.""" @@ -137,6 +125,18 @@ class Meta: attendee = factory.SubFactory(AttendeeF) +class CertificateTypeF(factory.django.DjangoModelFactory): + class Meta: + model = CertificateType + + name = factory.sequence(lambda n: 'Test certificate type name %s' % n) + description = factory.sequence( + lambda n: 'Description certificate type %s' % n) + printed_text = factory.sequence( + lambda n: 'Wording certificate type %s' % n) + order = factory.sequence(lambda n: n) + + class CertificateF(factory.django.DjangoModelFactory): """Certificate model factory.""" @@ -147,6 +147,7 @@ class Meta: course = factory.SubFactory(CourseF) attendee = factory.SubFactory(AttendeeF) author = factory.SubFactory(UserF) + certificate_type = factory.SubFactory(CertificateTypeF) class StatusF(factory.django.DjangoModelFactory): diff --git a/django_project/certification/tests/test_models.py b/django_project/certification/tests/test_models.py index e0b309c36..d97d7caa6 100644 --- a/django_project/certification/tests/test_models.py +++ b/django_project/certification/tests/test_models.py @@ -16,6 +16,14 @@ CourseAttendeeF, StatusF ) +from certification.models.certificate_type import CertificateType + + +class SetUpMixin: + def setUp(self): + """Set up before each test.""" + # Delete CertificateType created from migration 0007_certificate_type + CertificateType.objects.all().delete() class TestCertifyingOrganisation(TestCase): @@ -97,21 +105,19 @@ def test_Certifying_Organisation_update(self): self.assertEqual(model.__dict__.get(key), val) -class TestCertificate(TestCase): +class CertificateSetUp(SetUpMixin, TestCase): """Test certificate model.""" - def setUp(self): - """Set up before test.""" - - pass - def test_Certificate_create(self): """Test certificate model creation.""" - - model = CertificateF.create() + certificate_type = CertificateTypeF.create() + model = CertificateF.create( + certificate_type=certificate_type + ) # check if PK exists. self.assertTrue(model.pk is not None) + self.assertIsNotNone(model.certificate_type) def test_Certificate_delete(self): """Test certificate model deletion.""" @@ -122,14 +128,17 @@ def test_Certificate_delete(self): # check if deleted. self.assertTrue(model.pk is None) + def test_certificate_type_must_not_null(self): + msg = ('null value in column "certificate_type_id" ' + 'violates not-null constraint') + with self.assertRaisesMessage(IntegrityError, msg): + CertificateF.create(certificate_type=None) + -class TestCertificateType(TestCase): +class CertificateTypeSetUp(SetUpMixin, TestCase): """Test Certificate models.""" def test_CRUD_CertificateType(self): - from certification.models.certificate_type import CertificateType - CertificateType.objects.all().delete() - # initial self.assertEqual(CertificateType.objects.all().count(), 0) From ec6763c8450f5a4c32d65d2b323235638f69c47f Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 10 Dec 2021 10:54:04 +0800 Subject: [PATCH 14/27] added project certificate_type --- .../migrations/0009_projectcertificatetype.py | 38 +++++++++++++++++++ .../certification/models/certificate_type.py | 15 ++++++++ 2 files changed, 53 insertions(+) create mode 100644 django_project/certification/migrations/0009_projectcertificatetype.py diff --git a/django_project/certification/migrations/0009_projectcertificatetype.py b/django_project/certification/migrations/0009_projectcertificatetype.py new file mode 100644 index 000000000..86806aeb2 --- /dev/null +++ b/django_project/certification/migrations/0009_projectcertificatetype.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.18 on 2021-12-10 02:23 + +from django.db import migrations, models +import django.db.models.deletion + +def create_existing_project_certificate_type(apps, schema_editor): + CertificateType = apps.get_model('certification', 'CertificateType') + ProjectCertificateType = apps.get_model('certification', 'ProjectCertificateType') + Project = apps.get_model('base', 'Project') + certificate_type = CertificateType.objects.filter( + name='attendance and completion').first() + projects = Project.objects.all() + + for project in projects: + ProjectCertificateType.objects.create( + project=project, + certificate_type=certificate_type + ) + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0006_auto_20210308_0244'), + ('certification', '0008_certificate_certificate_type'), + ] + + operations = [ + migrations.CreateModel( + name='ProjectCertificateType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('certificate_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='certification.CertificateType')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.Project')), + ], + ), + + migrations.RunPython(create_existing_project_certificate_type, reverse_code=migrations.RunPython.noop), + ] diff --git a/django_project/certification/models/certificate_type.py b/django_project/certification/models/certificate_type.py index 1e4310bf5..7622dc398 100644 --- a/django_project/certification/models/certificate_type.py +++ b/django_project/certification/models/certificate_type.py @@ -3,6 +3,8 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from base.models.project import Project + class CertificateType(models.Model): name = models.CharField( @@ -38,3 +40,16 @@ class CertificateType(models.Model): def __str__(self): return self.name + + +class ProjectCertificateType(models.Model): + """A model to store a certificate type linked to a project""" + + project = models.ForeignKey( + Project, + on_delete=models.CASCADE + ) + certificate_type = models.ForeignKey( + CertificateType, + on_delete=models.CASCADE + ) From 1f76a371e4fc70debc51c5f42e088c109cd488ae Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 10 Dec 2021 15:55:48 +0800 Subject: [PATCH 15/27] part#2: enable certification manager to manage certificate type list --- django_project/certification/admin.py | 4 +- .../migrations/0007_certificatetype.py | 4 +- .../certification/models/certificate_type.py | 2 +- .../templates/certificate_type/list.html | 43 ++++++++++++ .../certification/tests/model_factories.py | 2 +- .../certification/tests/test_models.py | 2 +- django_project/certification/urls.py | 13 ++++ .../certification/views/__init__.py | 1 + .../certification/views/certificate_type.py | 69 +++++++++++++++++++ .../includes/base-auth-nav-left.html | 3 + 10 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 django_project/certification/templates/certificate_type/list.html create mode 100644 django_project/certification/views/certificate_type.py diff --git a/django_project/certification/admin.py b/django_project/certification/admin.py index ec79e8a83..5287f084a 100644 --- a/django_project/certification/admin.py +++ b/django_project/certification/admin.py @@ -38,9 +38,9 @@ def queryset(self, request): class CertificateTypeAdmin(admin.ModelAdmin): """CertificateType admin model.""" - list_display = ('name', 'printed_text', 'order') + list_display = ('name', 'wording', 'order') list_editable = ('order', ) - search_fields = ('name', 'printed_text') + search_fields = ('name', 'wording') ordering = ('order', ) diff --git a/django_project/certification/migrations/0007_certificatetype.py b/django_project/certification/migrations/0007_certificatetype.py index 636076242..9434d8235 100644 --- a/django_project/certification/migrations/0007_certificatetype.py +++ b/django_project/certification/migrations/0007_certificatetype.py @@ -7,7 +7,7 @@ def add_certification_type_as_existing_value(apps, schema_editor): CertificateType = apps.get_model('certification', 'CertificateType') CertificateType.objects.create( name='attendance and completion', - printed_text='Has attended and completed the course:', + wording='Has attended and completed the course:', order=0 ) @@ -25,7 +25,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(help_text='Certificate type.', max_length=200, unique=True)), ('description', models.TextField(blank=True, help_text='Certificate type description - 1000 characters limit.', max_length=1000, null=True)), - ('printed_text', models.CharField(help_text='Wording that will be placed on certificate. e.g. "Has attended and completed".', max_length=500)), + ('wording', models.CharField(help_text='Wording that will be placed on certificate. e.g. "Has attended and completed".', max_length=500)), ('order', models.IntegerField(blank=True, null=True, unique=True)), ], ), diff --git a/django_project/certification/models/certificate_type.py b/django_project/certification/models/certificate_type.py index 7622dc398..8647a4014 100644 --- a/django_project/certification/models/certificate_type.py +++ b/django_project/certification/models/certificate_type.py @@ -22,7 +22,7 @@ class CertificateType(models.Model): blank=True, ) - printed_text = models.CharField( + wording = models.CharField( help_text=_( 'Wording that will be placed on certificate. ' 'e.g. "Has attended and completed".' diff --git a/django_project/certification/templates/certificate_type/list.html b/django_project/certification/templates/certificate_type/list.html new file mode 100644 index 000000000..77e569a43 --- /dev/null +++ b/django_project/certification/templates/certificate_type/list.html @@ -0,0 +1,43 @@ +{% extends "project_base.html" %} + +{% block extra_js %} +{% endblock %} + +{% block content %} + + + + + + + + + + + + + + + {% csrf_token %} + {% for cer_type in certificate_types %} + + + + + + {% endfor %} + +
Certificate TypeWordingApply
{{ cer_type.name }}{{ cer_type.wording }} + +
+ + +{% endblock %} diff --git a/django_project/certification/tests/model_factories.py b/django_project/certification/tests/model_factories.py index 40f43240a..49fb8cf4f 100644 --- a/django_project/certification/tests/model_factories.py +++ b/django_project/certification/tests/model_factories.py @@ -132,7 +132,7 @@ class Meta: name = factory.sequence(lambda n: 'Test certificate type name %s' % n) description = factory.sequence( lambda n: 'Description certificate type %s' % n) - printed_text = factory.sequence( + wording = factory.sequence( lambda n: 'Wording certificate type %s' % n) order = factory.sequence(lambda n: n) diff --git a/django_project/certification/tests/test_models.py b/django_project/certification/tests/test_models.py index d97d7caa6..bbd125d82 100644 --- a/django_project/certification/tests/test_models.py +++ b/django_project/certification/tests/test_models.py @@ -150,7 +150,7 @@ def test_CRUD_CertificateType(self): self.assertIsNotNone(model.id) self.assertIn('Test certificate type name', model.name) self.assertIn('Description certificate type', model.description) - self.assertIn('Wording certificate type', model.printed_text) + self.assertIn('Wording certificate type', model.wording) self.assertIsNotNone(model.order) self.assertEqual(model.__str__(), model.name) diff --git a/django_project/certification/urls.py b/django_project/certification/urls.py index 13d912eb4..f3427961c 100644 --- a/django_project/certification/urls.py +++ b/django_project/certification/urls.py @@ -31,6 +31,10 @@ CourseDeleteView, CourseDetailView, + # CourseType + ProjectCertificateTypeView, + updateProjectCertificateView, + # Training Center. TrainingCenterCreateView, TrainingCenterDetailView, @@ -235,6 +239,15 @@ view=OrganisationCertificateDetailView.as_view(), name='detail-certificate-organisation'), + # Certificate Type. + url(regex='^(?P[\w-]+)/certificate-types/$', + view=ProjectCertificateTypeView.as_view(), + name='certificate-type-list'), + url(regex='^(?P[\w-]+)/certificate-types/update/$', + view=updateProjectCertificateView, + name='certificate-type-update'), + + # Certificate. url(regex='^(?P[\w-]+)/certifyingorganisation/' '(?P[\w-]+)/course/' diff --git a/django_project/certification/views/__init__.py b/django_project/certification/views/__init__.py index 032b91f9c..9c8acf31c 100644 --- a/django_project/certification/views/__init__.py +++ b/django_project/certification/views/__init__.py @@ -8,4 +8,5 @@ from .course_attendee import * from .validate import * from .certificate import * +from .certificate_type import * from .certificate_organisation import * diff --git a/django_project/certification/views/certificate_type.py b/django_project/certification/views/certificate_type.py new file mode 100644 index 000000000..ac1606313 --- /dev/null +++ b/django_project/certification/views/certificate_type.py @@ -0,0 +1,69 @@ +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.urls import reverse +from django.views.generic import ListView + +from base.models.project import Project +from certification.models.certificate_type import ( + CertificateType, ProjectCertificateType +) + + +class ProjectCertificateTypeView(LoginRequiredMixin, ListView): + context_object_name = 'project_certificate_types' + template_name = 'certificate_type/list.html' + model = ProjectCertificateType + + def get_context_data(self, **kwargs): + """Get the context data which is passed to a template.""" + + # Navbar data + self.project_slug = self.kwargs.get('project_slug', None) + context = super( + ProjectCertificateTypeView, self).get_context_data(*kwargs) + context['project_slug'] = self.project_slug + if self.project_slug: + context['the_project'] = \ + Project.objects.get(slug=self.project_slug) + context['project'] = context['the_project'] + + # certificate types + context['certificate_types'] = CertificateType.objects.all().order_by( + 'order' + ) + project = get_object_or_404(Project, slug=self.kwargs['project_slug']) + context['certificate_types_applied'] = ProjectCertificateType.\ + objects.filter(project=project).values_list( + 'certificate_type', flat=True) + return context + + def get_queryset(self): + """Return certificate_types for a project.""" + + project = get_object_or_404(Project, slug=self.kwargs['project_slug']) + qs = ProjectCertificateType.objects.filter(project=project) + return qs + + +def updateProjectCertificateView(request, project_slug): + project = get_object_or_404(Project, slug=project_slug) + manager = project.certification_managers.all() + if request.user.is_staff or request.user in manager: + certificate_types = request.POST.getlist('certificate_types', []) + for cer in certificate_types: + certificate_type = get_object_or_404(CertificateType, name=cer) + obj, created = ProjectCertificateType.objects.get_or_create( + certificate_type=certificate_type, project=project + ) + # remove certificate_type that is not in the list + old_certificate_type = ProjectCertificateType.objects.filter( + project=project).select_related('certificate_type').all() + for cer in old_certificate_type: + if cer.certificate_type.name not in certificate_types: + ProjectCertificateType.objects.get( + certificate_type=cer.certificate_type, project=project + ).delete() + return HttpResponseRedirect( + reverse('certificate-type-list', kwargs={'project_slug': project_slug}) + ) diff --git a/django_project/core/base_templates/includes/base-auth-nav-left.html b/django_project/core/base_templates/includes/base-auth-nav-left.html index 5df1d4d2b..2bd328cbd 100644 --- a/django_project/core/base_templates/includes/base-auth-nav-left.html +++ b/django_project/core/base_templates/includes/base-auth-nav-left.html @@ -187,6 +187,9 @@
  • Rejected Organisations
  • Verify certificate for Certifying Organisation
  • Verify certificate for Attendee
  • + {% if user.is_staff or user in the_project.certification_managers.all %} +
  • Manage Certificate Type
  • + {% endif %} From 935b99a9f6d08545a2a3ee8d2e5cc07206c262de Mon Sep 17 00:00:00 2001 From: Sumandari Date: Sat, 11 Dec 2021 09:49:28 +0800 Subject: [PATCH 16/27] fixed typo flake8 --- django_project/certification/views/certificate_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_project/certification/views/certificate_type.py b/django_project/certification/views/certificate_type.py index ac1606313..9ad9a5bcf 100644 --- a/django_project/certification/views/certificate_type.py +++ b/django_project/certification/views/certificate_type.py @@ -37,7 +37,7 @@ def get_context_data(self, **kwargs): objects.filter(project=project).values_list( 'certificate_type', flat=True) return context - + def get_queryset(self): """Return certificate_types for a project.""" From 45ca0e3e601a5c3385283dc0785fa624e2b3f85c Mon Sep 17 00:00:00 2001 From: Sumandari Date: Sat, 11 Dec 2021 10:13:21 +0800 Subject: [PATCH 17/27] Part#3: added certificate_type in courseform --- django_project/certification/forms.py | 2 + .../0010_course_certificate_type.py | 41 +++++++++++++++++++ django_project/certification/models/course.py | 3 ++ 3 files changed, 46 insertions(+) create mode 100644 django_project/certification/migrations/0010_course_certificate_type.py diff --git a/django_project/certification/forms.py b/django_project/certification/forms.py index 96f3d4fb2..0034060bf 100644 --- a/django_project/certification/forms.py +++ b/django_project/certification/forms.py @@ -303,6 +303,7 @@ class Meta: 'end_date', 'template_certificate', 'certifying_organisation', + 'certificate_type', ) def __init__(self, *args, **kwargs): @@ -322,6 +323,7 @@ def __init__(self, *args, **kwargs): Field('start_date', css_class='form-control'), Field('end_date', css_class='form-control'), Field('template_certificate', css_class='form-control'), + Field('certificate_type', css_class='form-control'), ) ) self.helper.layout = layout diff --git a/django_project/certification/migrations/0010_course_certificate_type.py b/django_project/certification/migrations/0010_course_certificate_type.py new file mode 100644 index 000000000..53e8b7e74 --- /dev/null +++ b/django_project/certification/migrations/0010_course_certificate_type.py @@ -0,0 +1,41 @@ +# Generated by Django 2.2.18 on 2021-12-10 08:31 + +from django.db import migrations, models +import django.db.models.deletion + +def set_existing_certificate_type_value(apps, shcema_editor): + CertificateType = apps.get_model('certification', 'CertificateType') + Course = apps.get_model('certification', 'Course') + certificate_type = CertificateType.objects.filter( + name='attendance and completion').first() + courses = Course.objects.all() + + for course in courses: + course.certificate_type = certificate_type + course.save(update_fields=['certificate_type']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('certification', '0009_projectcertificatetype'), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='certificate_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='certification.CertificateType'), + ), + + migrations.RunPython(set_existing_certificate_type_value, reverse_code=migrations.RunPython.noop), + + migrations.AlterField( + model_name='course', + name='certificate_type', + field=models.ForeignKey(null=False, on_delete=django.db.models.deletion.PROTECT, + to='certification.CertificateType'), + preserve_default=False, + ), + + ] diff --git a/django_project/certification/models/course.py b/django_project/certification/models/course.py index a845538e3..339aa3306 100644 --- a/django_project/certification/models/course.py +++ b/django_project/certification/models/course.py @@ -20,6 +20,7 @@ from .course_type import CourseType from certification.utilities import check_slug from .training_center import TrainingCenter +from certification.models.certificate_type import CertificateType logger = logging.getLogger(__name__) @@ -86,6 +87,8 @@ class Course(models.Model): on_delete=models.CASCADE) certifying_organisation = models.ForeignKey(CertifyingOrganisation, on_delete=models.CASCADE) + certificate_type = models.ForeignKey( + CertificateType, on_delete=models.PROTECT, null=True) author = models.ForeignKey(User, on_delete=models.CASCADE) objects = models.Manager() From 5c798d96a3e361d37b656bb2c3b3889d59565d82 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Sat, 11 Dec 2021 10:23:26 +0800 Subject: [PATCH 18/27] removed certificate_type in certificate model --- .../0008_certificate_certificate_type.py | 47 ------------------- ...type.py => 0008_projectcertificatetype.py} | 2 +- ...ype.py => 0009_course_certificate_type.py} | 2 +- .../certification/models/certificate.py | 2 - 4 files changed, 2 insertions(+), 51 deletions(-) delete mode 100644 django_project/certification/migrations/0008_certificate_certificate_type.py rename django_project/certification/migrations/{0009_projectcertificatetype.py => 0008_projectcertificatetype.py} (95%) rename django_project/certification/migrations/{0010_course_certificate_type.py => 0009_course_certificate_type.py} (95%) diff --git a/django_project/certification/migrations/0008_certificate_certificate_type.py b/django_project/certification/migrations/0008_certificate_certificate_type.py deleted file mode 100644 index d27f9225a..000000000 --- a/django_project/certification/migrations/0008_certificate_certificate_type.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 2.2.18 on 2021-12-03 07:09 - -from django.db import migrations, models -import django.db.models.deletion - - -def set_existing_certificate_type_value(apps, shcema_editor): - CertificateType = apps.get_model('certification', 'CertificateType') - Certificate = apps.get_model('certification', 'Certificate') - certificate_type = CertificateType.objects.filter( - name='attendance and completion').first() - certificates = Certificate.objects.all() - - for cer in certificates: - cer.certificate_type = certificate_type - cer.save(update_fields=['certificate_type']) - - - -class Migration(migrations.Migration): - - dependencies = [ - ('certification', '0007_certificatetype'), - ] - - operations = [ - migrations.AddField( - model_name='certificate', - name='certificate_type', - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.PROTECT, - to='certification.CertificateType'), - ), - - migrations.RunPython(set_existing_certificate_type_value, reverse_code=migrations.RunPython.noop), - - migrations.AlterField( - model_name='certificate', - name='certificate_type', - field=models.ForeignKey( - null=False, - on_delete=django.db.models.deletion.PROTECT, - to='certification.CertificateType'), - preserve_default=False, - ), - ] diff --git a/django_project/certification/migrations/0009_projectcertificatetype.py b/django_project/certification/migrations/0008_projectcertificatetype.py similarity index 95% rename from django_project/certification/migrations/0009_projectcertificatetype.py rename to django_project/certification/migrations/0008_projectcertificatetype.py index 86806aeb2..b13a5b41d 100644 --- a/django_project/certification/migrations/0009_projectcertificatetype.py +++ b/django_project/certification/migrations/0008_projectcertificatetype.py @@ -21,7 +21,7 @@ class Migration(migrations.Migration): dependencies = [ ('base', '0006_auto_20210308_0244'), - ('certification', '0008_certificate_certificate_type'), + ('certification', '0007_certificatetype'), ] operations = [ diff --git a/django_project/certification/migrations/0010_course_certificate_type.py b/django_project/certification/migrations/0009_course_certificate_type.py similarity index 95% rename from django_project/certification/migrations/0010_course_certificate_type.py rename to django_project/certification/migrations/0009_course_certificate_type.py index 53e8b7e74..d44f9e233 100644 --- a/django_project/certification/migrations/0010_course_certificate_type.py +++ b/django_project/certification/migrations/0009_course_certificate_type.py @@ -18,7 +18,7 @@ def set_existing_certificate_type_value(apps, shcema_editor): class Migration(migrations.Migration): dependencies = [ - ('certification', '0009_projectcertificatetype'), + ('certification', '0008_projectcertificatetype'), ] operations = [ diff --git a/django_project/certification/models/certificate.py b/django_project/certification/models/certificate.py index e48bcdc06..109f54e2c 100644 --- a/django_project/certification/models/certificate.py +++ b/django_project/certification/models/certificate.py @@ -55,8 +55,6 @@ class Certificate(models.Model): author = models.ForeignKey(User, on_delete=models.CASCADE) course = models.ForeignKey(Course, on_delete=models.CASCADE) attendee = models.ForeignKey(Attendee, on_delete=models.CASCADE) - certificate_type = models.ForeignKey(CertificateType, - on_delete=models.PROTECT) objects = models.Manager() class Meta: From 22db5b54dfd3b0a5a87e5d8960063b8a7641a89c Mon Sep 17 00:00:00 2001 From: Sumandari Date: Thu, 16 Dec 2021 20:45:15 +0800 Subject: [PATCH 19/27] updated queryset in courseform --- django_project/certification/forms.py | 5 ++++ .../certification/views/certificate.py | 25 +++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/django_project/certification/forms.py b/django_project/certification/forms.py index 0034060bf..d186691db 100644 --- a/django_project/certification/forms.py +++ b/django_project/certification/forms.py @@ -21,6 +21,8 @@ ) from .models import ( CertifyingOrganisation, + CertificateType, + ProjectCertificateType, CourseConvener, CourseType, TrainingCenter, @@ -344,6 +346,9 @@ def __init__(self, *args, **kwargs): self.certifying_organisation self.fields['certifying_organisation'].widget = forms.HiddenInput() self.helper.add_input(Submit('submit', 'Submit')) + self.fields['certificate_type'].queryset = CertificateType.objects.filter( + projectcertificatetype__project=self.certifying_organisation.project + ) def save(self, commit=True): instance = super(CourseForm, self).save(commit=False) diff --git a/django_project/certification/views/certificate.py b/django_project/certification/views/certificate.py index 2c610d5b4..5e05a602c 100644 --- a/django_project/certification/views/certificate.py +++ b/django_project/certification/views/certificate.py @@ -32,6 +32,7 @@ import djstripe.settings from ..models import ( Certificate, + CertificateType, Course, Attendee, CertifyingOrganisation, @@ -234,7 +235,8 @@ def get_object(self, queryset=None): def generate_pdf( - pathname, project, course, attendee, certificate, current_site): + pathname, project, course, attendee, certificate, current_site, + wording='Has attended and completed the course:'): """Create the PDF object, using the response object as its file.""" # Register new font @@ -348,7 +350,7 @@ def generate_pdf( attendee.surname)) page.setFont('Noto-Regular', 16) page.drawCentredString( - center, 370, 'Has attended and completed the course:') + center, 370, wording) page.setFont('Noto-Bold', 20) page.drawCentredString( center, 335, course.course_type.name) @@ -456,7 +458,9 @@ def certificate_pdf_view(request, **kwargs): os.makedirs(makepath) generate_pdf( - pathname, project, course, attendee, certificate, current_site) + pathname, project, course, attendee, certificate, current_site, + course.certificate_type.wording + ) try: return FileResponse(open(pathname, 'rb'), content_type='application/pdf') @@ -650,7 +654,8 @@ def regenerate_certificate(request, **kwargs): organisation_slug = kwargs.pop('organisation_slug', None) course_slug = kwargs.pop('course_slug', None) pk = kwargs.pop('pk') - course = Course.objects.get(slug=course_slug) + course = Course.objects.get( + slug=course_slug).select_related('certificate_type') attendee = Attendee.objects.get(pk=pk) project = Project.objects.get(slug=project_slug) certificate = Certificate.objects.get(course=course, attendee=attendee) @@ -691,7 +696,9 @@ def regenerate_certificate(request, **kwargs): current_site = request.META['HTTP_HOST'] generate_pdf( - pathname, project, course, attendee, certificate, current_site) + pathname, project, course, attendee, certificate, current_site, + course.certificate_type.wording + ) try: return FileResponse(open(pathname, 'rb'), content_type='application/pdf') @@ -775,7 +782,8 @@ def regenerate_all_certificate(request, **kwargs): project_slug = kwargs.pop('project_slug', None) course_slug = kwargs.pop('course_slug', None) organisation_slug = kwargs.get('organisation_slug', None) - course = Course.objects.get(slug=course_slug) + course = Course.objects.get( + slug=course_slug).select_related('certificate_type') project = Project.objects.get(slug=project_slug) certifying_organisation = \ CertifyingOrganisation.objects.get(slug=organisation_slug) @@ -843,7 +851,8 @@ def regenerate_all_certificate(request, **kwargs): '/home/web/media', 'pdf/{}/{}'.format(project_folder, filename)) generate_pdf( - pathname, project, course, key, value, current_site) + pathname, project, course, key, value, current_site, + course.certificate_type.wording) messages.success(request, 'All certificates are updated', 'regenerate') return HttpResponseRedirect(url) @@ -914,6 +923,8 @@ def preview_certificate(request, **kwargs): organisation_slug = kwargs.pop('organisation_slug') convener_id = request.POST.get('course_convener', None) + # certificate_type_name = request.POST.get('certificate_type', None) + # certificate_type = CertificateType.objects.get(name=certificate_type_name) if convener_id is not None: # Get all posted data. course_convener = CourseConvener.objects.get(id=convener_id) From 2adf94ed5740c8bdb5d5dbd78c8b9484a92df2d0 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 17 Dec 2021 00:09:11 +0800 Subject: [PATCH 20/27] Part#4 update template --- django_project/certification/forms.py | 9 +++++---- django_project/certification/models/certificate.py | 1 - .../certification/templates/course/create.html | 5 +++++ .../certification/templates/course/update.html | 5 +++++ django_project/certification/views/certificate.py | 14 +++++++------- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/django_project/certification/forms.py b/django_project/certification/forms.py index d186691db..28fa4a397 100644 --- a/django_project/certification/forms.py +++ b/django_project/certification/forms.py @@ -22,7 +22,6 @@ from .models import ( CertifyingOrganisation, CertificateType, - ProjectCertificateType, CourseConvener, CourseType, TrainingCenter, @@ -346,9 +345,11 @@ def __init__(self, *args, **kwargs): self.certifying_organisation self.fields['certifying_organisation'].widget = forms.HiddenInput() self.helper.add_input(Submit('submit', 'Submit')) - self.fields['certificate_type'].queryset = CertificateType.objects.filter( - projectcertificatetype__project=self.certifying_organisation.project - ) + self.fields['certificate_type'].queryset = \ + CertificateType.objects.filter( + projectcertificatetype__project=self.certifying_organisation. + project + ) def save(self, commit=True): instance = super(CourseForm, self).save(commit=False) diff --git a/django_project/certification/models/certificate.py b/django_project/certification/models/certificate.py index 109f54e2c..a218731a8 100644 --- a/django_project/certification/models/certificate.py +++ b/django_project/certification/models/certificate.py @@ -9,7 +9,6 @@ from django.utils.translation import ugettext_lazy as _ from .course import Course from .attendee import Attendee -from .certificate_type import CertificateType def increment_id(project): diff --git a/django_project/certification/templates/course/create.html b/django_project/certification/templates/course/create.html index bc5db0f90..0fb35efda 100644 --- a/django_project/certification/templates/course/create.html +++ b/django_project/certification/templates/course/create.html @@ -99,6 +99,7 @@

    New Course for {{ organisation.name }}

    +

    @@ -130,6 +131,9 @@

    New Course for {{ organisation.name }}

    }else if($('input[id=id_end_date]').val() === ''){ $('#error-submit').html('Please choose end date.'); return false + }else if($('select[id=id_certificate_type]').val() === ''){ + $('#error-submit').html('Please choose certificate type.'); + return false } $('#preview-form input[name=course_convener]').val($('select[name=course_convener]').val()); @@ -138,6 +142,7 @@

    New Course for {{ organisation.name }}

    $('#preview-form input[name=start_date]').val($('input[id=id_start_date]').val()); $('#preview-form input[name=end_date]').val($('input[id=id_end_date]').val()); $('#preview-form input[name=trained_competence]').val($('input[id=id_trained_competence]').val()); + $('#preview-form input[name=certificate_type]').val($('select[id=id_certificate_type]').val()); } //check if browser supports file api and filereader features diff --git a/django_project/certification/templates/course/update.html b/django_project/certification/templates/course/update.html index b83e1ecb4..5cc923f05 100644 --- a/django_project/certification/templates/course/update.html +++ b/django_project/certification/templates/course/update.html @@ -106,6 +106,7 @@

    Update Course for {{ organisation.name }}

    +

    @@ -138,6 +139,9 @@

    Update Course for {{ organisation.name }}

    }else if($('input[id=id_end_date]').val() === ''){ $('#error-submit').html('Please choose end date.'); return false + }else if($('select[id=id_certificate_type]').val() === ''){ + $('#error-submit').html('Please choose certificate type.'); + return false } $('#preview-form input[name=course_convener]').val($('select[name=course_convener]').val()); @@ -146,6 +150,7 @@

    Update Course for {{ organisation.name }}

    $('#preview-form input[name=start_date]').val($('input[id=id_start_date]').val()); $('#preview-form input[name=end_date]').val($('input[id=id_end_date]').val()); $('#preview-form input[name=trained_competence]').val($('input[id=id_trained_competence]').val()); + $('#preview-form input[name=certificate_type]').val($('select[id=id_certificate_type]').val()); } //check if browser supports file api and filereader features diff --git a/django_project/certification/views/certificate.py b/django_project/certification/views/certificate.py index 5e05a602c..8e4846e7a 100644 --- a/django_project/certification/views/certificate.py +++ b/django_project/certification/views/certificate.py @@ -654,8 +654,7 @@ def regenerate_certificate(request, **kwargs): organisation_slug = kwargs.pop('organisation_slug', None) course_slug = kwargs.pop('course_slug', None) pk = kwargs.pop('pk') - course = Course.objects.get( - slug=course_slug).select_related('certificate_type') + course = Course.objects.get(slug=course_slug) attendee = Attendee.objects.get(pk=pk) project = Project.objects.get(slug=project_slug) certificate = Certificate.objects.get(course=course, attendee=attendee) @@ -782,8 +781,7 @@ def regenerate_all_certificate(request, **kwargs): project_slug = kwargs.pop('project_slug', None) course_slug = kwargs.pop('course_slug', None) organisation_slug = kwargs.get('organisation_slug', None) - course = Course.objects.get( - slug=course_slug).select_related('certificate_type') + course = Course.objects.get(slug=course_slug) project = Project.objects.get(slug=project_slug) certifying_organisation = \ CertifyingOrganisation.objects.get(slug=organisation_slug) @@ -923,8 +921,8 @@ def preview_certificate(request, **kwargs): organisation_slug = kwargs.pop('organisation_slug') convener_id = request.POST.get('course_convener', None) - # certificate_type_name = request.POST.get('certificate_type', None) - # certificate_type = CertificateType.objects.get(name=certificate_type_name) + certificate_type_id = request.POST.get('certificate_type', None) + certificate_type = CertificateType.objects.get(id=certificate_type_id) if convener_id is not None: # Get all posted data. course_convener = CourseConvener.objects.get(id=convener_id) @@ -960,7 +958,9 @@ def preview_certificate(request, **kwargs): current_site = request.META['HTTP_HOST'] generate_pdf( - response, project, course, attendee, certificate, current_site) + response, project, course, attendee, certificate, current_site, + certificate_type.wording + ) else: # When preview page is refreshed, the data is gone so user needs to From 5aeacd6db885fe80d399f50daa38fc86392d77ae Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 17 Dec 2021 00:12:41 +0800 Subject: [PATCH 21/27] fixed typo flake8 --- django_project/certification/forms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/django_project/certification/forms.py b/django_project/certification/forms.py index 28fa4a397..43780daef 100644 --- a/django_project/certification/forms.py +++ b/django_project/certification/forms.py @@ -347,9 +347,8 @@ def __init__(self, *args, **kwargs): self.helper.add_input(Submit('submit', 'Submit')) self.fields['certificate_type'].queryset = \ CertificateType.objects.filter( - projectcertificatetype__project=self.certifying_organisation. - project - ) + projectcertificatetype__project= + self.certifying_organisation.project) def save(self, commit=True): instance = super(CourseForm, self).save(commit=False) From 85bd0b4c8644b4bd557cd584a3399863f93f5f70 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 24 Dec 2021 17:45:36 +0800 Subject: [PATCH 22/27] updated unit test and fix failed test --- .../certification/tests/model_factories.py | 30 +++++--- .../certification/tests/test_models.py | 12 +--- .../tests/views/test_certificate_previews.py | 4 +- .../tests/views/test_certificate_type_view.py | 72 +++++++++++++++++++ .../certification/views/certificate.py | 15 ++-- 5 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 django_project/certification/tests/views/test_certificate_type_view.py diff --git a/django_project/certification/tests/model_factories.py b/django_project/certification/tests/model_factories.py index 49fb8cf4f..96a480403 100644 --- a/django_project/certification/tests/model_factories.py +++ b/django_project/certification/tests/model_factories.py @@ -6,6 +6,7 @@ from certification.models import ( Certificate, CertificateType, + ProjectCertificateType, Attendee, Course, CourseType, @@ -82,6 +83,19 @@ class Meta: author = factory.SubFactory(UserF) +class CertificateTypeF(factory.django.DjangoModelFactory): + """CertificateType model factory.""" + + class Meta: + model = CertificateType + + name = factory.sequence(lambda n: 'Test certificate type name %s' % n) + description = factory.sequence( + lambda n: 'Description certificate type %s' % n) + wording = factory.sequence( + lambda n: 'Wording certificate type %s' % n) + + class CourseF(factory.django.DjangoModelFactory): """Course model factory.""" @@ -98,6 +112,7 @@ class Meta: course_type = factory.SubFactory(CourseTypeF) training_center = factory.SubFactory(TrainingCenterF) author = factory.SubFactory(UserF) + certificate_type = factory.SubFactory(CertificateTypeF) class AttendeeF(factory.django.DjangoModelFactory): @@ -125,16 +140,14 @@ class Meta: attendee = factory.SubFactory(AttendeeF) -class CertificateTypeF(factory.django.DjangoModelFactory): +class ProjectCertificateTypeF(factory.django.DjangoModelFactory): + """ProjectCertificateType model factory.""" + class Meta: - model = CertificateType + model = ProjectCertificateType - name = factory.sequence(lambda n: 'Test certificate type name %s' % n) - description = factory.sequence( - lambda n: 'Description certificate type %s' % n) - wording = factory.sequence( - lambda n: 'Wording certificate type %s' % n) - order = factory.sequence(lambda n: n) + project = factory.SubFactory(ProjectF) + certificate_type = factory.SubFactory(CertificateTypeF) class CertificateF(factory.django.DjangoModelFactory): @@ -147,7 +160,6 @@ class Meta: course = factory.SubFactory(CourseF) attendee = factory.SubFactory(AttendeeF) author = factory.SubFactory(UserF) - certificate_type = factory.SubFactory(CertificateTypeF) class StatusF(factory.django.DjangoModelFactory): diff --git a/django_project/certification/tests/test_models.py b/django_project/certification/tests/test_models.py index bbd125d82..aa7b58672 100644 --- a/django_project/certification/tests/test_models.py +++ b/django_project/certification/tests/test_models.py @@ -110,14 +110,10 @@ class CertificateSetUp(SetUpMixin, TestCase): def test_Certificate_create(self): """Test certificate model creation.""" - certificate_type = CertificateTypeF.create() - model = CertificateF.create( - certificate_type=certificate_type - ) + model = CertificateF.create() # check if PK exists. self.assertTrue(model.pk is not None) - self.assertIsNotNone(model.certificate_type) def test_Certificate_delete(self): """Test certificate model deletion.""" @@ -128,11 +124,6 @@ def test_Certificate_delete(self): # check if deleted. self.assertTrue(model.pk is None) - def test_certificate_type_must_not_null(self): - msg = ('null value in column "certificate_type_id" ' - 'violates not-null constraint') - with self.assertRaisesMessage(IntegrityError, msg): - CertificateF.create(certificate_type=None) class CertificateTypeSetUp(SetUpMixin, TestCase): @@ -151,7 +142,6 @@ def test_CRUD_CertificateType(self): self.assertIn('Test certificate type name', model.name) self.assertIn('Description certificate type', model.description) self.assertIn('Wording certificate type', model.wording) - self.assertIsNotNone(model.order) self.assertEqual(model.__str__(), model.name) # diff --git a/django_project/certification/tests/views/test_certificate_previews.py b/django_project/certification/tests/views/test_certificate_previews.py index 7673b5acd..e8fdc79d5 100644 --- a/django_project/certification/tests/views/test_certificate_previews.py +++ b/django_project/certification/tests/views/test_certificate_previews.py @@ -9,7 +9,8 @@ CertifyingOrganisationF, CourseConvenerF, TrainingCenterF, - CourseTypeF + CourseTypeF, + CertificateTypeF ) @@ -46,6 +47,7 @@ def setUp(self): self.convener = CourseConvenerF.create() self.training_center = TrainingCenterF.create() self.course_type = CourseTypeF.create() + self.certificate_type = CertificateTypeF.create() @override_settings(VALID_DOMAIN=['testserver', ]) def tearDown(self): diff --git a/django_project/certification/tests/views/test_certificate_type_view.py b/django_project/certification/tests/views/test_certificate_type_view.py new file mode 100644 index 000000000..bfe0163e6 --- /dev/null +++ b/django_project/certification/tests/views/test_certificate_type_view.py @@ -0,0 +1,72 @@ +from bs4 import BeautifulSoup as Soup +from django.shortcuts import reverse +from django.test import TestCase, override_settings +from django.test.client import Client + +from base.tests.model_factories import ProjectF +from core.model_factories import UserF +from certification.tests.model_factories import ( + CertifyingOrganisationF, + CertificateTypeF, + ProjectCertificateTypeF +) + + +class TestCertificateTypesView(TestCase): + + def setUp(self): + self.project = ProjectF.create() + another_project = ProjectF.create() + self.certificate_type_1 = CertificateTypeF.create(name='type-1') + self.certificate_type_2 = CertificateTypeF.create(name='type-2') + ProjectCertificateTypeF.create( + project=self.project, certificate_type=self.certificate_type_1 + ) + ProjectCertificateTypeF.create( + project=another_project, certificate_type=self.certificate_type_2 + ) + self.user = UserF.create(**{ + 'username': 'tester', + 'password': 'password', + 'is_staff': True, + }) + self.user.set_password('password') + self.user.save() + + @override_settings(VALID_DOMAIN=['testserver', ]) + def test_certificate_type_view_contains_course_type(self): + """Test CertificateType list page.""" + + self.client.post('/set_language/', data={'language': 'en'}) + self.client.login(username='tester', password='password') + url = reverse('certificate-type-list', kwargs={ + 'project_slug': self.project.slug + }) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + # all certificate types should be displayed + self.assertContains(response, self.certificate_type_1.name) + self.assertContains(response, self.certificate_type_2.name) + + # only certificate types related to project in context_object ListView + self.assertEqual(len(response.context_data['object_list']), 1) + self.assertEqual( + response.context_data['object_list'].last().certificate_type, + self.certificate_type_1 + ) + + @override_settings(VALID_DOMAIN=['testserver', ]) + def test_update_project_certificate_view(self): + self.client.post('/set_language/', data={'language': 'en'}) + self.client.login(username='tester', password='password') + url = reverse('certificate-type-update', kwargs={ + 'project_slug': self.project.slug + }) + # choose certificate type-2 only + post_data = {'certificate_types': 'type-2'} + response = self.client.post(url, data=post_data, follow=True) + self.assertEqual(response.status_code, 200) + soup = Soup(response.content, "html5lib") + self.assertTrue(len(soup.find_all('input', checked=True)) == 1) + self.assertEqual(soup.find('input', checked=True)["value"], "type-2") diff --git a/django_project/certification/views/certificate.py b/django_project/certification/views/certificate.py index 8e4846e7a..d7d7dbcc6 100644 --- a/django_project/certification/views/certificate.py +++ b/django_project/certification/views/certificate.py @@ -922,7 +922,6 @@ def preview_certificate(request, **kwargs): convener_id = request.POST.get('course_convener', None) certificate_type_id = request.POST.get('certificate_type', None) - certificate_type = CertificateType.objects.get(id=certificate_type_id) if convener_id is not None: # Get all posted data. course_convener = CourseConvener.objects.get(id=convener_id) @@ -957,10 +956,16 @@ def preview_certificate(request, **kwargs): current_site = request.META['HTTP_HOST'] - generate_pdf( - response, project, course, attendee, certificate, current_site, - certificate_type.wording - ) + if certificate_type_id: + certificate_type = CertificateType.objects.get( + id=certificate_type_id) + generate_pdf( + response, project, course, attendee, certificate, current_site, + certificate_type.wording + ) + else: + generate_pdf( + response, project, course, attendee, certificate, current_site) else: # When preview page is refreshed, the data is gone so user needs to From cf89c41b5a57afa9b8eb5202cf521325f53fde02 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 24 Dec 2021 17:48:28 +0800 Subject: [PATCH 23/27] updated unit test and fix failed test --- .../certification/tests/views/test_certificate_type_view.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/django_project/certification/tests/views/test_certificate_type_view.py b/django_project/certification/tests/views/test_certificate_type_view.py index bfe0163e6..61a32f6d4 100644 --- a/django_project/certification/tests/views/test_certificate_type_view.py +++ b/django_project/certification/tests/views/test_certificate_type_view.py @@ -1,12 +1,10 @@ from bs4 import BeautifulSoup as Soup from django.shortcuts import reverse from django.test import TestCase, override_settings -from django.test.client import Client from base.tests.model_factories import ProjectF from core.model_factories import UserF from certification.tests.model_factories import ( - CertifyingOrganisationF, CertificateTypeF, ProjectCertificateTypeF ) From 13f8c1a18aee26ce549cb9db07d250a55b04a0a4 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 11 Feb 2022 09:00:46 +0800 Subject: [PATCH 24/27] fix typo --- .../certification/templates/certificate_type/list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_project/certification/templates/certificate_type/list.html b/django_project/certification/templates/certificate_type/list.html index 77e569a43..d742cbea9 100644 --- a/django_project/certification/templates/certificate_type/list.html +++ b/django_project/certification/templates/certificate_type/list.html @@ -38,6 +38,6 @@

    Certificate Types

    {% endfor %} - + {% endblock %} From 75fe979c24e1fe2c0b5d083c181174d4edce8f1d Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 11 Feb 2022 19:17:18 +0800 Subject: [PATCH 25/27] wrap CertificateType query in try catch block --- .../certification/views/certificate.py | 17 +++++++++++------ .../certification/views/certificate_type.py | 3 +-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/django_project/certification/views/certificate.py b/django_project/certification/views/certificate.py index d7d7dbcc6..a73cdc1ec 100644 --- a/django_project/certification/views/certificate.py +++ b/django_project/certification/views/certificate.py @@ -957,12 +957,17 @@ def preview_certificate(request, **kwargs): current_site = request.META['HTTP_HOST'] if certificate_type_id: - certificate_type = CertificateType.objects.get( - id=certificate_type_id) - generate_pdf( - response, project, course, attendee, certificate, current_site, - certificate_type.wording - ) + try: + certificate_type = CertificateType.objects.get( + id=certificate_type_id) + generate_pdf( + response, project, course, attendee, certificate, + current_site, certificate_type.wording + ) + except CertificateType.DoesNotExist: + generate_pdf( + response, project, course, attendee, certificate, + current_site) else: generate_pdf( response, project, course, attendee, certificate, current_site) diff --git a/django_project/certification/views/certificate_type.py b/django_project/certification/views/certificate_type.py index 9ad9a5bcf..ce5bf9955 100644 --- a/django_project/certification/views/certificate_type.py +++ b/django_project/certification/views/certificate_type.py @@ -42,8 +42,7 @@ def get_queryset(self): """Return certificate_types for a project.""" project = get_object_or_404(Project, slug=self.kwargs['project_slug']) - qs = ProjectCertificateType.objects.filter(project=project) - return qs + return ProjectCertificateType.objects.filter(project=project) def updateProjectCertificateView(request, project_slug): From 6560b4c732d3fe33fcb257997922e6c39693a6d2 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Sat, 12 Feb 2022 10:13:01 +0800 Subject: [PATCH 26/27] added unit test --- .../tests/views/test_certificate_previews.py | 63 ++++++++++++++++++- .../tests/views/test_course_view.py | 27 ++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/django_project/certification/tests/views/test_certificate_previews.py b/django_project/certification/tests/views/test_certificate_previews.py index e8fdc79d5..099a91bbf 100644 --- a/django_project/certification/tests/views/test_certificate_previews.py +++ b/django_project/certification/tests/views/test_certificate_previews.py @@ -1,4 +1,6 @@ # coding=utf-8 +import mock.mock +from unittest.mock import patch, call from django.urls import reverse from django.test import TestCase, override_settings from django.test.client import Client @@ -82,7 +84,8 @@ def test_preview_certificate_no_data_posted(self): self.assertEqual(response.status_code, 200) @override_settings(VALID_DOMAIN=['testserver', ]) - def test_preview_certificate_with_posted_data(self): + @patch('certification.views.certificate.generate_pdf') + def test_preview_certificate_with_posted_data(self, mock_gen_pdf): client = Client(HTTP_HOST='testserver') client.login(username='anita', password='password') post_data = { @@ -98,3 +101,61 @@ def test_preview_certificate_with_posted_data(self): 'organisation_slug': self.test_certifying_organisation.slug }), post_data) self.assertEqual(response.status_code, 200) + # Only 6 args in generate_pdf call, + # Since there's no CertificateType id in POST body + self.assertEqual(len(mock_gen_pdf.call_args[0]), 6) + self.assertIn(self.test_project, mock_gen_pdf.call_args[0]) + self.assertNotIn( + self.certificate_type.wording, mock_gen_pdf.call_args[0]) + + @override_settings(VALID_DOMAIN=['testserver', ]) + @patch('certification.views.certificate.generate_pdf') + def test_preview_certificate_with_posted_data_and_certificate_type( + self, mock_gen_pdf): + client = Client(HTTP_HOST='testserver') + client.login(username='anita', password='password') + post_data = { + 'course_convener': self.convener.pk, + 'training_center': self.training_center.pk, + 'course_type': self.course_type.pk, + 'start_date': '2018-01-01', + 'end_date': '2018-02-01', + 'template_certificate': '', + 'certificate_type': self.certificate_type.id + } + response = client.post(reverse('preview-certificate', kwargs={ + 'project_slug': self.test_project.slug, + 'organisation_slug': self.test_certifying_organisation.slug + }), post_data) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(mock_gen_pdf.call_args[0]), 7) + self.assertIn(self.test_project, mock_gen_pdf.call_args[0]) + self.assertIn(self.certificate_type.wording, mock_gen_pdf.call_args[0]) + + + @override_settings(VALID_DOMAIN=['testserver', ]) + @patch('certification.views.certificate.generate_pdf') + def test_preview_certificate_with_posted_data_cert_type_not_found( + self, mock_gen_pdf): + client = Client(HTTP_HOST='testserver') + client.login(username='anita', password='password') + post_data = { + 'course_convener': self.convener.pk, + 'training_center': self.training_center.pk, + 'course_type': self.course_type.pk, + 'start_date': '2018-01-01', + 'end_date': '2018-02-01', + 'template_certificate': '', + 'certificate_type': 99999 + } + response = client.post(reverse('preview-certificate', kwargs={ + 'project_slug': self.test_project.slug, + 'organisation_slug': self.test_certifying_organisation.slug + }), post_data) + self.assertEqual(response.status_code, 200) + # Only 6 args in generate_pdf call, + # Since there's the CertificateType id doesn't exist + self.assertEqual(len(mock_gen_pdf.call_args[0]), 6) + self.assertIn(self.test_project, mock_gen_pdf.call_args[0]) + self.assertNotIn( + self.certificate_type.wording, mock_gen_pdf.call_args[0]) diff --git a/django_project/certification/tests/views/test_course_view.py b/django_project/certification/tests/views/test_course_view.py index 3a1a25693..c3e50d130 100644 --- a/django_project/certification/tests/views/test_course_view.py +++ b/django_project/certification/tests/views/test_course_view.py @@ -1,5 +1,7 @@ # coding=utf-8 import logging +from bs4 import BeautifulSoup as Soup + from django.test import TestCase, override_settings from django.test.client import Client from django.urls import reverse @@ -7,6 +9,8 @@ ProjectF, UserF, CertifyingOrganisationF, + CertificateTypeF, + ProjectCertificateTypeF, CourseF ) @@ -41,6 +45,11 @@ def setUp(self): self.course = CourseF.create( certifying_organisation=self.certifying_organisation ) + self.certificate_type = CertificateTypeF.create() + self.project_cert_type = ProjectCertificateTypeF.create( + project=self.project, + certificate_type=self.certificate_type + ) @override_settings(VALID_DOMAIN=['testserver', ]) def tearDown(self): @@ -55,6 +64,24 @@ def tearDown(self): self.project.delete() self.user.delete() + @override_settings(VALID_DOMAIN=['testserver', ]) + def test_create_course_must_showing_CertificateTypes(self): + self.client.login(username='anita', password='password') + response = self.client.get(reverse('course-create', kwargs={ + 'project_slug': self.project.slug, + 'organisation_slug': self.certifying_organisation.slug, + })) + self.assertEqual(response.status_code, 200) + soup = Soup(response.content, "html5lib") + cert_type_option = soup.find( + 'select', + {'id': 'id_certificate_type'} + ).find_all('option') + self.assertIn( + self.certificate_type.name, + [cert_type.text for cert_type in cert_type_option] + ) + @override_settings(VALID_DOMAIN=['testserver', ]) def test_detail_view(self): client = Client() From f2254ea984d2c128727b394f03ae4d8ed6a8d3c6 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Sat, 12 Feb 2022 10:21:59 +0800 Subject: [PATCH 27/27] fixed typo flake8 --- .../certification/tests/views/test_certificate_previews.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django_project/certification/tests/views/test_certificate_previews.py b/django_project/certification/tests/views/test_certificate_previews.py index 099a91bbf..3faab7345 100644 --- a/django_project/certification/tests/views/test_certificate_previews.py +++ b/django_project/certification/tests/views/test_certificate_previews.py @@ -1,6 +1,5 @@ # coding=utf-8 -import mock.mock -from unittest.mock import patch, call +from unittest.mock import patch from django.urls import reverse from django.test import TestCase, override_settings from django.test.client import Client