Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c77e25c
extract zipfile inside zipfile in sample data lesson
sumandari May 17, 2021
d09aeb5
update unit test for download sample data lesson
sumandari May 17, 2021
0092fff
update unit test
sumandari May 17, 2021
7de9b96
fixed dockerfile for development and the documentation
Nov 23, 2021
a35044f
modified delete convener mechanism
sumandari Nov 26, 2021
d5427d7
add unit test
sumandari Nov 26, 2021
745b191
fix typo flake8
sumandari Nov 26, 2021
547869c
fixed raising ValidationError in validate_email_address
sumandari Nov 26, 2021
79e0987
fixed typo flake8
sumandari Nov 26, 2021
bfab4ac
Merge pull request #1374 from sumandari/fix_dev_docker_and_readme
Nov 26, 2021
f4b45d3
Merge branch 'develop' of https://github.com/kartoza/prj.app into 137…
sumandari Nov 26, 2021
0e1a285
Merge branch 'develop' of https://github.com/kartoza/prj.app into 137…
sumandari Nov 26, 2021
7f837e5
Merge pull request #1376 from sumandari/1372_fix_update_sustaining_me…
Dec 2, 2021
c1a2cb5
Merge pull request #1341 from sumandari/1328_lesson_extract_zip_in_zi…
Dec 2, 2021
613dbe7
added CertificateType model
sumandari Dec 3, 2021
a1c5b72
added admin class
sumandari Dec 3, 2021
3fb6b64
added CertificateType as foreign key in Certificate models and update…
sumandari Dec 3, 2021
af6b582
updated unittest
sumandari Dec 3, 2021
ec6763c
added project certificate_type
sumandari Dec 10, 2021
1f76a37
part#2: enable certification manager to manage certificate type list
sumandari Dec 10, 2021
935b99a
fixed typo flake8
sumandari Dec 11, 2021
45ca0e3
Part#3: added certificate_type in courseform
sumandari Dec 11, 2021
5c798d9
removed certificate_type in certificate model
sumandari Dec 11, 2021
22db5b5
updated queryset in courseform
sumandari Dec 16, 2021
2adf94e
Part#4 update template
sumandari Dec 16, 2021
5aeacd6
fixed typo flake8
sumandari Dec 16, 2021
85bd0b4
updated unit test and fix failed test
sumandari Dec 24, 2021
cf89c41
updated unit test and fix failed test
sumandari Dec 24, 2021
b2895c7
Merge pull request #1375 from sumandari/1373_delete_converner_mechanism
Feb 11, 2022
13f8c1a
fix typo
sumandari Feb 11, 2022
75fe979
wrap CertificateType query in try catch block
sumandari Feb 11, 2022
6560b4c
added unit test
sumandari Feb 12, 2022
4398bef
Merge branch 'develop' of https://github.com/kartoza/prj.app into 137…
sumandari Feb 12, 2022
f2254ea
fixed typo flake8
sumandari Feb 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -54,6 +54,7 @@ repeatable steps:
Windows:

```
copy docker-compose.override.example.yml docker-compose.override.yml
make-devbuild.bat
make-devweb.bat
```
Expand Down
6 changes: 3 additions & 3 deletions deployment/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ MAINTAINER Tim Sutton<tim@kartoza.com>
#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

Expand All @@ -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


Expand Down Expand Up @@ -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

# --------------------------------------------------------
Expand Down
11 changes: 11 additions & 0 deletions django_project/certification/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -34,6 +35,15 @@ def queryset(self, request):
return query_set


class CertificateTypeAdmin(admin.ModelAdmin):
"""CertificateType admin model."""

list_display = ('name', 'wording', 'order')
list_editable = ('order', )
search_fields = ('name', 'wording')
ordering = ('order', )


class AttendeeAdmin(admin.ModelAdmin):
"""Attendee admin model."""
list_display = ('firstname', 'surname', 'email', 'certifying_organisation')
Expand Down Expand Up @@ -163,6 +173,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)
Expand Down
12 changes: 11 additions & 1 deletion django_project/certification/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from .models import (
CertifyingOrganisation,
CertificateType,
CourseConvener,
CourseType,
TrainingCenter,
Expand Down Expand Up @@ -156,6 +157,7 @@ class Meta:
'user',
'degree',
'signature',
'is_active',
)

def __init__(self, *args, **kwargs):
Expand All @@ -171,6 +173,7 @@ def __init__(self, *args, **kwargs):
Field('user', css_class='form-control chosen-select'),
Field('degree', css_class='form-control'),
Field('signature', css_class='form-control'),
Field('is_active', css_class='checkbox-primary'),
)
)
self.helper.layout = layout
Expand Down Expand Up @@ -303,6 +306,7 @@ class Meta:
'end_date',
'template_certificate',
'certifying_organisation',
'certificate_type',
)

def __init__(self, *args, **kwargs):
Expand All @@ -322,14 +326,16 @@ 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
self.helper.html5_required = False
super(CourseForm, self).__init__(*args, **kwargs)
self.fields['course_convener'].queryset = \
CourseConvener.objects.filter(
certifying_organisation=self.certifying_organisation)
certifying_organisation=self.certifying_organisation,
is_active=True)
self.fields['course_convener'].label_from_instance = \
lambda obj: "%s <%s>" % (obj.user.get_full_name(), obj)
self.fields['course_type'].queryset = \
Expand All @@ -342,6 +348,10 @@ 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)
Expand Down
34 changes: 34 additions & 0 deletions django_project/certification/migrations/0007_certificatetype.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 2.2.18 on 2021-12-03 00:56

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',
wording='Has attended and completed the course:',
order=0
)


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, unique=True)),
('description', models.TextField(blank=True, help_text='Certificate type description - 1000 characters limit.', max_length=1000, null=True)),
('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)),
],
),

migrations.RunPython(add_certification_type_as_existing_value, reverse_code=migrations.RunPython.noop),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.18 on 2021-11-26 04:51

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('certification', '0006_auto_20210730_0615'),
]

operations = [
migrations.AddField(
model_name='courseconvener',
name='is_active',
field=models.BooleanField(default=True, help_text='Inactive Convener will not be available in your organisation list.'),
),
]
Original file line number Diff line number Diff line change
@@ -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', '0007_certificatetype'),
]

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),
]
Original file line number Diff line number Diff line change
@@ -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', '0008_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,
),

]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 2.2.18 on 2022-02-12 02:17

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('certification', '0007_courseconvener_is_active'),
('certification', '0009_course_certificate_type'),
]

operations = [
]
1 change: 1 addition & 0 deletions django_project/certification/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
55 changes: 55 additions & 0 deletions django_project/certification/models/certificate_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Certificate type model for certification app"""

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(
help_text=_('Certificate type.'),
max_length=200,
null=False,
blank=False,
unique=True,
)
Comment on lines +10 to +16

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name = models.CharField(
help_text=_('Certificate type.'),
max_length=200,
null=False,
blank=False,
unique=True,
)
name = models.CharField(help_text=_("Certificate type."), max_length=200, unique=True)

False is the default value Django uses for null, so null=False can be removed. Explained here.

Similarly, redundant default arguments can be removed.


description = models.TextField(
help_text=_('Certificate type description - 1000 characters limit.'),
max_length=1000,
null=True,
blank=True,
)
Comment on lines +18 to +23

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description = models.TextField(
help_text=_('Certificate type description - 1000 characters limit.'),
max_length=1000,
null=True,
blank=True,
)
description = models.TextField(
help_text=_("Certificate type description - 1000 characters limit."),
max_length=1000,
default="",
blank=True,
)

null=True on a string field causes inconsistent data types because the value can be either str or None. This adds complexity and maybe bugs, but can be solved by replacing null=True with default="". More details.


wording = models.CharField(
help_text=_(
'Wording that will be placed on certificate. '
'e.g. "Has attended and completed".'
),
max_length=500,
null=False,
blank=False
)
Comment on lines +25 to +33

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
wording = models.CharField(
help_text=_(
'Wording that will be placed on certificate. '
'e.g. "Has attended and completed".'
),
max_length=500,
null=False,
blank=False
)
wording = models.CharField(
help_text=_(
'Wording that will be placed on certificate. e.g. "Has attended and completed".'
),
max_length=500,
)

Likewise, redundant default arguments can be removed.

Likewise, redundant default arguments can be removed.


order = models.IntegerField(
blank=True,
null=True,
unique=True
)

def __str__(self):
return self.name


class ProjectCertificateType(models.Model):
"""A model to store a certificate type linked to a project"""

project = models.ForeignKey(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Django automatically creates a related_name if it's not set. If it were set then a more readable and explicit relationship is set up. Explained here.

Project,
on_delete=models.CASCADE
)
certificate_type = models.ForeignKey(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, with an explicit related_name would be better.

CertificateType,
on_delete=models.CASCADE
)
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
3 changes: 3 additions & 0 deletions django_project/certification/models/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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)
Comment on lines +90 to +91

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
certificate_type = models.ForeignKey(
CertificateType, on_delete=models.PROTECT, null=True)
certificate_type = models.ForeignKey(
CertificateType, on_delete=models.PROTECT, null=True, blank=True
)

Django automatically creates a related_name if it's not set. If it were set then a more readable and explicit relationship is set up. More details.

Expect unwanted behavior if null and blank are different values: null controls if the database allows no value for certificate_type and blank controls if the application allows no value for certificate_type. Consider setting null and blank to the same value for certificate_type. More details.

author = models.ForeignKey(User, on_delete=models.CASCADE)
objects = models.Manager()

Expand Down
6 changes: 6 additions & 0 deletions django_project/certification/models/course_convener.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class CourseConvener(models.Model):
null=True
)

is_active = models.BooleanField(
help_text=_('Inactive Convener will not be available in your '
'organisation list.'),
default=True
)

class Meta:
ordering = ['user']

Expand Down
Loading