From c77e25c0e4303a62b3f5ab183040cc1af721c069 Mon Sep 17 00:00:00 2001 From: suman Date: Mon, 17 May 2021 10:57:54 +0800 Subject: [PATCH 01/23] 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/23] 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/23] 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/23] 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 547869cd8cee5406492d447776567c7e0601bf8c Mon Sep 17 00:00:00 2001 From: Sumandari Date: Fri, 26 Nov 2021 16:35:57 +0800 Subject: [PATCH 05/23] 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 06/23] 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 07/23] 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 08/23] 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 09/23] 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 10/23] 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 11/23] 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 12/23] 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 13/23] 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 14/23] 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 15/23] 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 16/23] 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 17/23] 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 18/23] 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 19/23] 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 20/23] 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 2cfa12285b0a8d9983580516bc2d98d9ac082241 Mon Sep 17 00:00:00 2001 From: Sumandari Date: Mon, 17 Jan 2022 09:23:47 +0800 Subject: [PATCH 21/23] checklist app add and move order --- .../api_views/certificate_application.py | 0 .../migrations/0010_auto_20220115_1805.py | 33 ++++++++ .../models/certificate_checklist.py | 32 ++++++++ .../certificate_checklist/create.html | 20 +++++ .../certificate_management.html | 74 ++++++++++++++++++ .../certificate_management/test.html | 36 +++++++++ django_project/certification/urls.py | 19 +++++ .../certification/views/__init__.py | 2 + .../views/certificate_checklist.py | 75 +++++++++++++++++++ .../views/certificate_management.py | 67 +++++++++++++++++ .../certification/views/certificate_type.py | 2 +- .../jquery-ui/1.11.4/jquery-ui.min.css | 7 ++ .../jquery-ui/1.11.4/jquery-ui.min.js | 13 ++++ .../1.11.4/jquery-ui.structure.min.css | 5 ++ .../jquery-ui/1.11.4/jquery-ui.theme.min.css | 5 ++ .../jquery-ui/1.13.0/jquery-ui.min.css | 7 ++ .../jquery-ui/1.13.0/jquery-ui.min.js | 6 ++ .../1.13.0/jquery-ui.structure.min.css | 5 ++ .../jquery-ui/1.13.0/jquery-ui.theme.min.css | 5 ++ .../base_static/jquery/1.11.1/jquery.min.js | 4 + .../includes/base-auth-nav-left.html | 2 +- 21 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 django_project/certification/api_views/certificate_application.py create mode 100644 django_project/certification/migrations/0010_auto_20220115_1805.py create mode 100644 django_project/certification/models/certificate_checklist.py create mode 100644 django_project/certification/templates/certificate_checklist/create.html create mode 100644 django_project/certification/templates/certificate_management/certificate_management.html create mode 100644 django_project/certification/templates/certificate_management/test.html create mode 100644 django_project/certification/views/certificate_checklist.py create mode 100644 django_project/certification/views/certificate_management.py create mode 100644 django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.min.css create mode 100644 django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.min.js create mode 100644 django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.structure.min.css create mode 100644 django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.theme.min.css create mode 100644 django_project/core/base_static/jquery-ui/1.13.0/jquery-ui.min.css create mode 100644 django_project/core/base_static/jquery-ui/1.13.0/jquery-ui.min.js create mode 100644 django_project/core/base_static/jquery-ui/1.13.0/jquery-ui.structure.min.css create mode 100644 django_project/core/base_static/jquery-ui/1.13.0/jquery-ui.theme.min.css create mode 100644 django_project/core/base_static/jquery/1.11.1/jquery.min.js diff --git a/django_project/certification/api_views/certificate_application.py b/django_project/certification/api_views/certificate_application.py new file mode 100644 index 000000000..e69de29bb diff --git a/django_project/certification/migrations/0010_auto_20220115_1805.py b/django_project/certification/migrations/0010_auto_20220115_1805.py new file mode 100644 index 000000000..8d098eb4f --- /dev/null +++ b/django_project/certification/migrations/0010_auto_20220115_1805.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.18 on 2022-01-15 16:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0006_auto_20210308_0244'), + ('certification', '0009_course_certificate_type'), + ] + + operations = [ + migrations.AlterField( + model_name='course', + name='certificate_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='certification.CertificateType'), + ), + migrations.CreateModel( + name='CertificateChecklist', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('question', models.CharField(help_text='Question for certifying organisation applicant.', max_length=1000, unique=True)), + ('sort_number', models.SmallIntegerField(blank=True, help_text='The order in which this category is listed within a project', null=True)), + ('is_archived', models.BooleanField(default=False, help_text='Is this question archived?')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.Project')), + ], + options={ + 'unique_together': {('question', 'sort_number')}, + }, + ), + ] diff --git a/django_project/certification/models/certificate_checklist.py b/django_project/certification/models/certificate_checklist.py new file mode 100644 index 000000000..6e4c85827 --- /dev/null +++ b/django_project/certification/models/certificate_checklist.py @@ -0,0 +1,32 @@ +"""Certificate application model for certification apps.""" + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class CertificateChecklist(models.Model): + question = models.CharField( + help_text=_('Question for certifying organisation applicant.'), + max_length=1000, + unique=True, + ) + sort_number = models.SmallIntegerField( + help_text=_( + 'The order in which this category is listed within a ' + 'project'), + blank=True, + null=True + ) + is_archived = models.BooleanField( + help_text=_('Is this question archived?'), + default=False + ) + project = models.ForeignKey('base.Project', on_delete=models.CASCADE) + + class Meta: + unique_together = ( + ('question', 'sort_number'), + ) + + def __str__(self): + return f'[{self.project.name}] {self.question}' diff --git a/django_project/certification/templates/certificate_checklist/create.html b/django_project/certification/templates/certificate_checklist/create.html new file mode 100644 index 000000000..87ef260d1 --- /dev/null +++ b/django_project/certification/templates/certificate_checklist/create.html @@ -0,0 +1,20 @@ +{% extends "project_base.html" %} +{% load crispy_forms_tags %} + +{% block extra_js %} +{% endblock %} + +{% block page_title %} +

    Add New Checklist Question

    +{% endblock page_title %} + +{% block content %} + +
    + {% csrf_token %} + {{ form|crispy }} + +
    +{% endblock %} diff --git a/django_project/certification/templates/certificate_management/certificate_management.html b/django_project/certification/templates/certificate_management/certificate_management.html new file mode 100644 index 000000000..663a52bc7 --- /dev/null +++ b/django_project/certification/templates/certificate_management/certificate_management.html @@ -0,0 +1,74 @@ +{% extends "project_base.html" %} +{% load custom_markup %} + +{% block extra_js %} +{% endblock %} + +{% block content %} + + +
    +

    Certification Management

    +
    + + + + + + + + + + + + + + + {% csrf_token %} + {% for cer_type in certificate_types %} + + + + + + {% endfor %} + +
    Certificate TypeWordingApply
    {{ cer_type.name }}{{ cer_type.wording }} + +
    + + + + + + + + + + {# To submit order use change category.js #} +
      + {% for checklist in certificate_checklists %} +
    • + {{ checklist.question }} +
    • + {% endfor %} +
    + + +{% endblock %} diff --git a/django_project/certification/templates/certificate_management/test.html b/django_project/certification/templates/certificate_management/test.html new file mode 100644 index 000000000..0d1717a4a --- /dev/null +++ b/django_project/certification/templates/certificate_management/test.html @@ -0,0 +1,36 @@ + + + + + + jQuery UI Sortable - Default functionality + + + + + + + + + +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    • Item 4
    • +
    • Item 5
    • +
    • Item 6
    • +
    • Item 7
    • +
    + + + + \ No newline at end of file diff --git a/django_project/certification/urls.py b/django_project/certification/urls.py index f3427961c..2db2918c2 100644 --- a/django_project/certification/urls.py +++ b/django_project/certification/urls.py @@ -63,6 +63,13 @@ preview_certificate, CertificateRevokeView, + # Certificate Checklist + CertificateChecklistCreateView, + CertificateChecklistOrderSubmitView, + + # Certificate Management + CertificateManagementTemplateView, + # Certificate for certifying organisation. OrganisationCertificateCreateView, organisation_certificate_pdf_view, @@ -247,6 +254,18 @@ view=updateProjectCertificateView, name='certificate-type-update'), + # Certificate Checklist + url(regex='^(?P[\w-]+)/certificate-checklist/create/$', + view=CertificateChecklistCreateView.as_view(), + name='certificate-checklist-create'), + url(regex='^(?P[\w-]+)/certificate-checklist/submit-order/$', + view=CertificateChecklistOrderSubmitView.as_view(), + name='certificate-checklist-submit-order'), + + # Certificate Management + url(regex='^(?P[\w-]+)/certificate-management/$', + view=CertificateManagementTemplateView.as_view(), + name='certificate-management'), # Certificate. url(regex='^(?P[\w-]+)/certifyingorganisation/' diff --git a/django_project/certification/views/__init__.py b/django_project/certification/views/__init__.py index 9c8acf31c..40bb8a4b0 100644 --- a/django_project/certification/views/__init__.py +++ b/django_project/certification/views/__init__.py @@ -8,5 +8,7 @@ from .course_attendee import * from .validate import * from .certificate import * +from .certificate_checklist import * +from .certificate_management import * from .certificate_type import * from .certificate_organisation import * diff --git a/django_project/certification/views/certificate_checklist.py b/django_project/certification/views/certificate_checklist.py new file mode 100644 index 000000000..720202a44 --- /dev/null +++ b/django_project/certification/views/certificate_checklist.py @@ -0,0 +1,75 @@ +import json +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import Http404, HttpResponse +from django.shortcuts import get_object_or_404 +from django.views.generic import CreateView, UpdateView +from django.urls import reverse + +from base.models.project import Project +from certification.models.certificate_checklist import ( + CertificateChecklist +) + + +class CertificateChecklistMixin(): + """Mixin class to provide standard settings for Certificate Checklist.""" + model = CertificateChecklist + + +class CertificateChecklistCreateView(LoginRequiredMixin, + CertificateChecklistMixin, + CreateView): + """Create view for certificate checklist question.""" + + context_object_name = 'question' + template_name = 'certificate_checklist/create.html' + fields = ['question'] + + def get_success_url(self): + project_slug = self.kwargs.get('project_slug', None) + return reverse('certificate-management', kwargs={ + 'project_slug': project_slug + }) + + 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().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'] + return context + + def form_valid(self, form): + project_slug = self.kwargs.get('project_slug', None) + project = get_object_or_404(Project, slug=project_slug) + form.instance.project = project + return super(CertificateChecklistCreateView, self).form_valid(form) + + +class CertificateChecklistOrderSubmitView(LoginRequiredMixin, + CertificateChecklistMixin, + UpdateView): + """Update order view for Certificate Checklist.""" + + def post(self, request, *args, **kwargs): + checklist_json = request.body + + try: + checklist_request = json.loads(checklist_json) + except ValueError: + raise Http404( + 'Error json values' + ) + + for cl in checklist_request: + checklist = CertificateChecklist.objects.get(id=cl['id']) + if checklist: + checklist.sort_number = cl['sort_number'] + checklist.save() + + return HttpResponse('') diff --git a/django_project/certification/views/certificate_management.py b/django_project/certification/views/certificate_management.py new file mode 100644 index 000000000..417aaaeda --- /dev/null +++ b/django_project/certification/views/certificate_management.py @@ -0,0 +1,67 @@ +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 TemplateView + +from base.models.project import Project +from certification.models.certificate_type import ( + CertificateType, ProjectCertificateType +) +from certification.models.certificate_checklist import CertificateChecklist + + +class CertificateManagementTemplateView(LoginRequiredMixin, TemplateView): + template_name = 'certificate_management/certificate_management.html' + + 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( + CertificateManagementTemplateView, 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 + project = get_object_or_404(Project, slug=self.kwargs['project_slug']) + context['project_certificate_types'] = ProjectCertificateType.\ + objects.filter(project=project).all() + context['certificate_types'] = CertificateType.objects.all().order_by( + 'order' + ) + context['certificate_types_applied'] = ProjectCertificateType.\ + objects.filter(project=project).values_list( + 'certificate_type', flat=True) + + # checklist + context['certificate_checklists'] = CertificateChecklist.objects.\ + filter(project=project).all().order_by('sort_number') + return context + +# +# 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/certification/views/certificate_type.py b/django_project/certification/views/certificate_type.py index 9ad9a5bcf..eb169343c 100644 --- a/django_project/certification/views/certificate_type.py +++ b/django_project/certification/views/certificate_type.py @@ -65,5 +65,5 @@ def updateProjectCertificateView(request, project_slug): certificate_type=cer.certificate_type, project=project ).delete() return HttpResponseRedirect( - reverse('certificate-type-list', kwargs={'project_slug': project_slug}) + reverse('certificate-management', kwargs={'project_slug': project_slug}) ) diff --git a/django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.min.css b/django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.min.css new file mode 100644 index 000000000..efccc4767 --- /dev/null +++ b/django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.min.css @@ -0,0 +1,7 @@ +/*! jQuery UI - v1.11.4 - 2015-03-11 +* http://jqueryui.com +* Includes: core.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, draggable.css, menu.css, progressbar.css, resizable.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px +* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ + +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;min-height:0;font-size:100%}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:none}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{position:relative;margin:0;padding:3px 1em 3px .4em;cursor:pointer;min-height:0;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-button{display:inline-block;overflow:hidden;position:relative;text-decoration:none;cursor:pointer}.ui-selectmenu-button span.ui-icon{right:0.5em;left:auto;margin-top:-8px;position:absolute;top:50%}.ui-selectmenu-button span.ui-selectmenu-text{text-align:left;padding:0.4em 2.1em 0.4em 1em;display:block;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #e78f08;background:#f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#1c94c4}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#1c94c4;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #fbcb09;background:#fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#c77405}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#c77405;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #fbd850;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:bold;color:#eb8f00}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#eb8f00;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fed22f;background:#ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_ef8c08_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_228ef1_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_ffd27a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);border-radius:5px} \ No newline at end of file diff --git a/django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.min.js b/django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.min.js new file mode 100644 index 000000000..5824d1292 --- /dev/null +++ b/django_project/core/base_static/jquery-ui/1.11.4/jquery-ui.min.js @@ -0,0 +1,13 @@ +/*! jQuery UI - v1.11.4 - 2015-03-11 +* http://jqueryui.com +* Includes: core.js, widget.js, mouse.js, position.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, draggable.js, droppable.js, effect.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js, menu.js, progressbar.js, resizable.js, selectable.js, selectmenu.js, slider.js, sortable.js, spinner.js, tabs.js, tooltip.js +* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ + +(function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(jQuery)})(function(e){function t(t,s){var n,a,o,r=t.nodeName.toLowerCase();return"area"===r?(n=t.parentNode,a=n.name,t.href&&a&&"map"===n.nodeName.toLowerCase()?(o=e("img[usemap='#"+a+"']")[0],!!o&&i(o)):!1):(/^(input|select|textarea|button|object)$/.test(r)?!t.disabled:"a"===r?t.href||s:s)&&i(t)}function i(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}function s(e){for(var t,i;e.length&&e[0]!==document;){if(t=e.css("position"),("absolute"===t||"relative"===t||"fixed"===t)&&(i=parseInt(e.css("zIndex"),10),!isNaN(i)&&0!==i))return i;e=e.parent()}return 0}function n(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},e.extend(this._defaults,this.regional[""]),this.regional.en=e.extend(!0,{},this.regional[""]),this.regional["en-US"]=e.extend(!0,{},this.regional.en),this.dpDiv=a(e("
    "))}function a(t){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return t.delegate(i,"mouseout",function(){e(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).removeClass("ui-datepicker-next-hover")}).delegate(i,"mouseover",o)}function o(){e.datepicker._isDisabledDatepicker(v.inline?v.dpDiv.parent()[0]:v.input[0])||(e(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),e(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&e(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&e(this).addClass("ui-datepicker-next-hover"))}function r(t,i){e.extend(t,i);for(var s in i)null==i[s]&&(t[s]=i[s]);return t}function h(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.ui=e.ui||{},e.extend(e.ui,{version:"1.11.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({scrollParent:function(t){var i=this.css("position"),s="absolute"===i,n=t?/(auto|scroll|hidden)/:/(auto|scroll)/,a=this.parents().filter(function(){var t=e(this);return s&&"static"===t.css("position")?!1:n.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==i&&a.length?a:e(this[0].ownerDocument||document)},uniqueId:function(){var e=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++e)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,s){return!!e.data(t,s[3])},focusable:function(i){return t(i,!isNaN(e.attr(i,"tabindex")))},tabbable:function(i){var s=e.attr(i,"tabindex"),n=isNaN(s);return(n||s>=0)&&t(i,!n)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(t,i){function s(t,i,s,a){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,s&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),a&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],a=i.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+i]=function(t){return void 0===t?o["inner"+i].call(this):this.each(function(){e(this).css(a,s(this,t)+"px")})},e.fn["outer"+i]=function(t,n){return"number"!=typeof t?o["outer"+i].call(this,t):this.each(function(){e(this).css(a,s(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.fn.extend({focus:function(t){return function(i,s){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),s&&s.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),disableSelection:function(){var e="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.bind(e+".ui-disableSelection",function(e){e.preventDefault()})}}(),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(t){if(void 0!==t)return this.css("zIndex",t);if(this.length)for(var i,s,n=e(this[0]);n.length&&n[0]!==document;){if(i=n.css("position"),("absolute"===i||"relative"===i||"fixed"===i)&&(s=parseInt(n.css("zIndex"),10),!isNaN(s)&&0!==s))return s;n=n.parent()}return 0}}),e.ui.plugin={add:function(t,i,s){var n,a=e.ui[t].prototype;for(n in s)a.plugins[n]=a.plugins[n]||[],a.plugins[n].push([i,s[n]])},call:function(e,t,i,s){var n,a=e.plugins[t];if(a&&(s||e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType))for(n=0;a.length>n;n++)e.options[a[n][0]]&&a[n][1].apply(e.element,i)}};var l=0,u=Array.prototype.slice;e.cleanData=function(t){return function(i){var s,n,a;for(a=0;null!=(n=i[a]);a++)try{s=e._data(n,"events"),s&&s.remove&&e(n).triggerHandler("remove")}catch(o){}t(i)}}(e.cleanData),e.widget=function(t,i,s){var n,a,o,r,h={},l=t.split(".")[0];return t=t.split(".")[1],n=l+"-"+t,s||(s=i,i=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[l]=e[l]||{},a=e[l][t],o=e[l][t]=function(e,t){return this._createWidget?(arguments.length&&this._createWidget(e,t),void 0):new o(e,t)},e.extend(o,a,{version:s.version,_proto:e.extend({},s),_childConstructors:[]}),r=new i,r.options=e.widget.extend({},r.options),e.each(s,function(t,s){return e.isFunction(s)?(h[t]=function(){var e=function(){return i.prototype[t].apply(this,arguments)},n=function(e){return i.prototype[t].apply(this,e)};return function(){var t,i=this._super,a=this._superApply;return this._super=e,this._superApply=n,t=s.apply(this,arguments),this._super=i,this._superApply=a,t}}(),void 0):(h[t]=s,void 0)}),o.prototype=e.widget.extend(r,{widgetEventPrefix:a?r.widgetEventPrefix||t:t},h,{constructor:o,namespace:l,widgetName:t,widgetFullName:n}),a?(e.each(a._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete a._childConstructors):i._childConstructors.push(o),e.widget.bridge(t,o),o},e.widget.extend=function(t){for(var i,s,n=u.call(arguments,1),a=0,o=n.length;o>a;a++)for(i in n[a])s=n[a][i],n[a].hasOwnProperty(i)&&void 0!==s&&(t[i]=e.isPlainObject(s)?e.isPlainObject(t[i])?e.widget.extend({},t[i],s):e.widget.extend({},s):s);return t},e.widget.bridge=function(t,i){var s=i.prototype.widgetFullName||t;e.fn[t]=function(n){var a="string"==typeof n,o=u.call(arguments,1),r=this;return a?this.each(function(){var i,a=e.data(this,s);return"instance"===n?(r=a,!1):a?e.isFunction(a[n])&&"_"!==n.charAt(0)?(i=a[n].apply(a,o),i!==a&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):e.error("no such method '"+n+"' for "+t+" widget instance"):e.error("cannot call methods on "+t+" prior to initialization; "+"attempted to call method '"+n+"'")}):(o.length&&(n=e.widget.extend.apply(null,[n].concat(o))),this.each(function(){var t=e.data(this,s);t?(t.option(n||{}),t._init&&t._init()):e.data(this,s,new i(n,this))})),r}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
    ",options:{disabled:!1,create:null},_createWidget:function(t,i){i=e(i||this.defaultElement||this)[0],this.element=e(i),this.uuid=l++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=e(),this.hoverable=e(),this.focusable=e(),i!==this&&(e.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===i&&this.destroy()}}),this.document=e(i.style?i.ownerDocument:i.document||i),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(t,i){var s,n,a,o=t;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof t)if(o={},s=t.split("."),t=s.shift(),s.length){for(n=o[t]=e.widget.extend({},this.options[t]),a=0;s.length-1>a;a++)n[s[a]]=n[s[a]]||{},n=n[s[a]];if(t=s.pop(),1===arguments.length)return void 0===n[t]?null:n[t];n[t]=i}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=i}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled",!!t),t&&(this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus"))),this},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_on:function(t,i,s){var n,a=this;"boolean"!=typeof t&&(s=i,i=t,t=!1),s?(i=n=e(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),e.each(s,function(s,o){function r(){return t||a.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?a[o]:o).apply(a,arguments):void 0}"string"!=typeof o&&(r.guid=o.guid=o.guid||r.guid||e.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+a.eventNamespace,u=h[2];u?n.delegate(u,l,r):i.bind(l,r)})},_off:function(t,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(i).undelegate(i),this.bindings=e(this.bindings.not(t).get()),this.focusable=e(this.focusable.not(t).get()),this.hoverable=e(this.hoverable.not(t).get())},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,o=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(o)&&o.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var o,r=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),o=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),o&&e.effects&&e.effects.effect[r]?s[t](n):r!==t&&s[r]?s[r](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}}),e.widget;var d=!1;e(document).mouseup(function(){d=!1}),e.widget("ui.mouse",{version:"1.11.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(!d){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var i=this,s=1===t.which,n="string"==typeof this.options.cancel&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(t)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(t)!==!1,!this._mouseStarted)?(t.preventDefault(),!0):(!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return i._mouseMove(e)},this._mouseUpDelegate=function(e){return i._mouseUp(e)},this.document.bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),d=!0,!0)):!0}},_mouseMove:function(t){if(this._mouseMoved){if(e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button)return this._mouseUp(t);if(!t.which)return this._mouseUp(t)}return(t.which||t.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),d=!1,!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),function(){function t(e,t,i){return[parseFloat(e[0])*(p.test(e[0])?t/100:1),parseFloat(e[1])*(p.test(e[1])?i/100:1)]}function i(t,i){return parseInt(e.css(t,i),10)||0}function s(t){var i=t[0];return 9===i.nodeType?{width:t.width(),height:t.height(),offset:{top:0,left:0}}:e.isWindow(i)?{width:t.width(),height:t.height(),offset:{top:t.scrollTop(),left:t.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:t.outerWidth(),height:t.outerHeight(),offset:t.offset()}}e.ui=e.ui||{};var n,a,o=Math.max,r=Math.abs,h=Math.round,l=/left|center|right/,u=/top|center|bottom/,d=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,p=/%$/,f=e.fn.position;e.position={scrollbarWidth:function(){if(void 0!==n)return n;var t,i,s=e("
    "),a=s.children()[0];return e("body").append(s),t=a.offsetWidth,s.css("overflow","scroll"),i=a.offsetWidth,t===i&&(i=s[0].clientWidth),s.remove(),n=t-i},getScrollInfo:function(t){var i=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),s=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),n="scroll"===i||"auto"===i&&t.widthi?"left":t>0?"right":"center",vertical:0>a?"top":s>0?"bottom":"middle"};d>m&&m>r(t+i)&&(h.horizontal="center"),c>g&&g>r(s+a)&&(h.vertical="middle"),h.important=o(r(t),r(i))>o(r(s),r(a))?"horizontal":"vertical",n.using.call(this,e,h)}),u.offset(e.extend(M,{using:l}))})},e.ui.position={fit:{left:function(e,t){var i,s=t.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=e.left-t.collisionPosition.marginLeft,h=n-r,l=r+t.collisionWidth-a-n;t.collisionWidth>a?h>0&&0>=l?(i=e.left+h+t.collisionWidth-a-n,e.left+=h-i):e.left=l>0&&0>=h?n:h>l?n+a-t.collisionWidth:n:h>0?e.left+=h:l>0?e.left-=l:e.left=o(e.left-r,e.left)},top:function(e,t){var i,s=t.within,n=s.isWindow?s.scrollTop:s.offset.top,a=t.within.height,r=e.top-t.collisionPosition.marginTop,h=n-r,l=r+t.collisionHeight-a-n;t.collisionHeight>a?h>0&&0>=l?(i=e.top+h+t.collisionHeight-a-n,e.top+=h-i):e.top=l>0&&0>=h?n:h>l?n+a-t.collisionHeight:n:h>0?e.top+=h:l>0?e.top-=l:e.top=o(e.top-r,e.top)}},flip:{left:function(e,t){var i,s,n=t.within,a=n.offset.left+n.scrollLeft,o=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=e.left-t.collisionPosition.marginLeft,u=l-h,d=l+t.collisionWidth-o-h,c="left"===t.my[0]?-t.elemWidth:"right"===t.my[0]?t.elemWidth:0,p="left"===t.at[0]?t.targetWidth:"right"===t.at[0]?-t.targetWidth:0,f=-2*t.offset[0];0>u?(i=e.left+c+p+f+t.collisionWidth-o-a,(0>i||r(u)>i)&&(e.left+=c+p+f)):d>0&&(s=e.left-t.collisionPosition.marginLeft+c+p+f-h,(s>0||d>r(s))&&(e.left+=c+p+f))},top:function(e,t){var i,s,n=t.within,a=n.offset.top+n.scrollTop,o=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=e.top-t.collisionPosition.marginTop,u=l-h,d=l+t.collisionHeight-o-h,c="top"===t.my[1],p=c?-t.elemHeight:"bottom"===t.my[1]?t.elemHeight:0,f="top"===t.at[1]?t.targetHeight:"bottom"===t.at[1]?-t.targetHeight:0,m=-2*t.offset[1];0>u?(s=e.top+p+f+m+t.collisionHeight-o-a,(0>s||r(u)>s)&&(e.top+=p+f+m)):d>0&&(i=e.top-t.collisionPosition.marginTop+p+f+m-h,(i>0||d>r(i))&&(e.top+=p+f+m))}},flipfit:{left:function(){e.ui.position.flip.left.apply(this,arguments),e.ui.position.fit.left.apply(this,arguments)},top:function(){e.ui.position.flip.top.apply(this,arguments),e.ui.position.fit.top.apply(this,arguments)}}},function(){var t,i,s,n,o,r=document.getElementsByTagName("body")[0],h=document.createElement("div");t=document.createElement(r?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},r&&e.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(o in s)t.style[o]=s[o];t.appendChild(h),i=r||document.documentElement,i.insertBefore(t,i.firstChild),h.style.cssText="position: absolute; left: 10.7432222px;",n=e(h).offset().left,a=n>10&&11>n,t.innerHTML="",i.removeChild(t)}()}(),e.ui.position,e.widget("ui.accordion",{version:"1.11.4",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var t=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),t.collapsible||t.active!==!1&&null!=t.active||(t.active=0),this._processPanels(),0>t.active&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").removeUniqueId(),this._destroyIcons(),e=this.headers.next().removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").css("display","").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&e.css("height","")},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):("event"===e&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),"collapsible"!==e||t||this.options.active!==!1||this._activate(0),"icons"===e&&(this._destroyIcons(),t&&this._createIcons()),"disabled"===e&&(this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)),void 0)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var i=e.ui.keyCode,s=this.headers.length,n=this.headers.index(t.target),a=!1;switch(t.keyCode){case i.RIGHT:case i.DOWN:a=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:a=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(t);break;case i.HOME:a=this.headers[0];break;case i.END:a=this.headers[s-1]}a&&(e(t.target).attr("tabIndex",-1),e(a).attr("tabIndex",0),a.focus(),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t=this.options;this._processPanels(),t.active===!1&&t.collapsible===!0||!this.headers.length?(t.active=!1,this.active=e()):t.active===!1?this._activate(0):this.active.length&&!e.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=e()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var e=this.headers,t=this.panels;this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-state-default ui-corner-all"),this.panels=this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide(),t&&(this._off(e.not(this.headers)),this._off(t.not(this.panels)))},_refresh:function(){var t,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(){var t=e(this),i=t.uniqueId().attr("id"),s=t.next(),n=s.uniqueId().attr("id");t.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(t=n.height(),this.element.siblings(":visible").each(function(){var i=e(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(t-=i.outerHeight(!0))}),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===s&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var i=this._findActive(t)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):e()},_setupEvents:function(t){var i={keydown:"_keydown"};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n[0]===s[0],o=a&&i.collapsible,r=o?e():n.next(),h=s.next(),l={oldHeader:s,oldPanel:h,newHeader:o?e():n,newPanel:r};t.preventDefault(),a&&!i.collapsible||this._trigger("beforeActivate",t,l)===!1||(i.active=o?!1:this.headers.index(n),this.active=a?e():n,this._toggle(l),s.removeClass("ui-accordion-header-active ui-state-active"),i.icons&&s.children(".ui-accordion-header-icon").removeClass(i.icons.activeHeader).addClass(i.icons.header),a||(n.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),i.icons&&n.children(".ui-accordion-header-icon").removeClass(i.icons.header).addClass(i.icons.activeHeader),n.next().addClass("ui-accordion-content-active")))},_toggle:function(t){var i=t.newPanel,s=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,t):(s.hide(),i.show(),this._toggleComplete(t)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(e(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(e,t,i){var s,n,a,o=this,r=0,h=e.css("box-sizing"),l=e.length&&(!t.length||e.index()",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},items:"> *",menus:"ul",position:{my:"left-1 top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item":function(e){e.preventDefault()},"click .ui-menu-item":function(t){var i=e(t.target);!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(t),t.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(t):!this.element.is(":focus")&&e(this.document[0].activeElement).closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){if(!this.previousFilter){var i=e(t.currentTarget); +i.siblings(".ui-state-active").removeClass("ui-state-active"),this.focus(t,i)}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var i=this.active||this.element.find(this.options.items).eq(0);t||this.focus(e,i)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(e){this._closeOnDocumentClick(e)&&this.collapseAll(e),this.mouseHandled=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-menu-icons ui-front").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").removeUniqueId().removeClass("ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){var i,s,n,a,o=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:o=!1,s=this.previousFilter||"",n=String.fromCharCode(t.keyCode),a=!1,clearTimeout(this.filterTimer),n===s?a=!0:n=s+n,i=this._filterMenuItems(n),i=a&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(t.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(t,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}o&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.is("[aria-haspopup='true']")?this.expand(e):this.select(e))},refresh:function(){var t,i,s=this,n=this.options.icons.submenu,a=this.element.find(this.options.menus);this.element.toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length),a.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-front").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),i=t.parent(),s=e("").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);i.attr("aria-haspopup","true").prepend(s),t.attr("aria-labelledby",i.attr("id"))}),t=a.add(this.element),i=t.find(this.options.items),i.not(".ui-menu-item").each(function(){var t=e(this);s._isDivider(t)&&t.addClass("ui-widget-content ui-menu-divider")}),i.not(".ui-menu-item, .ui-menu-divider").addClass("ui-menu-item").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(e,t){"icons"===e&&this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(t.submenu),"disabled"===e&&this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this._super(e,t)},focus:function(e,t){var i,s;this.blur(e,e&&"focus"===e.type),this._scrollIntoView(t),this.active=t.first(),s=this.active.addClass("ui-state-focus").removeClass("ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),this.active.parent().closest(".ui-menu-item").addClass("ui-state-active"),e&&"keydown"===e.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=t.children(".ui-menu"),i.length&&e&&/^mouse/.test(e.type)&&this._startOpening(i),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var i,s,n,a,o,r;this._hasScroll()&&(i=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,n=t.offset().top-this.activeMenu.offset().top-i-s,a=this.activeMenu.scrollTop(),o=this.activeMenu.height(),r=t.outerHeight(),0>n?this.activeMenu.scrollTop(a+n):n+r>o&&this.activeMenu.scrollTop(a+n-o+r))},blur:function(e,t){t||clearTimeout(this.timer),this.active&&(this.active.removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active}))},_startOpening:function(e){clearTimeout(this.timer),"true"===e.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(e)},this.delay))},_open:function(t){var i=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(t,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(t),this.activeMenu=s},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find(".ui-state-active").not(".ui-state-focus").removeClass("ui-state-active")},_closeOnDocumentClick:function(t){return!e(t.target).closest(".ui-menu").length},_isDivider:function(e){return!/[^\-\u2014\u2013\s]/.test(e.text())},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,i){var s;this.active&&(s="first"===e||"last"===e?this.active["first"===e?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[e+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[t]()),this.focus(i,s)},nextPage:function(t){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=e(this),0>i.offset().top-s-n}),this.focus(t,i)):this.focus(t,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(t),void 0)},previousPage:function(t){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=e(this),i.offset().top-s+n>0}),this.focus(t,i)):this.focus(t,this.activeMenu.find(this.options.items).first())),void 0):(this.next(t),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,void 0;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),void 0):(this._searchTimeout(e),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(e),this._change(e),void 0)}}),this._initSource(),this.menu=e("