diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..345c303 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,6 @@ +name-template: v$NEXT_PATCH_VERSION +tag-template: v$NEXT_PATCH_VERSION +template: | + ## What’s Changed + + $CHANGES diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98c503c..a491999 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: '/snapshots/' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.1.0 diff --git a/.pylintrc b/.pylintrc index 43d2e79..51afeeb 100644 --- a/.pylintrc +++ b/.pylintrc @@ -7,7 +7,7 @@ extension-pkg-whitelist= # Add files or directories to the blacklist. They should be base names, not # paths. -ignore=CVS,tests,migrations,local_settings.py +ignore=CVS,tests,migrations,local_settings.py,snapshots # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. @@ -126,7 +126,8 @@ disable=print-statement, next-method-defined, dict-items-not-iterating, dict-keys-not-iterating, - dict-values-not-iterating + dict-values-not-iterating, + bad-continuation # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/MANIFEST.in b/MANIFEST.in index 63636f2..f72dde5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,5 @@ include LICENSE include README.md recursive-include small_small_hr/static * recursive-include small_small_hr/templates * + +recursive-exclude tests * diff --git a/requirements/dev.in b/requirements/dev.in index 4107e7c..9b989f4 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -20,3 +20,5 @@ black pre-commit mypy tblib +snapshottest +freezegun diff --git a/requirements/dev.txt b/requirements/dev.txt index 3e1d5ce..1e597e5 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -6,77 +6,88 @@ # appdirs==1.4.4 # via black, virtualenv -asgiref==3.2.7 # via django -astroid==2.4.1 # via pylint +asgiref==3.2.10 # via django +astroid==2.4.2 # via pylint attrs==19.3.0 # via black autopep8==1.5.3 babel==2.8.0 # via django-phonenumber-field -backcall==0.1.0 # via ipython +backcall==0.2.0 # via ipython black==19.10b0 cfgv==3.1.0 # via pre-commit click==7.1.2 # via black coverage==5.1 decorator==4.4.2 # via ipython, traitlets distlib==0.3.0 # via virtualenv +django-braces==1.14.0 # via django-model-reviews +django-contrib-comments==1.9.2 # via django-model-reviews django-crispy-forms==1.9.1 +django-js-asset==1.2.2 # via django-mptt +django-model-reviews==1.0.2 +django-mptt==0.11.0 django-phonenumber-field==4.0.0 django-private-storage==2.2.2 -django==3.0.7 # via django-phonenumber-field, djangorestframework, model-mommy +django==3.0.7 # via django-braces, django-contrib-comments, django-model-reviews, django-mptt, django-phonenumber-field, djangorestframework, model-mommy djangorestframework==3.11.0 +fastdiff==0.2.0 # via snapshottest filelock==3.0.12 # via tox, virtualenv -flake8==3.8.2 -identify==1.4.19 # via pre-commit +flake8==3.8.3 +freezegun==0.3.15 +identify==1.4.20 # via pre-commit importlib-metadata==1.6.1 # via flake8, importlib-resources, pluggy, pre-commit, tox, virtualenv -importlib-resources==1.5.0 # via pre-commit, virtualenv -ipdb==0.13.2 +importlib-resources==2.0.1 # via pre-commit, virtualenv +ipdb==0.13.3 ipython-genutils==0.2.0 # via traitlets ipython==7.15.0 # via ipdb isort==4.3.21 -jedi==0.17.0 # via ipython +jedi==0.17.1 # via ipython lazy-object-proxy==1.4.3 # via astroid mccabe==0.6.1 # via flake8, pylint model-mommy==2.0.0 mypy-extensions==0.4.3 # via mypy -mypy==0.780 +mypy==0.782 nodeenv==1.4.0 # via pre-commit packaging==20.4 # via tox parso==0.7.0 # via jedi pathspec==0.8.0 # via black pep8==1.7.1 pexpect==4.8.0 # via ipython -phonenumberslite==8.12.5 +phonenumberslite==8.12.6 pickleshare==0.7.5 # via ipython pillow==7.1.2 pluggy==0.13.1 # via tox -pre-commit==2.4.0 +pre-commit==2.5.1 prompt-toolkit==3.0.5 # via ipython psycopg2-binary==2.8.5 ptyprocess==0.6.0 # via pexpect -py==1.8.1 # via tox +py==1.8.2 # via tox pycodestyle==2.6.0 pydocstyle==5.0.2 pyflakes==2.2.0 # via flake8 pygments==2.6.1 # via ipython pylint-django==2.0.15 pylint-plugin-utils==0.6 # via pylint-django -pylint==2.5.2 +pylint==2.5.3 pyparsing==2.4.7 # via packaging +python-dateutil==2.8.1 # via freezegun pytz==2020.1 # via babel, django pyyaml==5.3.1 # via pre-commit -regex==2020.5.14 # via black -six==1.15.0 # via astroid, packaging, tox, traitlets, virtualenv +regex==2020.6.8 # via black +six==1.15.0 # via astroid, django-braces, django-contrib-comments, freezegun, packaging, python-dateutil, snapshottest, tox, traitlets, virtualenv +snapshottest==0.5.1 snowballstemmer==2.0.0 # via pydocstyle sorl-thumbnail==12.6.3 sqlparse==0.3.1 # via django tblib==1.6.0 +termcolor==1.1.0 # via snapshottest toml==0.10.1 # via autopep8, black, pre-commit, pylint, tox tox==3.15.2 traitlets==4.3.3 # via ipython typed-ast==1.4.1 # via astroid, black, mypy typing-extensions==3.7.4.2 # via mypy -virtualenv==20.0.21 # via pre-commit, tox +virtualenv==20.0.25 # via pre-commit, tox voluptuous==0.11.7 -wcwidth==0.2.3 # via prompt-toolkit +wasmer==0.4.1 # via fastdiff +wcwidth==0.2.5 # via prompt-toolkit wrapt==1.12.1 # via astroid yapf==0.30.0 zipp==3.1.0 # via importlib-metadata, importlib-resources diff --git a/setup.cfg b/setup.cfg index c445da7..3475aba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,7 @@ line_length = 88 [flake8] max-line-length=90 -exclude = migrations +exclude = migrations, snapshots [pycodestyle] max-line-length=90 diff --git a/setup.py b/setup.py index 9e7b673..0300348 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,34 @@ -""" -Setup.py for small_small_hr -""" -from os import path +"""Setup.py for small_small_hr.""" +import os +import sys from setuptools import find_packages, setup +import small_small_hr as sshr + # read the contents of your README file with open( - path.join(path.abspath(path.dirname(__file__)), "README.md"), - encoding="utf-8") as f: + os.path.join(os.path.abspath(os.path.dirname(__file__)), "README.md"), + encoding="utf-8", +) as f: LONG_DESCRIPTION = f.read() +# convenience command to publish +if sys.argv[-1] == "publish": + if os.system("pip freeze | grep twine"): + print("twine not installed.\nUse `pip install twine`.\nExiting.") + sys.exit() + os.system("rm -rf build/ *.egg-info/") + os.system("python setup.py sdist bdist_wheel") + os.system("twine upload dist/* --skip-existing") + print("You probably want to also tag the version now:") + print(f" git tag -a v{sshr.__version__} -m 'version {sshr.__version__}'") + print(" git push --tags") + sys.exit() + setup( name="small-small-hr", - version=__import__("small_small_hr").__version__, + version=sshr.__version__, description="Minimal human resource management app for Django", long_description=LONG_DESCRIPTION, long_description_content_type="text/markdown", @@ -21,19 +36,21 @@ author="Kelvin Jayanoris", author_email="kelvin@jayanoris.com", url="https://github.com/moshthepitt/small-small-hr", - packages=find_packages(exclude=["docs", "tests"]), + packages=find_packages(exclude=["docs", "*.egg-info", "build", "tests.*", "tests"]), install_requires=[ - "Django >= 2.0.11", + "Django >= 2.2", "voluptuous", "psycopg2-binary", "sorl-thumbnail", "django-private-storage", + "django-model-reviews", "phonenumberslite", "django-phonenumber-field", "django-crispy-forms", "djangorestframework", "sorl-thumbnail", "Pillow", + "django-mptt", ], classifiers=[ "Programming Language :: Python", @@ -44,4 +61,5 @@ "Framework :: Django :: 2.2", "Framework :: Django :: 3.0", ], + include_package_data=True, ) diff --git a/small_small_hr/__init__.py b/small_small_hr/__init__.py index 0ab9e5e..1d0da64 100644 --- a/small_small_hr/__init__.py +++ b/small_small_hr/__init__.py @@ -1,7 +1,5 @@ -""" -Main init file for small_small_hr -""" -VERSION = (0, 1, 9) +"""Main init file for small_small_hr.""" +VERSION = (0, 2, 0) __version__ = ".".join(str(v) for v in VERSION) # pylint: disable=invalid-name default_app_config = "small_small_hr.apps.SmallSmallHrConfig" # noqa diff --git a/small_small_hr/apps.py b/small_small_hr/apps.py index d96c3d1..0dda60a 100644 --- a/small_small_hr/apps.py +++ b/small_small_hr/apps.py @@ -2,23 +2,45 @@ Apps module for small-small-hr """ from django.apps import AppConfig +from django.db.models.signals import post_migrate from django.utils.translation import gettext_lazy as _ +def mptt_callback(sender, **kwargs): # pylint: disable=unused-argument + """ + Rebuild mptt tree. + + We need to do this so that for existing objects we can rebuild the mptt tree + and set correct values for all the mptt fields. This is necessary because we + need to remove the defaults placed in the migrations file. + """ + # pylint: disable=import-outside-toplevel + from small_small_hr.models import StaffProfile + + StaffProfile.objects.rebuild() + + class SmallSmallHrConfig(AppConfig): """Apps config class.""" - name = 'small_small_hr' - app_label = 'small_small_hr' + + name = "small_small_hr" + app_label = "small_small_hr" verbose_name = _("Small Small HR") def ready(self): """Do stuff when the app is ready.""" # pylint: disable=import-outside-toplevel,unused-import + + # post migrate function + post_migrate.connect(mptt_callback, sender=self) + + # signals import small_small_hr.signals # noqa # set up app settings from django.conf import settings import small_small_hr.settings as defaults + for name in dir(defaults): if name.isupper() and not hasattr(settings, name): setattr(settings, name, getattr(defaults, name)) diff --git a/small_small_hr/constants.py b/small_small_hr/constants.py new file mode 100644 index 0000000..f229471 --- /dev/null +++ b/small_small_hr/constants.py @@ -0,0 +1,9 @@ +"""Constants.""" +HOURS = "hours" +MINUTES = "minutes" +STAFF = "staff" +OVERTIME_APPLICATION_EMAIL_TEMPLATE = "overtime_application" +OVERTIME_COMPLETED_EMAIL_TEMPLATE = "overtime_completed" +LEAVE_APPLICATION_EMAIL_TEMPLATE = "leave_application" +LEAVE_COMPLETED_EMAIL_TEMPLATE = "leave_completed" +EMAIL_TEMPLATE_PATH = "small_small_hr/email" diff --git a/small_small_hr/emails.py b/small_small_hr/emails.py index b53ec32..d2b991f 100644 --- a/small_small_hr/emails.py +++ b/small_small_hr/emails.py @@ -1,173 +1,76 @@ -""" -Emails module for scam app -""" -from django.conf import settings -from django.contrib.sites.models import Site -from django.core.mail import EmailMultiAlternatives -from django.template.loader import render_to_string -from django.utils.translation import ugettext as _ - -from small_small_hr.models import Leave, OverTime - - -def send_email( # pylint: disable=too-many-arguments,too-many-locals,bad-continuation - name: str, - email: str, - subject: str, - message: str, - obj: object = None, - cc_list: list = None, - template: str = "generic", - template_path: str = "small_small_hr/email", -): - """ - Sends a generic email - - :param name: name of person - :param email: email address to send to - :param subject: the email's subject - :param message: the email's body text - :param obj: the object in question - :param cc_list: the list of email address to "CC" - :param template: the template to use - """ - context = { - "name": name, - "subject": subject, - "message": message, - "object": obj, - "SITE": Site.objects.get_current(), - } - email_subject = render_to_string( - f"{template_path}/{template}_email_subject.txt", context - ).replace("\n", "") - email_txt_body = render_to_string( - f"{template_path}/{template}_email_body.txt", context - ) - email_html_body = render_to_string( - f"{template_path}/{template}_email_body.html", context - ).replace("\n", "") - - subject = email_subject - from_email = settings.DEFAULT_FROM_EMAIL - to_email = f"{name} <{email}>" - text_content = email_txt_body - html_content = email_html_body - msg = EmailMultiAlternatives(subject, text_content, from_email, [to_email]) - if cc_list: - msg.cc = cc_list - msg.attach_alternative(html_content, "text/html") - - return msg.send(fail_silently=True) - - -def leave_application_email(leave_obj: Leave): - """ - Sends an email to admins when a leave application is made - """ - msg = getattr( - settings, - "SSHR_LEAVE_APPLICATION_EMAIL_TXT", - _("There has been a new leave application. Please log in to process " "it."), - ) - subj = getattr( - settings, "SSHR_LEAVE_APPLICATION_EMAIL_SUBJ", _("New Leave Application") - ) - admin_emails = settings.SSHR_ADMIN_LEAVE_EMAILS - - for admin_email in admin_emails: +"""Emails module for scam app.""" +from model_reviews.emails import get_display_name, send_email +from model_reviews.models import ModelReview, Reviewer + +from small_small_hr.constants import ( + LEAVE_APPLICATION_EMAIL_TEMPLATE, + LEAVE_COMPLETED_EMAIL_TEMPLATE, + OVERTIME_APPLICATION_EMAIL_TEMPLATE, + OVERTIME_COMPLETED_EMAIL_TEMPLATE, +) + + +def send_request_for_leave_review(reviewer: Reviewer): + """Send email requesting a Leave review to one reviewer.""" + if reviewer.user.email: + source = reviewer.review.content_object send_email( - name=settings.SSHR_ADMIN_NAME, - email=admin_email, - subject=subj, - message=msg, - obj=leave_obj, - template="leave_application", + name=get_display_name(reviewer.user), + email=reviewer.user.email, + subject=source.review_request_email_subject, + message=source.review_request_email_body, + obj=reviewer.review, + cc_list=None, + template=LEAVE_APPLICATION_EMAIL_TEMPLATE, + template_path=source.email_template_path, ) -def leave_processed_email(leave_obj: Leave): - """ - Sends an email to admins when a leave application is processed - """ - if leave_obj.staff.user.email: - msg = getattr( - settings, - "SSHR_LEAVE_PROCESSED_EMAIL_TXT", - _( - f"You leave application status is " - f"{leave_obj.get_status_display()}. Log in for more info." - ), - ) - subj = getattr( - settings, - "SSHR_LEAVE_PROCESSED_EMAIL_SUBJ", - _("Your leave application has been processed"), - ) - +def send_request_for_overtime_review(reviewer: Reviewer): + """Send email requesting a OverTime review to one reviewer.""" + if reviewer.user.email: + source = reviewer.review.content_object send_email( - name=leave_obj.staff.get_name(), - email=leave_obj.staff.user.email, - subject=subj, - message=msg, - obj=leave_obj, - cc_list=settings.SSHR_ADMIN_LEAVE_EMAILS, + name=get_display_name(reviewer.user), + email=reviewer.user.email, + subject=source.review_request_email_subject, + message=source.review_request_email_body, + obj=reviewer.review, + cc_list=None, + template=OVERTIME_APPLICATION_EMAIL_TEMPLATE, + template_path=source.email_template_path, ) -def overtime_application_email(overtime_obj: OverTime): - """ - Sends an email to admins when an overtime application is made - """ - msg = getattr( - settings, - "SSHR_OVERTIME_APPLICATION_EMAIL_TXT", - _( - "There has been a new overtime application. Please log in to " - "process it." - ), - ) - subj = getattr( - settings, "SSHR_OVERTIME_APPLICATION_EMAIL_SUBJ", _("New Overtime Application") - ) - admin_emails = settings.SSHR_ADMIN_OVERTIME_EMAILS - - for admin_email in admin_emails: - send_email( - name=settings.SSHR_ADMIN_NAME, - email=admin_email, - subject=subj, - message=msg, - obj=overtime_obj, - template="overtime_application", - ) - - -def overtime_processed_email(overtime_obj: OverTime): - """ - Sends an email to admins when an overtime application is processed - """ - if overtime_obj.staff.user.email: - - msg = getattr( - settings, - "SSHR_OVERTIME_PROCESSED_EMAIL_TXT", - _( - f"You overtime application status is " - f"{overtime_obj.get_status_display()}. Log in for more info." - ), - ) - subj = getattr( - settings, - "SSHR_OVERTIME_PROCESSED_EMAIL_SUBJ", - _("Your overtime application has been processed"), - ) - - send_email( - name=overtime_obj.staff.get_name(), - email=overtime_obj.staff.user.email, - subject=subj, - message=msg, - obj=overtime_obj, - cc_list=settings.SSHR_ADMIN_OVERTIME_EMAILS, - ) +def send_leave_review_complete_notice(review_obj: ModelReview): + """Send notice that Leave review is complete.""" + if not review_obj.needs_review() and review_obj.user: + if review_obj.user.email: + source = review_obj.content_object + send_email( + name=get_display_name(review_obj.user), + email=review_obj.user.email, + subject=source.review_complete_email_subject, + message=source.review_complete_email_body, + obj=review_obj, + cc_list=None, + template=LEAVE_COMPLETED_EMAIL_TEMPLATE, + template_path=source.email_template_path, + ) + + +def send_overtime_review_complete_notice(review_obj: ModelReview): + """Send notice that OverTime review is complete.""" + if not review_obj.needs_review() and review_obj.user: + if review_obj.user.email: + source = review_obj.content_object + send_email( + name=get_display_name(review_obj.user), + email=review_obj.user.email, + subject=source.review_complete_email_subject, + message=source.review_complete_email_body, + obj=review_obj, + cc_list=None, + template=OVERTIME_COMPLETED_EMAIL_TEMPLATE, + template_path=source.email_template_path, + ) diff --git a/small_small_hr/forms.py b/small_small_hr/forms.py index 81cffda..2ba0dfe 100644 --- a/small_small_hr/forms.py +++ b/small_small_hr/forms.py @@ -1,6 +1,4 @@ -""" -Forms module for small small hr -""" +"""Forms module for small small hr.""" from datetime import datetime, time from django import forms @@ -16,218 +14,189 @@ from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.phonenumber import PhoneNumber -from small_small_hr.emails import (leave_application_email, - overtime_application_email) -from small_small_hr.models import (TWOPLACES, AnnualLeave, FreeDay, Leave, - OverTime, Role, StaffDocument, StaffProfile) +from small_small_hr.models import ( + TWOPLACES, + AnnualLeave, + FreeDay, + Leave, + OverTime, + Role, + StaffDocument, + StaffProfile, +) class AnnualLeaveForm(forms.ModelForm): - """ - Form used when managing AnnualLeave - """ + """Form used when managing AnnualLeave.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = AnnualLeave - fields = [ - 'staff', - 'year', - 'leave_type', - 'allowed_days', - 'carried_over_days' - ] + fields = ["staff", "year", "leave_type", "allowed_days", "carried_over_days"] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) if not self.instance: - self.fields['year'].initial = datetime.today().year + self.fields["year"].initial = datetime.today().year self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'annual-leave-form' + self.helper.form_id = "annual-leave-form" self.helper.layout = Layout( - Field('staff',), - Field('year',), - Field('leave_type',), - Field('allowed_days',), - Field('carried_over_days'), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("staff",), + Field("year",), + Field("leave_type",), + Field("allowed_days",), + Field("carried_over_days"), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) class RoleForm(forms.ModelForm): - """ - Form used when managing Role objects - """ + """Form used when managing Role objects.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = Role - fields = [ - 'name', - 'description' - ] + fields = ["name", "description"] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'role-form' + self.helper.form_id = "role-form" self.helper.layout = Layout( - Field('name',), - Field('description',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("name",), + Field("description",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) class FreeDayForm(forms.ModelForm): - """ - Form used when managing FreeDay objects - """ + """Form used when managing FreeDay objects.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = FreeDay - fields = [ - 'name', - 'date' - ] + fields = ["name", "date"] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'freeday-form' + self.helper.form_id = "freeday-form" self.helper.layout = Layout( - Field('name',), - Field('date',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("name",), + Field("date",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) class OverTimeForm(forms.ModelForm): - """ - Form used when managing OverTime objects - """ + """Form used when managing OverTime objects.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = OverTime fields = [ - 'staff', - 'date', - 'start', - 'end', - 'reason', - 'status', - 'comments' + "staff", + "date", + "start", + "end", + "review_reason", + "review_status", ] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'overtime-form' + self.helper.form_id = "overtime-form" self.helper.layout = Layout( - Field('staff',), - Field('date',), - Field('start',), - Field('end',), - Field('reason',), - Field('status',), - Field('comments'), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("staff",), + Field("date",), + Field("start",), + Field("end",), + Field("review_reason",), + Field("review_status",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) def clean(self): - """ - Custom clean method - """ + """Clean all the form fields.""" cleaned_data = super().clean() - end = cleaned_data.get('end') - start = cleaned_data.get('start') - date = cleaned_data.get('date') - staff = cleaned_data.get('staff') - status = cleaned_data.get('status') + end = cleaned_data.get("end") + start = cleaned_data.get("start") + date = cleaned_data.get("date") + staff = cleaned_data.get("staff") + review_status = cleaned_data.get("review_status") # end must be later than start if end <= start: - self.add_error('end', _("end must be greater than start")) + self.add_error("end", _("end must be greater than start")) # must not overlap within the same date unless being rejected # pylint: disable=no-member overlap_qs = OverTime.objects.filter( - date=date, staff=staff, status=OverTime.APPROVED).filter( - Q(start__gte=start) & Q(end__lte=end)) + date=date, staff=staff, review_status=OverTime.APPROVED + ).filter(Q(start__gte=start) & Q(end__lte=end)) if self.instance is not None: overlap_qs = overlap_qs.exclude(id=self.instance.id) - if overlap_qs.exists() and status != OverTime.REJECTED: - msg = _('you cannot have overlapping overtime hours on the ' - 'same day') - self.add_error('start', msg) - self.add_error('end', msg) - self.add_error('date', msg) + if overlap_qs.exists() and review_status != OverTime.REJECTED: + msg = _("you cannot have overlapping overtime hours on the " "same day") + self.add_error("start", msg) + self.add_error("end", msg) + self.add_error("date", msg) class ApplyOverTimeForm(OverTimeForm): - """ - Form used when applying for overtime - """ + """Form used when applying for overtime.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = OverTime fields = [ - 'staff', - 'date', - 'start', - 'end', - 'reason', + "staff", + "date", + "start", + "end", + "review_reason", ] def __init__(self, *args, **kwargs): + """Initialize the form.""" super().__init__(*args, **kwargs) - self.request = kwargs.pop('request', None) + self.request = kwargs.pop("request", None) if self.request: # pylint: disable=no-member try: @@ -235,182 +204,168 @@ def __init__(self, *args, **kwargs): except StaffProfile.DoesNotExist: pass else: - self.fields['staff'].queryset = StaffProfile.objects.filter( - id=self.request.user.staffprofile.id) + self.fields["staff"].queryset = StaffProfile.objects.filter( + id=self.request.user.staffprofile.id + ) self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'overtime-application-form' + self.helper.form_id = "overtime-application-form" self.helper.layout = Layout( - Field('staff', type="hidden"), - Field('date',), - Field('start',), - Field('end',), - Field('reason',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("staff", type="hidden"), + Field("date",), + Field("start",), + Field("end",), + Field("review_reason",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) def save(self, commit=True): - """ - Custom save method - """ + """Save the form.""" overtime = super().save() - overtime_application_email(overtime_obj=overtime) return overtime class LeaveForm(forms.ModelForm): - """ - Form used when managing Leave objects - """ - start = forms.DateField(label=_('Start Date'), required=True) - end = forms.DateField(label=_('End Date'), required=True) + """Form used when managing Leave objects.""" + + start = forms.DateField(label=_("Start Date"), required=True) + end = forms.DateField(label=_("End Date"), required=True) class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = Leave fields = [ - 'staff', - 'leave_type', - 'start', - 'end', - 'reason', - 'status', - 'comments' + "staff", + "leave_type", + "start", + "end", + "review_reason", + "review_status", ] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'leave-form' + self.helper.form_id = "leave-form" self.helper.layout = Layout( - Field('staff',), - Field('leave_type',), - Field('start',), - Field('end',), - Field('reason',), - Field('status',), - Field('comments'), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("staff",), + Field("leave_type",), + Field("start",), + Field("end",), + Field("review_reason",), + Field("review_status",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) def clean_start(self): - """ - clean start field - """ - data = self.cleaned_data['start'] + """Clean start field.""" + data = self.cleaned_data["start"] data = datetime.combine( date=data, time=time(settings.SSHR_DEFAULT_TIME, 0, 0, 0), - tzinfo=pytz.timezone(settings.TIME_ZONE)) + tzinfo=pytz.timezone(settings.TIME_ZONE), + ) return data def clean_end(self): - """ - clean end field - """ - data = self.cleaned_data['end'] + """Clean end field.""" + data = self.cleaned_data["end"] data = datetime.combine( date=data, time=time(settings.SSHR_DEFAULT_TIME, 0, 0, 0), - tzinfo=pytz.timezone(settings.TIME_ZONE)) + tzinfo=pytz.timezone(settings.TIME_ZONE), + ) return data def clean(self): - """ - Custom clean method - """ + """Clean all the form fields.""" cleaned_data = super().clean() - leave_type = cleaned_data.get('leave_type') - staff = cleaned_data.get('staff') - end = cleaned_data.get('end') - start = cleaned_data.get('start') - status = cleaned_data.get('status') + leave_type = cleaned_data.get("leave_type") + staff = cleaned_data.get("staff") + end = cleaned_data.get("end") + start = cleaned_data.get("start") + review_status = cleaned_data.get("review_status") if all([staff, leave_type, start, end]): # end year and start year must be the same if end.year != start.year: - msg = _('start and end must be from the same year') - self.add_error('start', msg) - self.add_error('end', msg) + msg = _("start and end must be from the same year") + self.add_error("start", msg) + self.add_error("end", msg) # end must be later than start if end < start: - self.add_error('end', _("end must be greater than start")) + self.add_error("end", _("end must be greater than start")) if not settings.SSHR_ALLOW_OVERSUBSCRIBE: # staff profile must have sufficient sick days if leave_type == Leave.SICK: sick_days = staff.get_available_sick_days(year=start.year) if (end - start).days > sick_days: - msg = _('Not enough sick days. Available sick days ' - f'are {sick_days.quantize(TWOPLACES)}') - self.add_error('start', msg) - self.add_error('end', msg) + msg = _( + "Not enough sick days. Available sick days " + f"are {sick_days.quantize(TWOPLACES)}" + ) + self.add_error("start", msg) + self.add_error("end", msg) # staff profile must have sufficient leave days if leave_type == Leave.REGULAR: - leave_days = staff.get_available_leave_days( - year=start.year) + leave_days = staff.get_available_leave_days(year=start.year) if (end - start).days > leave_days: - msg = _('Not enough leave days. Available leave days ' - f'are {leave_days.quantize(TWOPLACES)}') - self.add_error('start', msg) - self.add_error('end', msg) + msg = _( + "Not enough leave days. Available leave days " + f"are {leave_days.quantize(TWOPLACES)}" + ) + self.add_error("start", msg) + self.add_error("end", msg) # must not overlap unless it is being rejected # pylint: disable=no-member overlap_qs = Leave.objects.filter( - staff=staff, - status=Leave.APPROVED, - leave_type=leave_type).filter( - Q(start__gte=start) & Q(end__lte=end)) + staff=staff, review_status=Leave.APPROVED, leave_type=leave_type + ).filter(Q(start__gte=start) & Q(end__lte=end)) if self.instance is not None: overlap_qs = overlap_qs.exclude(id=self.instance.id) - if overlap_qs.exists() and status != Leave.REJECTED: - msg = _('you cannot have overlapping leave days') - self.add_error('start', msg) - self.add_error('end', msg) + if overlap_qs.exists() and review_status != Leave.REJECTED: + msg = _("you cannot have overlapping leave days") + self.add_error("start", msg) + self.add_error("end", msg) class ApplyLeaveForm(LeaveForm): - """ - Form used when applying for Leave - """ + """Form used when applying for Leave.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = Leave fields = [ - 'staff', - 'leave_type', - 'start', - 'end', - 'reason', + "staff", + "leave_type", + "start", + "end", + "review_reason", ] def __init__(self, *args, **kwargs): + """Initialize the form.""" super().__init__(*args, **kwargs) - self.request = kwargs.pop('request', None) + self.request = kwargs.pop("request", None) if self.request: # pylint: disable=no-member try: @@ -418,96 +373,86 @@ def __init__(self, *args, **kwargs): except StaffProfile.DoesNotExist: pass else: - self.fields['staff'].queryset = StaffProfile.objects.filter( - id=self.request.user.staffprofile.id) + self.fields["staff"].queryset = StaffProfile.objects.filter( + id=self.request.user.staffprofile.id + ) self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'leave-application-form' + self.helper.form_id = "leave-application-form" self.helper.layout = Layout( - Field('staff', type="hidden"), - Field('leave_type',), - Field('start',), - Field('end',), - Field('reason',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("staff", type="hidden"), + Field("leave_type",), + Field("start",), + Field("end",), + Field("review_reason",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) def save(self, commit=True): - """ - Custom save method - """ + """Save the form.""" leave = super().save() - leave_application_email(leave_obj=leave) return leave class StaffDocumentForm(forms.ModelForm): - """ - Form used when managing StaffDocument objects - """ + """Form used when managing StaffDocument objects.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = StaffDocument fields = [ - 'staff', - 'name', - 'description', - 'public', - 'file', + "staff", + "name", + "description", + "public", + "file", ] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) if self.instance and self.instance.file: - self.fields['file'].required = False + self.fields["file"].required = False self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'staffdocument-form' + self.helper.form_id = "staffdocument-form" self.helper.layout = Layout( - Field('staff',), - Field('name',), - Field('description',), - Field('file',), - Field('public',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("staff",), + Field("name",), + Field("description",), + Field("file",), + Field("public",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) class UserStaffDocumentForm(forms.ModelForm): - """ - Form used when managing one's own StaffDocument objects - """ + """Form used when managing one's own StaffDocument objects.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = StaffDocument fields = [ - 'staff', - 'name', - 'description', - 'file', + "staff", + "name", + "description", + "file", ] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) if self.request: # pylint: disable=no-member @@ -516,319 +461,313 @@ def __init__(self, *args, **kwargs): except StaffProfile.DoesNotExist: pass else: - self.fields['staff'].queryset = StaffProfile.objects.filter( - id=self.request.user.staffprofile.id) + self.fields["staff"].queryset = StaffProfile.objects.filter( + id=self.request.user.staffprofile.id + ) if self.instance and self.instance.file: - self.fields['file'].required = False + self.fields["file"].required = False self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'staffdocument-form' + self.helper.form_id = "staffdocument-form" self.helper.layout = Layout( - Field('staff', type="hidden"), - Field('name',), - Field('description',), - Field('file',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("staff", type="hidden"), + Field("name",), + Field("description",), + Field("file",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) class StaffProfileAdminForm(forms.ModelForm): - """ - Form used when managing StaffProfile objects - """ - first_name = forms.CharField(label=_('First Name'), required=True) - last_name = forms.CharField(label=_('Last Name'), required=True) - id_number = forms.CharField(label=_('ID Number'), required=True) - nhif = forms.CharField(label=_('NHIF'), required=False) - nssf = forms.CharField(label=_('NSSF'), required=False) - pin_number = forms.CharField(label=_('PIN Number'), required=False) + """Form used when managing StaffProfile objects.""" + + first_name = forms.CharField(label=_("First Name"), required=True) + last_name = forms.CharField(label=_("Last Name"), required=True) + id_number = forms.CharField(label=_("ID Number"), required=True) + nhif = forms.CharField(label=_("NHIF"), required=False) + nssf = forms.CharField(label=_("NSSF"), required=False) + pin_number = forms.CharField(label=_("PIN Number"), required=False) emergency_contact_name = forms.CharField( - label=_('Emergency Contact Name'), required=False) + label=_("Emergency Contact Name"), required=False + ) emergency_contact_relationship = forms.CharField( - label=_('Emergency Contact Relationship'), required=False) + label=_("Emergency Contact Relationship"), required=False + ) emergency_contact_number = PhoneNumberField( - label=_('Emergency Contact Phone Number'), required=False) + label=_("Emergency Contact Phone Number"), required=False + ) class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = StaffProfile fields = [ - 'first_name', - 'last_name', - 'id_number', - 'image', - 'phone', - 'sex', - 'role', - 'nhif', - 'nssf', - 'pin_number', - 'address', - 'birthday', - 'leave_days', - 'sick_days', - 'overtime_allowed', - 'start_date', - 'end_date', - 'emergency_contact_name', - 'emergency_contact_number', - 'emergency_contact_relationship' + "first_name", + "last_name", + "id_number", + "image", + "phone", + "sex", + "role", + "nhif", + "nssf", + "pin_number", + "address", + "birthday", + "leave_days", + "sick_days", + "overtime_allowed", + "start_date", + "end_date", + "emergency_contact_name", + "emergency_contact_number", + "emergency_contact_relationship", ] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) if self.instance and self.instance.image: - self.fields['image'].required = False + self.fields["image"].required = False self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'staffprofile-form' + self.helper.form_id = "staffprofile-form" self.helper.layout = Layout( - Field('first_name',), - Field('last_name',), - Field('image',), - Field('phone',), - Field('id_number',), - Field('sex',), - Field('role',), - Field('nhif',), - Field('nssf',), - Field('pin_number',), - Field('address',), - Field('birthday',), - Field('leave_days',), - Field('sick_days',), - Field('overtime_allowed',), - Field('start_date',), - Field('end_date',), - Field('emergency_contact_name',), - Field('emergency_contact_number',), - Field('emergency_contact_relationship',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("first_name",), + Field("last_name",), + Field("image",), + Field("phone",), + Field("id_number",), + Field("sex",), + Field("role",), + Field("nhif",), + Field("nssf",), + Field("pin_number",), + Field("address",), + Field("birthday",), + Field("leave_days",), + Field("sick_days",), + Field("overtime_allowed",), + Field("start_date",), + Field("end_date",), + Field("emergency_contact_name",), + Field("emergency_contact_number",), + Field("emergency_contact_relationship",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) def clean_id_number(self): - """ - Check if id number is unique - """ - value = self.cleaned_data.get('id_number') + """Check if id number is unique.""" + value = self.cleaned_data.get("id_number") # pylint: disable=no-member - if StaffProfile.objects.exclude( - id=self.instance.id).filter(data__id_number=value).exists(): - raise forms.ValidationError( - _('This id number is already in use.')) + if ( + StaffProfile.objects.exclude(id=self.instance.id) + .filter(data__id_number=value) + .exists() + ): + raise forms.ValidationError(_("This id number is already in use.")) return value def clean_nssf(self): - """ - Check if NSSF number is unique - """ - value = self.cleaned_data.get('nssf') + """Check if NSSF number is unique.""" + value = self.cleaned_data.get("nssf") # pylint: disable=no-member - if value and StaffProfile.objects.exclude( - id=self.instance.id).filter(data__nssf=value).exists(): - raise forms.ValidationError( - _('This NSSF number is already in use.')) + if ( + value + and StaffProfile.objects.exclude(id=self.instance.id) + .filter(data__nssf=value) + .exists() + ): + raise forms.ValidationError(_("This NSSF number is already in use.")) return value def clean_nhif(self): - """ - Check if NHIF number is unique - """ - value = self.cleaned_data.get('nhif') + """Check if NHIF number is unique.""" + value = self.cleaned_data.get("nhif") # pylint: disable=no-member - if value and StaffProfile.objects.exclude( - id=self.instance.id).filter(data__nhif=value).exists(): - raise forms.ValidationError( - _('This NHIF number is already in use.')) + if ( + value + and StaffProfile.objects.exclude(id=self.instance.id) + .filter(data__nhif=value) + .exists() + ): + raise forms.ValidationError(_("This NHIF number is already in use.")) return value def clean_pin_number(self): - """ - Check if PIN number is unique - """ - value = self.cleaned_data.get('pin_number') + """Check if PIN number is unique.""" + value = self.cleaned_data.get("pin_number") # pylint: disable=no-member - if value and StaffProfile.objects.exclude( - id=self.instance.id).filter(data__pin_number=value).exists(): - raise forms.ValidationError( - _('This PIN number is already in use.')) + if ( + value + and StaffProfile.objects.exclude(id=self.instance.id) + .filter(data__pin_number=value) + .exists() + ): + raise forms.ValidationError(_("This PIN number is already in use.")) return value def save(self, commit=True): # pylint: disable=unused-argument - """ - Custom save method - """ + """Save the form.""" staffprofile = super().save() - emergency_phone = self.cleaned_data.get('emergency_contact_number') + emergency_phone = self.cleaned_data.get("emergency_contact_number") if isinstance(emergency_phone, PhoneNumber): emergency_phone = emergency_phone.as_e164 json_data = { - 'id_number': self.cleaned_data.get('id_number'), - 'nhif': self.cleaned_data.get('nhif'), - 'nssf': self.cleaned_data.get('nssf'), - 'pin_number': self.cleaned_data.get('pin_number'), - 'emergency_contact_name': self.cleaned_data.get( - 'emergency_contact_name'), - 'emergency_contact_relationship': self.cleaned_data.get( - 'emergency_contact_relationship'), - 'emergency_contact_number': emergency_phone, + "id_number": self.cleaned_data.get("id_number"), + "nhif": self.cleaned_data.get("nhif"), + "nssf": self.cleaned_data.get("nssf"), + "pin_number": self.cleaned_data.get("pin_number"), + "emergency_contact_name": self.cleaned_data.get("emergency_contact_name"), + "emergency_contact_relationship": self.cleaned_data.get( + "emergency_contact_relationship" + ), + "emergency_contact_number": emergency_phone, } staffprofile.data = json_data staffprofile.save() user = staffprofile.user - user.first_name = self.cleaned_data['first_name'] - user.last_name = self.cleaned_data['last_name'] + user.first_name = self.cleaned_data["first_name"] + user.last_name = self.cleaned_data["last_name"] user.save() return staffprofile class StaffProfileAdminCreateForm(StaffProfileAdminForm): - """ - Form used when creating new Staff Profiles - """ + """Form used when creating new Staff Profiles.""" + user = forms.ModelChoiceField( - label=_('User'), queryset=User.objects.filter(staffprofile=None)) + label=_("User"), queryset=User.objects.filter(staffprofile=None) + ) class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = StaffProfile fields = [ - 'user', - 'first_name', - 'last_name', - 'id_number', - 'image', - 'phone', - 'sex', - 'role', - 'nhif', - 'nssf', - 'pin_number', - 'address', - 'birthday', - 'leave_days', - 'sick_days', - 'overtime_allowed', - 'start_date', - 'end_date', - 'emergency_contact_name', - 'emergency_contact_number', - 'emergency_contact_relationship' + "user", + "first_name", + "last_name", + "id_number", + "image", + "phone", + "sex", + "role", + "nhif", + "nssf", + "pin_number", + "address", + "birthday", + "leave_days", + "sick_days", + "overtime_allowed", + "start_date", + "end_date", + "emergency_contact_name", + "emergency_contact_number", + "emergency_contact_relationship", ] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) if self.instance and self.instance.image: - self.fields['image'].required = False + self.fields["image"].required = False self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'staffprofile-form' + self.helper.form_id = "staffprofile-form" self.helper.layout = Layout( - Field('user',), - Field('first_name',), - Field('last_name',), - Field('image',), - Field('phone',), - Field('id_number',), - Field('sex',), - Field('role',), - Field('nhif',), - Field('nssf',), - Field('pin_number',), - Field('address',), - Field('birthday',), - Field('leave_days',), - Field('sick_days',), - Field('overtime_allowed',), - Field('start_date',), - Field('end_date',), - Field('emergency_contact_name',), - Field('emergency_contact_number',), - Field('emergency_contact_relationship',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("user",), + Field("first_name",), + Field("last_name",), + Field("image",), + Field("phone",), + Field("id_number",), + Field("sex",), + Field("role",), + Field("nhif",), + Field("nssf",), + Field("pin_number",), + Field("address",), + Field("birthday",), + Field("leave_days",), + Field("sick_days",), + Field("overtime_allowed",), + Field("start_date",), + Field("end_date",), + Field("emergency_contact_name",), + Field("emergency_contact_number",), + Field("emergency_contact_relationship",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) class StaffProfileUserForm(StaffProfileAdminForm): - """ - Form used when the user is updating their own data - """ + """Form used when the user is updating their own data.""" class Meta: # pylint: disable=too-few-public-methods - """ - Class meta options - """ + """Class meta options.""" + model = StaffProfile fields = [ - 'first_name', - 'last_name', - 'id_number', - 'image', - 'phone', - 'sex', - 'nhif', - 'nssf', - 'pin_number', - 'address', - 'birthday', - 'emergency_contact_name', - 'emergency_contact_number', - 'emergency_contact_relationship' + "first_name", + "last_name", + "id_number", + "image", + "phone", + "sex", + "nhif", + "nssf", + "pin_number", + "address", + "birthday", + "emergency_contact_name", + "emergency_contact_number", + "emergency_contact_relationship", ] def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) + """Initialize the form.""" + self.request = kwargs.pop("request", None) super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_tag = True - self.helper.form_method = 'post' + self.helper.form_method = "post" self.helper.render_required_fields = True self.helper.form_show_labels = True self.helper.html5_required = True - self.helper.form_id = 'staffprofile-user-form' + self.helper.form_id = "staffprofile-user-form" self.helper.layout = Layout( - Field('first_name',), - Field('last_name',), - Field('image',), - Field('phone',), - Field('id_number',), - Field('sex',), - Field('nhif',), - Field('nssf',), - Field('pin_number',), - Field('address',), - Field('birthday',), - Field('emergency_contact_name',), - Field('emergency_contact_number',), - Field('emergency_contact_relationship',), - FormActions( - Submit('submitBtn', _('Submit'), css_class='btn-primary'), - ) + Field("first_name",), + Field("last_name",), + Field("image",), + Field("phone",), + Field("id_number",), + Field("sex",), + Field("nhif",), + Field("nssf",), + Field("pin_number",), + Field("address",), + Field("birthday",), + Field("emergency_contact_name",), + Field("emergency_contact_number",), + Field("emergency_contact_relationship",), + FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) diff --git a/small_small_hr/migrations/0008_auto_20200607_1537.py b/small_small_hr/migrations/0008_auto_20200607_1537.py new file mode 100644 index 0000000..dd9f1bd --- /dev/null +++ b/small_small_hr/migrations/0008_auto_20200607_1537.py @@ -0,0 +1,65 @@ +# Generated by Django 3.0.7 on 2020-06-07 12:37 +# pylint: disable=invalid-name,missing-module-docstring,missing-class-docstring +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("small_small_hr", "0007_auto_20190625_2111"), + ] + + operations = [ + migrations.RenameField( + model_name="leave", old_name="reason", new_name="review_reason", + ), + migrations.RenameField( + model_name="leave", old_name="status", new_name="review_status", + ), + migrations.RenameField( + model_name="overtime", old_name="reason", new_name="review_reason", + ), + migrations.RenameField( + model_name="overtime", old_name="status", new_name="review_status", + ), + migrations.RemoveField(model_name="leave", name="comments",), + migrations.RemoveField(model_name="overtime", name="comments",), + migrations.AddField( + model_name="leave", + name="review_date", + field=models.DateTimeField( + blank=True, default=None, null=True, verbose_name="Review Date" + ), + ), + migrations.AddField( + model_name="overtime", + name="review_date", + field=models.DateTimeField( + blank=True, default=None, null=True, verbose_name="Review Date" + ), + ), + migrations.AlterField( + model_name="annualleave", + name="year", + field=models.PositiveIntegerField( + choices=[ + (2017, 2017), + (2018, 2018), + (2019, 2019), + (2020, 2020), + (2021, 2021), + (2022, 2022), + (2023, 2023), + (2024, 2024), + (2025, 2025), + (2026, 2026), + (2027, 2027), + (2028, 2028), + (2029, 2029), + ], + db_index=True, + default=2017, + verbose_name="Year", + ), + ), + ] diff --git a/small_small_hr/migrations/0009_auto_20200607_2244.py b/small_small_hr/migrations/0009_auto_20200607_2244.py new file mode 100644 index 0000000..14b0e95 --- /dev/null +++ b/small_small_hr/migrations/0009_auto_20200607_2244.py @@ -0,0 +1,52 @@ +# Generated by Django 3.0.7 on 2020-06-07 19:44 +# pylint: disable=invalid-name,missing-module-docstring,missing-class-docstring +import django.db.models.deletion +from django.db import migrations, models + +import mptt.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("small_small_hr", "0008_auto_20200607_1537"), + ] + + operations = [ + migrations.AddField( + model_name="staffprofile", + name="level", + field=models.PositiveIntegerField(default=1, editable=False), + preserve_default=False, + ), + migrations.AddField( + model_name="staffprofile", + name="lft", + field=models.PositiveIntegerField(default=1, editable=False), + preserve_default=False, + ), + migrations.AddField( + model_name="staffprofile", + name="rght", + field=models.PositiveIntegerField(default=1, editable=False), + preserve_default=False, + ), + migrations.AddField( + model_name="staffprofile", + name="supervisor", + field=mptt.fields.TreeForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="children", + to="small_small_hr.StaffProfile", + verbose_name="Supervisor", + ), + ), + migrations.AddField( + model_name="staffprofile", + name="tree_id", + field=models.PositiveIntegerField(db_index=True, default=1, editable=False), + preserve_default=False, + ), + ] diff --git a/small_small_hr/models.py b/small_small_hr/models.py index ce1ab7d..17fbade 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -1,20 +1,23 @@ -""" -Models module for small_small_hr -""" +"""Models module for small_small_hr.""" from datetime import datetime, timedelta from decimal import Decimal +from typing import Optional from django.conf import settings from django.contrib.postgres.fields import JSONField from django.db import models from django.db.models import Q from django.utils import timezone +from django.utils.functional import cached_property from django.utils.translation import ugettext as _ +from model_reviews.models import AbstractReview +from mptt.models import MPTTModel, TreeForeignKey from phonenumber_field.modelfields import PhoneNumberField from private_storage.fields import PrivateFileField from sorl.thumbnail import ImageField +from small_small_hr.constants import EMAIL_TEMPLATE_PATH from small_small_hr.managers import LeaveManager USER = settings.AUTH_USER_MODEL @@ -22,346 +25,424 @@ class TimeStampedModel(models.Model): - """ - Abstract model class that includes timestamp fields - """ - created = models.DateTimeField( - verbose_name=_('Created'), - auto_now_add=True) - modified = models.DateTimeField( - verbose_name=_('Modified'), - auto_now=True) + """Abstract model class that includes timestamp fields.""" + + created = models.DateTimeField(verbose_name=_("Created"), auto_now_add=True) + modified = models.DateTimeField(verbose_name=_("Modified"), auto_now=True) # pylint: disable=too-few-public-methods class Meta: - """ - Meta options for TimeStampedModel - """ + """Meta options for TimeStampedModel.""" + abstract = True class Role(TimeStampedModel, models.Model): - """ - Model class for staff member role - """ - name = models.CharField(_('Name'), max_length=255) - description = models.TextField(_('Description'), blank=True, default='') + """Model class for staff member role.""" + + name = models.CharField(_("Name"), max_length=255) + description = models.TextField(_("Description"), blank=True, default="") class Meta: # pylint: disable=too-few-public-methods - """ - Meta options for StaffDocument - """ + """Meta options for StaffDocument.""" + abstract = False - verbose_name = _('Role') - verbose_name_plural = _('Roles') - ordering = ['name', 'created'] + verbose_name = _("Role") + verbose_name_plural = _("Roles") + ordering = ["name", "created"] def __str__(self): + """Unicode representation of class object.""" # pylint: disable=no-member return self.name -class StaffProfile(TimeStampedModel, models.Model): +class StaffProfile(TimeStampedModel, MPTTModel): """ - StaffProfile model class + StaffProfile model class. + Extends auth.User and adds more fields """ # sex choices # according to https://en.wikipedia.org/wiki/ISO/IEC_5218 - NOT_KNOWN = '0' - MALE = '1' - FEMALE = '2' - NOT_APPLICABLE = '9' + NOT_KNOWN = "0" + MALE = "1" + FEMALE = "2" + NOT_APPLICABLE = "9" SEX_CHOICES = ( - (NOT_KNOWN, _('Not Known')), - (MALE, _('Male')), - (FEMALE, _('Female')), - (NOT_APPLICABLE, _('Not Applicable')) + (NOT_KNOWN, _("Not Known")), + (MALE, _("Male")), + (FEMALE, _("Female")), + (NOT_APPLICABLE, _("Not Applicable")), ) - user = models.OneToOneField( - USER, verbose_name=_('User'), on_delete=models.CASCADE) - image = ImageField(upload_to="staff-images/", max_length=255, - verbose_name=_("Profile Image"), - help_text=_("A square image works best"), blank=True) - sex = models.CharField(_('Gender'), choices=SEX_CHOICES, max_length=1, - default=NOT_KNOWN, blank=True, db_index=True) - role = models.ForeignKey(Role, verbose_name=_('Role'), blank=True, - default=None, null=True, - on_delete=models.SET_NULL) - phone = PhoneNumberField(_('Phone'), blank=True, default='') - address = models.TextField(_('Addresss'), blank=True, default="") - birthday = models.DateField(_('Birthday'), blank=True, default=None, - null=True) + user = models.OneToOneField(USER, verbose_name=_("User"), on_delete=models.CASCADE) + supervisor = TreeForeignKey( + "self", + verbose_name=_("Manager"), + on_delete=models.PROTECT, + null=True, + blank=True, + related_name="children", + ) + image = ImageField( + upload_to="staff-images/", + max_length=255, + verbose_name=_("Profile Image"), + help_text=_("A square image works best"), + blank=True, + ) + sex = models.CharField( + _("Gender"), + choices=SEX_CHOICES, + max_length=1, + default=NOT_KNOWN, + blank=True, + db_index=True, + ) + role = models.ForeignKey( + Role, + verbose_name=_("Role"), + blank=True, + default=None, + null=True, + on_delete=models.SET_NULL, + ) + phone = PhoneNumberField(_("Phone"), blank=True, default="") + address = models.TextField(_("Addresss"), blank=True, default="") + birthday = models.DateField(_("Birthday"), blank=True, default=None, null=True) leave_days = models.PositiveIntegerField( - _('Leave days'), default=21, blank=True, - help_text=_('Number of leave days allowed in a year.')) + _("Leave days"), + default=21, + blank=True, + help_text=_("Number of leave days allowed in a year."), + ) sick_days = models.PositiveIntegerField( - _('Sick days'), default=10, blank=True, - help_text=_('Number of sick days allowed in a year.')) + _("Sick days"), + default=10, + blank=True, + help_text=_("Number of sick days allowed in a year."), + ) overtime_allowed = models.BooleanField( - _('Overtime allowed'), blank=True, default=False) + _("Overtime allowed"), blank=True, default=False + ) start_date = models.DateField( - _('Start Date'), null=True, default=None, blank=True, - help_text=_('The start date of employment')) + _("Start Date"), + null=True, + default=None, + blank=True, + help_text=_("The start date of employment"), + ) end_date = models.DateField( - _('End Date'), null=True, default=None, blank=True, - help_text=_('The end date of employment')) - data = JSONField(_('Data'), default=dict, blank=True) + _("End Date"), + null=True, + default=None, + blank=True, + help_text=_("The end date of employment"), + ) + data = JSONField(_("Data"), default=dict, blank=True) class Meta: # pylint: disable=too-few-public-methods - """ - Meta options for StaffProfile - """ + """Meta options for StaffProfile.""" + abstract = False - verbose_name = _('Staff Profile') - verbose_name_plural = _('Staff Profiles') - ordering = ['user__first_name', 'user__last_name', 'user__username', - 'created'] + verbose_name = _("Staff Profile") + verbose_name_plural = _("Staff Profiles") + ordering = ["user__first_name", "user__last_name", "user__username", "created"] + + class MPTTMeta: + """Meta options for MPTT.""" + + parent_attr = "supervisor" def get_name(self): - """ - Returns the staff member's name - """ + """Return the staff member's name.""" # pylint: disable=no-member - return f'{self.user.first_name} {self.user.last_name}' + return f"{self.user.first_name} {self.user.last_name}" - def get_approved_leave_days(self, year: int = datetime.today().year): - """ - Get approved leave days in the current year - """ + @cached_property + def _current_year(self): # pylint: disable=no-self-use + """Get the current year.""" + return datetime.today().year + + def get_approved_leave_days(self, year: Optional[int] = None): + """Get approved leave days in the current year.""" # pylint: disable=no-member return get_taken_leave_days( staffprofile=self, status=Leave.APPROVED, leave_type=Leave.REGULAR, - start_year=year, - end_year=year + start_year=year or self._current_year, + end_year=year or self._current_year, ) - def get_approved_sick_days(self, year: int = datetime.today().year): - """ - Get approved leave days in the current year - """ + def get_approved_sick_days(self, year: Optional[int] = None): + """Get approved leave days in the current year.""" return get_taken_leave_days( staffprofile=self, status=Leave.APPROVED, leave_type=Leave.SICK, - start_year=year, - end_year=year + start_year=year or self._current_year, + end_year=year or self._current_year, ) - def get_available_leave_days(self, year: int = datetime.today().year): - """ - Get available leave days - """ + def get_available_leave_days(self, year: Optional[int] = None): + """Get available leave days.""" try: # pylint: disable=no-member leave_record = AnnualLeave.objects.get( - leave_type=Leave.REGULAR, - staff=self, - year=year) + leave_type=Leave.REGULAR, staff=self, year=year or self._current_year + ) except AnnualLeave.DoesNotExist: return Decimal(0) else: return leave_record.get_available_leave_days() - def get_available_sick_days(self, year: int = datetime.today().year): - """ - Get available sick days - """ + def get_available_sick_days(self, year: Optional[int] = None): + """Get available sick days.""" try: # pylint: disable=no-member leave_record = AnnualLeave.objects.get( - leave_type=Leave.SICK, - staff=self, - year=year) + leave_type=Leave.SICK, staff=self, year=year or self._current_year + ) except AnnualLeave.DoesNotExist: return Decimal(0) else: return leave_record.get_available_leave_days() def __str__(self): + """Unicode representation of class object.""" return self.get_name() # pylint: disable=no-member class StaffDocument(TimeStampedModel, models.Model): - """ - StaffDocument model class - """ + """StaffDocument model class.""" + staff = models.ForeignKey( - StaffProfile, verbose_name=_('Staff Member'), on_delete=models.CASCADE) - name = models.CharField(_('Name'), max_length=255) - description = models.TextField(_('Description'), blank=True, default='') + StaffProfile, verbose_name=_("Staff Member"), on_delete=models.CASCADE + ) + name = models.CharField(_("Name"), max_length=255) + description = models.TextField(_("Description"), blank=True, default="") file = PrivateFileField( - _('File'), upload_to='staff-documents/', + _("File"), + upload_to="staff-documents/", help_text=_("Upload staff member document"), content_types=[ - 'application/pdf', - 'application/msword', - 'application/vnd.oasis.opendocument.text', - 'image/jpeg', - 'image/png' + "application/pdf", + "application/msword", + "application/vnd.oasis.opendocument.text", + "image/jpeg", + "image/png", ], - max_file_size=10485760 + max_file_size=10485760, ) public = models.BooleanField( - _('Public'), - help_text=_('If public, it will be available to everyone.'), - blank=True, default=False) + _("Public"), + help_text=_("If public, it will be available to everyone."), + blank=True, + default=False, + ) class Meta: # pylint: disable=too-few-public-methods - """ - Meta options for StaffDocument - """ + """Meta options for StaffDocument.""" + abstract = False - verbose_name = _('Staff Document') - verbose_name_plural = _('Staff Documents') - ordering = ['staff', 'name', '-created'] + verbose_name = _("Staff Document") + verbose_name_plural = _("Staff Documents") + ordering = ["staff", "name", "-created"] def __str__(self): - # pylint: disable=no-member - return f'{self.staff.get_name()} - {self.name}' + """Unicode representation of class object.""" + return f"{self.staff.get_name()} - {self.name}" -class BaseStaffRequest(TimeStampedModel, models.Model): - """ - Abstract model class for Leave & Overtime tracking - """ - APPROVED = '1' - REJECTED = '2' - PENDING = '3' - - STATUS_CHOICES = ( - (APPROVED, _('Approved')), - (PENDING, _('Pending')), - (REJECTED, _('Rejected')) - ) +class BaseStaffRequest(TimeStampedModel, AbstractReview): + """Abstract model class for Leave & Overtime tracking.""" staff = models.ForeignKey( - StaffProfile, verbose_name=_('Staff Member'), on_delete=models.CASCADE) - start = models.DateTimeField(_('Start Date')) - end = models.DateTimeField(_('End Date')) - reason = models.TextField(_('Reason'), blank=True, default='') - status = models.CharField( - _('Status'), max_length=1, choices=STATUS_CHOICES, default=PENDING, - blank=True, db_index=True) - comments = models.TextField(_('Comments'), blank=True, default='') + StaffProfile, verbose_name=_("Staff Member"), on_delete=models.CASCADE + ) + start = models.DateTimeField(_("Start Date")) + end = models.DateTimeField(_("End Date")) + review_reason = models.TextField(_("Reason"), blank=True, default="") + review_status = models.CharField( + _("Status"), + max_length=1, + choices=AbstractReview.STATUS_CHOICES, + default=AbstractReview.PENDING, + blank=True, + db_index=True, + ) + + # MODEL REVIEW OPTIONS + # path to function that will be used to determine reviewers + set_reviewers_function: Optional[ + str + ] = "small_small_hr.reviews.set_staff_request_reviewer" + # path to function that will be used to determine the user for a review object + set_user_function: Optional[ + str + ] = "small_small_hr.reviews.set_staff_request_review_user" class Meta: # pylint: disable=too-few-public-methods - """ - Meta options for StaffDocument - """ + """Meta options for BaseStaffRequest.""" + abstract = True class Leave(BaseStaffRequest): - """ - Leave model class - """ - SICK = '1' - REGULAR = '2' + """Leave model class.""" + + SICK = "1" + REGULAR = "2" TYPE_CHOICES = ( - (SICK, _('Sick Leave')), - (REGULAR, _('Regular Leave')), + (SICK, _("Sick Leave")), + (REGULAR, _("Regular Leave")), ) leave_type = models.CharField( - _('Type'), max_length=1, choices=TYPE_CHOICES, default=REGULAR, - blank=True, db_index=True) + _("Type"), + max_length=1, + choices=TYPE_CHOICES, + default=REGULAR, + blank=True, + db_index=True, + ) objects = LeaveManager() + # MODEL REVIEW OPTIONS + email_template_path = EMAIL_TEMPLATE_PATH + # path to function that will be used to send email to reviewers + request_for_review_function: Optional[ + str + ] = "small_small_hr.emails.send_request_for_leave_review" + # path to function that will be used to send email to user after review + review_complete_notify_function: Optional[ + str + ] = "small_small_hr.emails.send_leave_review_complete_notice" + class Meta: # pylint: disable=too-few-public-methods - """ - Meta options for Leave - """ + """Meta options for Leave.""" + abstract = False - verbose_name = _('Leave') - verbose_name_plural = _('Leave') - ordering = ['staff', '-start'] + verbose_name = _("Leave") + verbose_name_plural = _("Leave") + ordering = ["staff", "-start"] def __str__(self): + """Unicode representation of class object.""" # pylint: disable=no-member - return _(f'{self.staff.get_name()}: {self.start} to {self.end}') + return _(f"{self.staff.get_name()}: {self.start} to {self.end}") + + def get_duration(self): + """Get duration.""" + return self.end - self.start + + @cached_property + def duration(self): + """Get duration as a property.""" + return self.get_duration() class OverTime(BaseStaffRequest): - """ - Overtime model class - """ + """Overtime model class.""" + date = models.DateField( - _('Date'), auto_now=False, auto_now_add=False, db_index=True) - start = models.TimeField(_('Start'), auto_now=False, auto_now_add=False) - end = models.TimeField(_('End'), auto_now=False, auto_now_add=False) + _("Date"), auto_now=False, auto_now_add=False, db_index=True + ) + start = models.TimeField(_("Start"), auto_now=False, auto_now_add=False) + end = models.TimeField(_("End"), auto_now=False, auto_now_add=False) + + # MODEL REVIEW OPTIONS + email_template_path = EMAIL_TEMPLATE_PATH + # path to function that will be used to send email to reviewers + request_for_review_function: Optional[ + str + ] = "small_small_hr.emails.send_request_for_overtime_review" + # path to function that will be used to send email to user after review + review_complete_notify_function: Optional[ + str + ] = "small_small_hr.emails.send_overtime_review_complete_notice" class Meta: # pylint: disable=too-few-public-methods - """ - Meta options for OverTime - """ + """Meta options for OverTime.""" + abstract = False - verbose_name = _('Overtime') - verbose_name_plural = _('Overtime') - ordering = ['staff', '-date', 'start'] + verbose_name = _("Overtime") + verbose_name_plural = _("Overtime") + ordering = ["staff", "-date", "start"] def __str__(self): + """Unicode representation of class object.""" name = self.staff.get_name() # pylint: disable=no-member - return _(f'{name}: {self.date} from {self.start} to {self.end}') + return _(f"{name}: {self.date} from {self.start} to {self.end}") def get_duration(self): - """ - Get duration - """ + """Get duration.""" start = datetime.combine(self.date, self.start) end = datetime.combine(self.date, self.end) return end - start + @cached_property + def duration(self): + """Get duration as a property.""" + return self.get_duration() + class AnnualLeave(TimeStampedModel, models.Model): """ - Model to keep track of staff employee annual leave + Model to keep track of staff employee annual leave. This model is meant to be populated once a year Each staff member can only have one record per leave_type per year """ - YEAR_CHOICES = [ - (r, r) for r in range(2017, datetime.today().year + 10) - ] + + YEAR_CHOICES = [(r, r) for r in range(2017, datetime.today().year + 10)] year = models.PositiveIntegerField( - _('Year'), choices=YEAR_CHOICES, default=2017, db_index=True) + _("Year"), choices=YEAR_CHOICES, default=2017, db_index=True + ) staff = models.ForeignKey( - StaffProfile, verbose_name=_('Staff Member'), on_delete=models.CASCADE) + StaffProfile, verbose_name=_("Staff Member"), on_delete=models.CASCADE + ) leave_type = models.CharField( - _('Type'), max_length=1, choices=Leave.TYPE_CHOICES, db_index=True) + _("Type"), max_length=1, choices=Leave.TYPE_CHOICES, db_index=True + ) allowed_days = models.PositiveIntegerField( - _('Allowed Leave days'), default=21, blank=True, - help_text=_('Number of leave days allowed in a year.')) + _("Allowed Leave days"), + default=21, + blank=True, + help_text=_("Number of leave days allowed in a year."), + ) carried_over_days = models.PositiveIntegerField( - _('Carried Over Leave days'), default=0, blank=True, - help_text=_('Number of leave days carried over into this year.')) + _("Carried Over Leave days"), + default=0, + blank=True, + help_text=_("Number of leave days carried over into this year."), + ) class Meta: # pylint: disable=too-few-public-methods - """ - Meta options for AnnualLeave - """ - verbose_name = _('Annual Leave') - verbose_name_plural = _('Annual Leave') - ordering = ['-year', 'leave_type', 'staff'] - unique_together = (('year', 'staff', 'leave_type'),) + """Meta options for AnnualLeave.""" + + verbose_name = _("Annual Leave") + verbose_name_plural = _("Annual Leave") + ordering = ["-year", "leave_type", "staff"] + unique_together = (("year", "staff", "leave_type"),) def __str__(self): + """Unicode representation of class object.""" # pylint: disable=no-member return _( - f'{self.year}: {self.staff.get_name()} ' - f'{self.get_leave_type_display()}') + f"{self.year}: {self.staff.get_name()} " f"{self.get_leave_type_display()}" + ) def get_cumulative_leave_taken(self): """ - Get the cumulative leave taken + Get the cumulative leave taken. Returns a timedelta """ @@ -370,13 +451,11 @@ def get_cumulative_leave_taken(self): status=Leave.APPROVED, leave_type=self.leave_type, start_year=self.year, - end_year=self.year + end_year=self.year, ) def get_available_leave_days(self, month: int = 12): - """ - Get the remaining leave days - """ + """Get the remaining leave days.""" if month <= 0: month = 1 elif month > 12: @@ -402,24 +481,24 @@ def get_available_leave_days(self, month: int = 12): class FreeDay(models.Model): """Model definition for FreeDay.""" + name = models.CharField(_("Name"), max_length=255) - date = models.DateField(_('Date'), unique=True) + date = models.DateField(_("Date"), unique=True) class Meta: """Meta definition for FreeDay.""" - ordering = ['-date'] - verbose_name = _('Free Day') - verbose_name_plural = _('Free Days') + + ordering = ["-date"] + verbose_name = _("Free Day") + verbose_name_plural = _("Free Days") def __str__(self): - """Unicode representation of FreeDay.""" + """Unicode representation of class object.""" return f"{self.date.year} - {self.name}" def get_days(start: object, end: object): - """ - Yield the days between two datetime objects - """ + """Yield the days between two datetime objects.""" current_tz = timezone.get_current_timezone() local_start = current_tz.normalize(start) local_end = current_tz.normalize(end) @@ -428,30 +507,25 @@ def get_days(start: object, end: object): yield local_start.date() + timedelta(days=i) -def get_taken_leave_days( - staffprofile: object, - status: str, - leave_type: str, - start_year: int, - end_year: int): +def get_taken_leave_days( # pylint: disable=bad-continuation + staffprofile: object, status: str, leave_type: str, start_year: int, end_year: int +): """ - Calculate the number of leave days actually taken, - taking into account weekends and weekend policy + Calculate the number of leave days actually taken. + + Takes into account weekends and weekend policy """ count = Decimal(0) free_days = FreeDay.objects.filter( date__year__gte=start_year, date__year__lte=end_year - ).values_list('date', flat=True) + ).values_list("date", flat=True) queryset = Leave.objects.filter( - staff=staffprofile, - status=status, - leave_type=leave_type).filter( - Q(start__year__gte=start_year) | Q(end__year__lte=end_year)) + staff=staffprofile, review_status=status, leave_type=leave_type + ).filter(Q(start__year__gte=start_year) | Q(end__year__lte=end_year)) for leave_obj in queryset: days = get_days(start=leave_obj.start, end=leave_obj.end) for day in days: - if day.year >= start_year and day.year <= end_year and\ - day not in free_days: + if day.year >= start_year and day.year <= end_year and day not in free_days: day_value = settings.SSHR_DAY_LEAVE_VALUES[day.isoweekday()] count = count + Decimal(day_value) return count diff --git a/small_small_hr/reviews.py b/small_small_hr/reviews.py new file mode 100644 index 0000000..c4e2f70 --- /dev/null +++ b/small_small_hr/reviews.py @@ -0,0 +1,50 @@ +"""Review module for small-small-hr.""" +from django.conf import settings +from django.contrib.auth.models import User +from django.db import models + +from model_reviews.models import Reviewer + +from small_small_hr.constants import STAFF + + +def set_staff_request_review_user(review_obj: models.Model): + """ + Set user for Leave and Overtime requests. + + This is the default strategy of auto-setting the user for a review object. + It simply sets the user using a field on the model object that is under review. + """ + if not review_obj.user: + object_under_review = review_obj.content_object + staff_profile = getattr(object_under_review, STAFF, None) + if staff_profile: + review_obj.user = staff_profile.user + + +def set_staff_request_reviewer(review_obj: models.Model): + """ + Set reviewer for Leave and Overtime requests. + + This is the strategy that will be used: + + 1. Set it to the staff member's supervisor + 2. Additionally, set to all members of the Group named SSHR_ADMIN_USER_GROUP_NAME + """ + if review_obj.user: + staff_member = review_obj.user.staffprofile + manager = staff_member.supervisor + if ( + manager + and not Reviewer.objects.filter( + review=review_obj, user=manager.user + ).exists() + ): + reviewer = Reviewer(review=review_obj, user=manager.user) + reviewer.save() # ensure save method is called + + hr_group_name = settings.SSHR_ADMIN_USER_GROUP_NAME + for user in User.objects.filter(groups__name=hr_group_name): + if not Reviewer.objects.filter(review=review_obj, user=user).exists(): + reviewer = Reviewer(review=review_obj, user=user) + reviewer.save() # ensure save method is called diff --git a/small_small_hr/settings.py b/small_small_hr/settings.py index 02546a0..18a34e1 100644 --- a/small_small_hr/settings.py +++ b/small_small_hr/settings.py @@ -16,14 +16,16 @@ SSHR_ALLOW_OVERSUBSCRIBE = True # allow taking more leave days one has SSHR_DEFAULT_TIME = 7 # default time of the day for leave SSHR_FREE_DAYS = [ - {'day': 1, 'month': 1}, # New year - {'day': 1, 'month': 5}, # labour day - {'day': 1, 'month': 6}, # Madaraka day - {'day': 20, 'month': 10}, # Mashujaa day - {'day': 12, 'month': 12}, # Jamhuri day - {'day': 25, 'month': 12}, # Christmas - {'day': 26, 'month': 12}, # Boxing day + {"day": 1, "month": 1}, # New year + {"day": 1, "month": 5}, # labour day + {"day": 1, "month": 6}, # Madaraka day + {"day": 20, "month": 10}, # Mashujaa day + {"day": 12, "month": 12}, # Jamhuri day + {"day": 25, "month": 12}, # Christmas + {"day": 26, "month": 12}, # Boxing day ] # these are days that are not counted when getting taken leave days +# admins +SSHR_ADMIN_USER_GROUP_NAME = "Human Resource" # emails SSHR_ADMIN_NAME = "HR" SSHR_ADMIN_EMAILS = [settings.DEFAULT_FROM_EMAIL] diff --git a/small_small_hr/templates/small_small_hr/email/generic_email_body.html b/small_small_hr/templates/small_small_hr/email/generic_email_body.html deleted file mode 100644 index 7fed0ef..0000000 --- a/small_small_hr/templates/small_small_hr/email/generic_email_body.html +++ /dev/null @@ -1,7 +0,0 @@ -Hello {{name}},

-{{message|linebreaks}} -

-Thank you,
-{{SITE.name}}
-------
-http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/generic_email_body.txt b/small_small_hr/templates/small_small_hr/email/generic_email_body.txt deleted file mode 100644 index ac06063..0000000 --- a/small_small_hr/templates/small_small_hr/email/generic_email_body.txt +++ /dev/null @@ -1,9 +0,0 @@ -Hello {{name}}, - -{{message}} - -Thank you, - -{{SITE.name}} ------- -http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/generic_email_subject.txt b/small_small_hr/templates/small_small_hr/email/generic_email_subject.txt deleted file mode 100644 index b975387..0000000 --- a/small_small_hr/templates/small_small_hr/email/generic_email_subject.txt +++ /dev/null @@ -1 +0,0 @@ -{{subject}} \ No newline at end of file diff --git a/small_small_hr/templates/small_small_hr/email/leave_application_email_body.html b/small_small_hr/templates/small_small_hr/email/leave_application_email_body.html index f0d3095..2dd511d 100644 --- a/small_small_hr/templates/small_small_hr/email/leave_application_email_body.html +++ b/small_small_hr/templates/small_small_hr/email/leave_application_email_body.html @@ -1,5 +1,8 @@ -Hello,

-{{message|linebreaks}} +{{ object.content_object.staff.get_name }} requested time off:

+{{ object.content_object.duration.days }} days of {{ object.content_object.get_leave_type_display}}
+{{ object.content_object.start|date:"D, d M Y" }} - {{ object.content_object.end|date:"D, d M Y" }}
+Available Balance: {{ object.content_object.staff.get_available_leave_days|floatformat:2 }} days

+Please log in to process the above: http://{{SITE.name}}/reviews/{{ object.pk }}

Thank you,
{{SITE.name}}
diff --git a/small_small_hr/templates/small_small_hr/email/leave_application_email_body.txt b/small_small_hr/templates/small_small_hr/email/leave_application_email_body.txt index a1e77af..ec10080 100644 --- a/small_small_hr/templates/small_small_hr/email/leave_application_email_body.txt +++ b/small_small_hr/templates/small_small_hr/email/leave_application_email_body.txt @@ -1,9 +1,14 @@ -Hello, +{{ object.content_object.staff.get_name }} requested time off: -{{message}} +{{ object.content_object.duration.days }} days of {{ object.content_object.get_leave_type_display}} +{{ object.content_object.start|date:"D, d M Y" }} - {{ object.content_object.end|date:"D, d M Y" }} +Available Balance: {{ object.content_object.staff.get_available_leave_days|floatformat:2 }} days + +Please log in to process the above: http://{{SITE.name}}/reviews/{{ object.pk }} Thank you, + {{SITE.name}} ------ http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/leave_application_email_subject.txt b/small_small_hr/templates/small_small_hr/email/leave_application_email_subject.txt index b975387..019041e 100644 --- a/small_small_hr/templates/small_small_hr/email/leave_application_email_subject.txt +++ b/small_small_hr/templates/small_small_hr/email/leave_application_email_subject.txt @@ -1 +1 @@ -{{subject}} \ No newline at end of file +{{ object.content_object.staff.get_name }} requested time off on {{ object.content_object.start|date:"d M" }} to {{ object.content_object.end|date:"d M" }} diff --git a/small_small_hr/templates/small_small_hr/email/leave_completed_email_body.html b/small_small_hr/templates/small_small_hr/email/leave_completed_email_body.html new file mode 100644 index 0000000..a2e32b4 --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/leave_completed_email_body.html @@ -0,0 +1,11 @@ +{{ object.content_object.staff.get_name }},

+Your time off request for {{ object.content_object.duration.days }} days of {{ object.content_object.get_leave_type_display }} from {{ object.content_object.start|date:"d M" }} - {{ object.content_object.end|date:"d M" }} has been {{ object.content_object.get_review_status_display|lower }}.

+{{ object.content_object.start|date:"D, d M Y" }} - {{ object.content_object.end|date:"D, d M Y" }}
+{{ object.content_object.get_leave_type_display}}
+{{ object.content_object.duration.days }} days
+Status: {{ object.content_object.get_review_status_display }} +

+Thank you,
+{{SITE.name}}
+------
+http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/leave_completed_email_body.txt b/small_small_hr/templates/small_small_hr/email/leave_completed_email_body.txt new file mode 100644 index 0000000..955f22f --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/leave_completed_email_body.txt @@ -0,0 +1,15 @@ +{{ object.content_object.staff.get_name }}, + +Your time off request for {{ object.content_object.duration.days }} days of {{ object.content_object.get_leave_type_display }} from {{ object.content_object.start|date:"d M" }} - {{ object.content_object.end|date:"d M" }} has been {{ object.content_object.get_review_status_display|lower }}. + +{{ object.content_object.start|date:"D, d M Y" }} - {{ object.content_object.end|date:"D, d M Y" }} +{{ object.content_object.get_leave_type_display}} +{{ object.content_object.duration.days }} days +Status: {{ object.content_object.get_review_status_display }} + +Thank you, + + +{{SITE.name}} +------ +http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/leave_completed_email_subject.txt b/small_small_hr/templates/small_small_hr/email/leave_completed_email_subject.txt new file mode 100644 index 0000000..0a0819f --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/leave_completed_email_subject.txt @@ -0,0 +1 @@ +Your time off request of {{ object.content_object.start|date:"d M" }} - {{ object.content_object.end|date:"d M" }} has a response diff --git a/small_small_hr/templates/small_small_hr/email/overtime_application_email_body.html b/small_small_hr/templates/small_small_hr/email/overtime_application_email_body.html index f0d3095..6a9078b 100644 --- a/small_small_hr/templates/small_small_hr/email/overtime_application_email_body.html +++ b/small_small_hr/templates/small_small_hr/email/overtime_application_email_body.html @@ -1,5 +1,7 @@ -Hello,

-{{message|linebreaks}} +{% load small_small_hr %}{{ object.content_object.staff.get_name }} requested overtime:

+{{ object.content_object.duration|overtime_duration }} on {{ object.content_object.date|date:"D, d M Y" }}
+{{ object.content_object.start|date:"P" }} - {{ object.content_object.end|date:"P" }}

+Please log in to process the above: http://{{SITE.name}}/reviews/{{ object.pk }}

Thank you,
{{SITE.name}}
diff --git a/small_small_hr/templates/small_small_hr/email/overtime_application_email_body.txt b/small_small_hr/templates/small_small_hr/email/overtime_application_email_body.txt index a1e77af..6130123 100644 --- a/small_small_hr/templates/small_small_hr/email/overtime_application_email_body.txt +++ b/small_small_hr/templates/small_small_hr/email/overtime_application_email_body.txt @@ -1,9 +1,13 @@ -Hello, +{% load small_small_hr %}{{ object.content_object.staff.get_name }} requested overtime: -{{message}} +{{ object.content_object.duration|overtime_duration }} on {{ object.content_object.date|date:"D, d M Y" }} +{{ object.content_object.start|date:"P" }} - {{ object.content_object.end|date:"P" }} + +Please log in to process the above: http://{{SITE.name}}/reviews/{{ object.pk }} Thank you, + {{SITE.name}} ------ http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/overtime_application_email_subject.txt b/small_small_hr/templates/small_small_hr/email/overtime_application_email_subject.txt index b975387..5865f0f 100644 --- a/small_small_hr/templates/small_small_hr/email/overtime_application_email_subject.txt +++ b/small_small_hr/templates/small_small_hr/email/overtime_application_email_subject.txt @@ -1 +1 @@ -{{subject}} \ No newline at end of file +{{ object.content_object.staff.get_name }} requested overtime on {{ object.content_object.date|date:"d M" }} diff --git a/small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.html b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.html new file mode 100644 index 0000000..571349c --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.html @@ -0,0 +1,9 @@ +{% load small_small_hr %}{{ object.content_object.staff.get_name }},

+Your overtime request for {{ object.content_object.duration|overtime_duration }} has been {{ object.content_object.get_review_status_display|lower }}.

+{{ object.content_object.duration|overtime_duration }} on {{ object.content_object.date|date:"D, d M Y" }}
+{{ object.content_object.start|date:"P" }} - {{ object.content_object.end|date:"P" }}
+Status: {{ object.content_object.get_review_status_display }} +Thank you,
+{{SITE.name}}
+------
+http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.txt b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.txt new file mode 100644 index 0000000..4b90eca --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.txt @@ -0,0 +1,13 @@ +{% load small_small_hr %}{{ object.content_object.staff.get_name }}, + +Your overtime request for {{ object.content_object.duration|overtime_duration }} has been {{ object.content_object.get_review_status_display|lower }}. + +{{ object.content_object.duration|overtime_duration }} on {{ object.content_object.date|date:"D, d M Y" }} +{{ object.content_object.start|date:"P" }} - {{ object.content_object.end|date:"P" }} +Status: {{ object.content_object.get_review_status_display }} + +Thank you, + +{{SITE.name}} +------ +http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subject.txt b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subject.txt new file mode 100644 index 0000000..3642088 --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subject.txt @@ -0,0 +1 @@ +Your overtime request of {{ object.content_object.date|date:"d M" }} has a response diff --git a/small_small_hr/templatetags/__init__.py b/small_small_hr/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/small_small_hr/templatetags/small_small_hr.py b/small_small_hr/templatetags/small_small_hr.py new file mode 100644 index 0000000..92a8657 --- /dev/null +++ b/small_small_hr/templatetags/small_small_hr.py @@ -0,0 +1,17 @@ +"""Template tags module.""" +from django import template +from django.utils.translation import ugettext as _ + +from small_small_hr.constants import HOURS, MINUTES + +register = template.Library() + + +@register.filter +def overtime_duration(time_delta): + """Display overtime duration.""" + total_seconds = int(time_delta.total_seconds()) + hours = total_seconds // 3600 + minutes = (total_seconds % 3600) // 60 + + return f"{hours} {_(HOURS)} {minutes} {_(MINUTES)}" diff --git a/small_small_hr/utils.py b/small_small_hr/utils.py index bcaeb77..5304502 100644 --- a/small_small_hr/utils.py +++ b/small_small_hr/utils.py @@ -6,17 +6,16 @@ from django.conf import settings from django.utils import timezone -from small_small_hr.models import AnnualLeave, FreeDay, Leave +from small_small_hr.models import AnnualLeave, FreeDay, Leave, StaffProfile -def get_carry_over(staffprofile: object, year: int, leave_type: str): - """ - Get carried over leave days - """ +def get_carry_over(staffprofile: StaffProfile, year: int, leave_type: str): + """Get carried over leave days.""" # pylint: disable=no-member if leave_type == Leave.REGULAR: previous_obj = AnnualLeave.objects.filter( - staff=staffprofile, year=year - 1, leave_type=leave_type).first() + staff=staffprofile, year=year - 1, leave_type=leave_type + ).first() if previous_obj: remaining = previous_obj.get_available_leave_days() max_carry_over = settings.SSHR_MAX_CARRY_OVER @@ -30,14 +29,13 @@ def get_carry_over(staffprofile: object, year: int, leave_type: str): return 0 -def create_annual_leave(staffprofile: object, year: int, leave_type: str): - """ - Creates an annuall leave object for the staff member - """ +def create_annual_leave(staffprofile: StaffProfile, year: int, leave_type: str): + """Creates an annual leave object for the staff member.""" # pylint: disable=no-member try: annual_leave = AnnualLeave.objects.get( - staff=staffprofile, year=year, leave_type=leave_type) + staff=staffprofile, year=year, leave_type=leave_type + ) except AnnualLeave.DoesNotExist: carry_over = get_carry_over(staffprofile, year, leave_type) @@ -51,17 +49,16 @@ def create_annual_leave(staffprofile: object, year: int, leave_type: str): year=year, leave_type=leave_type, allowed_days=allowed_days, - carried_over_days=carry_over + carried_over_days=carry_over, ) annual_leave.save() return annual_leave -def create_free_days( - start_year: int = timezone.now().year, number_of_years: int = 11): +def create_free_days(start_year: int = timezone.now().year, number_of_years: int = 11): """ - Create FreeDay records + Create FreeDay records. :param start_year: the year from which to start creating free days :param number_of_years: number of years to create free days objects @@ -71,12 +68,7 @@ def create_free_days( for year in years: for default_day in default_days: the_date = date( - year=year, - month=default_day['month'], - day=default_day['day'], - ) - free_day = FreeDay( - name=the_date.strftime("%A %d %B %Y"), - date=the_date, + year=year, month=default_day["month"], day=default_day["day"], ) + free_day = FreeDay(name=the_date.strftime("%A %d %B %Y"), date=the_date,) free_day.save() diff --git a/tests/settings.py b/tests/settings.py index f837e54..172c8c4 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -8,30 +8,31 @@ INSTALLED_APPS = [ # core django apps - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", # third party - 'sorl.thumbnail', - 'private_storage', - 'phonenumber_field', - 'crispy_forms', - 'rest_framework', + "sorl.thumbnail", + "private_storage", + "phonenumber_field", + "crispy_forms", + "rest_framework", # custom - 'small_small_hr', + "small_small_hr", + "model_reviews", ] DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'small_small_hr', - 'USER': 'postgres', - 'PASSWORD': '', - 'HOST': '127.0.0.1' + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "small_small_hr", + "USER": "postgres", + "PASSWORD": "", + "HOST": "127.0.0.1", } } @@ -47,33 +48,36 @@ TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -TIME_ZONE = 'Africa/Nairobi' +TIME_ZONE = "Africa/Nairobi" USE_I18N = True USE_L10N = True USE_TZ = True SECRET_KEY = "i love oov" -PRIVATE_STORAGE_ROOT = '/tmp/' -MEDIA_ROOT = '/tmp/' -PRIVATE_STORAGE_AUTH_FUNCTION = 'private_storage.permissions.allow_staff' +PRIVATE_STORAGE_ROOT = "/tmp/" +MEDIA_ROOT = "/tmp/" +PRIVATE_STORAGE_AUTH_FUNCTION = "private_storage.permissions.allow_staff" SITE_ID = 1 +# snapshot testing +TEST_RUNNER = "snapshottest.django.TestRunner" + # try and load local_settings if present try: # pylint: disable=wildcard-import diff --git a/tests/snapshots/__init__.py b/tests/snapshots/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/snapshots/snap_test_emails.py b/tests/snapshots/snap_test_emails.py new file mode 100644 index 0000000..aa64913 --- /dev/null +++ b/tests/snapshots/snap_test_emails.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# snapshottest: v1 - https://goo.gl/zC4yUc +from __future__ import unicode_literals + +from snapshottest import Snapshot + + +snapshots = Snapshot() + +snapshots['TestEmails::test_leave_emails 1'] = '''Mosh Pitt requested time off: + +5 days of Regular Leave +Mon, 05 Jun 2017 - Sat, 10 Jun 2017 +Available Balance: 17.00 days + +Please log in to process the above: http://example.com/reviews/1338 + +Thank you, + + +example.com +------ +http://example.com +''' + +snapshots['TestEmails::test_leave_emails 2'] = 'Mosh Pitt requested time off:

5 days of Regular Leave
Mon, 05 Jun 2017 - Sat, 10 Jun 2017
Available Balance: 17.00 days

Please log in to process the above: http://example.com/reviews/1338

Thank you,
example.com
------
http://example.com' + +snapshots['TestEmails::test_overtime_emails 1'] = '''Mosh Pitt requested overtime: + +4 hours 45 minutes on Mon, 05 Jun 2017 +4:45 p.m. - 9:30 p.m. + +Please log in to process the above: http://example.com/reviews/1337 + +Thank you, + + +example.com +------ +http://example.com +''' + +snapshots['TestEmails::test_overtime_emails 2'] = 'Mosh Pitt requested overtime:

4 hours 45 minutes on Mon, 05 Jun 2017
4:45 p.m. - 9:30 p.m.

Please log in to process the above: http://example.com/reviews/1337

Thank you,
example.com
------
http://example.com' + +snapshots['TestEmails::test_leave_emails 3'] = '''Mosh Pitt, + +Your time off request for 5 days of Regular Leave from 05 Jun - 10 Jun has been approved. + +Mon, 05 Jun 2017 - Sat, 10 Jun 2017 +Regular Leave +5 days +Status: Approved + +Thank you, + + +example.com +------ +http://example.com +''' + +snapshots['TestEmails::test_leave_emails 4'] = 'Mosh Pitt,

Your time off request for 5 days of Regular Leave from 05 Jun - 10 Jun has been approved.

Mon, 05 Jun 2017 - Sat, 10 Jun 2017
Regular Leave
5 days
Status: Approved

Thank you,
example.com
------
http://example.com' + +snapshots['TestEmails::test_leave_emails 5'] = '''Mosh Pitt, + +Your time off request for 5 days of Regular Leave from 05 Jun - 10 Jun has been rejected. + +Mon, 05 Jun 2017 - Sat, 10 Jun 2017 +Regular Leave +5 days +Status: Rejected + +Thank you, + + +example.com +------ +http://example.com +''' + +snapshots['TestEmails::test_leave_emails 6'] = 'Mosh Pitt,

Your time off request for 5 days of Regular Leave from 05 Jun - 10 Jun has been rejected.

Mon, 05 Jun 2017 - Sat, 10 Jun 2017
Regular Leave
5 days
Status: Rejected

Thank you,
example.com
------
http://example.com' + +snapshots['TestEmails::test_overtime_emails 3'] = '''Mosh Pitt, + +Your overtime request for 4 hours 45 minutes has been approved. + +4 hours 45 minutes on Mon, 05 Jun 2017 +4:45 p.m. - 9:30 p.m. +Status: Approved + +Thank you, + +example.com +------ +http://example.com +''' + +snapshots['TestEmails::test_overtime_emails 4'] = 'Mosh Pitt,

Your overtime request for 4 hours 45 minutes has been approved.

4 hours 45 minutes on Mon, 05 Jun 2017
4:45 p.m. - 9:30 p.m.
Status: ApprovedThank you,
example.com
------
http://example.com' + +snapshots['TestEmails::test_overtime_emails 5'] = '''Mosh Pitt, + +Your overtime request for 4 hours 45 minutes has been rejected. + +4 hours 45 minutes on Mon, 05 Jun 2017 +4:45 p.m. - 9:30 p.m. +Status: Rejected + +Thank you, + +example.com +------ +http://example.com +''' + +snapshots['TestEmails::test_overtime_emails 6'] = 'Mosh Pitt,

Your overtime request for 4 hours 45 minutes has been rejected.

4 hours 45 minutes on Mon, 05 Jun 2017
4:45 p.m. - 9:30 p.m.
Status: RejectedThank you,
example.com
------
http://example.com' diff --git a/tests/test_emails.py b/tests/test_emails.py index 2ec2846..e5026e7 100644 --- a/tests/test_emails.py +++ b/tests/test_emails.py @@ -1,286 +1,189 @@ -""" -Module to test small_small_hr Emails -""" +"""Module to test small_small_hr Emails.""" from datetime import datetime -from unittest.mock import call, patch from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.core import mail -from django.test import TestCase, override_settings +from django.test import override_settings import pytz +from freezegun import freeze_time from model_mommy import mommy +from model_reviews.forms import PerformReview +from model_reviews.models import ModelReview, Reviewer +from snapshottest.django import TestCase -from small_small_hr.emails import (leave_application_email, - leave_processed_email, - overtime_application_email, - overtime_processed_email, send_email) -from small_small_hr.models import Leave, OverTime +from small_small_hr.forms import ApplyLeaveForm, ApplyOverTimeForm +from small_small_hr.models import Leave, StaffProfile +from small_small_hr.utils import create_annual_leave -@override_settings( - SSHR_ADMIN_EMAILS=["admin@example.com"], - SSHR_ADMIN_LEAVE_EMAILS=["hr@example.com"], - SSHR_ADMIN_OVERTIME_EMAILS=["ot@example.com"], - SSHR_ADMIN_NAME="mosh" -) +@override_settings(ROOT_URLCONF="tests.urls") class TestEmails(TestCase): - """ - Test class for emails - """ + """Test class for emails.""" + + maxDiff = None def setUp(self): - """ - Set up - """ + """Set up.""" self.user = mommy.make( - 'auth.User', first_name='Bob', last_name='Ndoe', - email="bob@example.com") - self.staffprofile = mommy.make( - 'small_small_hr.StaffProfile', user=self.user) - - @patch('small_small_hr.emails.send_email') - def test_leave_application_email(self, mock): - """ - Test leave_application_email - """ - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - leave = mommy.make( - 'small_small_hr.Leave', staff=self.staffprofile, start=start, - end=end, leave_type=Leave.SICK, - status=Leave.PENDING) - - leave_application_email(leave) - - mock.assert_called_with( - name="mosh", - email="hr@example.com", - subject="New Leave Application", - message="There has been a new leave application. Please log in to process it.", # noqa - obj=leave, - template="leave_application", + "auth.User", first_name="Mosh", last_name="Pitt", email="bob@example.com" ) + self.staffprofile = mommy.make("small_small_hr.StaffProfile", user=self.user) + self.staffprofile.leave_days = 17 + self.staffprofile.sick_days = 9 + self.staffprofile.save() + self.staffprofile.refresh_from_db() - @patch('small_small_hr.emails.send_email') - def test_leave_processed_email(self, mock): - """ - Test leave_processed_email - """ - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - leave = mommy.make( - 'small_small_hr.Leave', staff=self.staffprofile, start=start, - end=end, leave_type=Leave.SICK, - status=Leave.APPROVED) + create_annual_leave(self.staffprofile, 2017, Leave.REGULAR) - leave_processed_email(leave) + StaffProfile.objects.rebuild() - mock.assert_called_with( - name="Bob Ndoe", - email="bob@example.com", - subject="Your leave application has been processed", - message="You leave application status is Approved. Log in for more info.", # noqa - obj=leave, - cc_list=['hr@example.com'] + hr_group = mommy.make("auth.Group", name=settings.SSHR_ADMIN_USER_GROUP_NAME) + self.boss = mommy.make( + "auth.User", first_name="Mother", last_name="Hen", email="hr@example.com" ) - - @patch('small_small_hr.emails.send_email') - def test_overtime_application_email(self, mock): - """ - Test overtime_application_email - """ - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - overtime = mommy.make( - 'small_small_hr.OverTime', staff=self.staffprofile, start=start, - end=end, status=OverTime.PENDING) - - overtime_application_email(overtime) - - mock.assert_called_with( - name="mosh", - email="ot@example.com", - subject="New Overtime Application", - message="There has been a new overtime application. Please log in to process it.", # noqa - obj=overtime, - template="overtime_application", + self.boss.groups.add(hr_group) + + @freeze_time("June 1st, 2017") + def test_leave_emails(self): + """Test Leave emails.""" + # apply for leave + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + data = { + "staff": self.staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", + } + form = ApplyLeaveForm(data=data) + self.assertTrue(form.is_valid()) + leave = form.save() + + obj_type = ContentType.objects.get_for_model(leave) + # Hard code the pk for the snapshot test + # empty the test outbox so that we don't deal with the old review's emails + mail.outbox = [] + ModelReview.objects.get(content_type=obj_type, object_id=leave.id).delete() + review = mommy.make( + "model_reviews.ModelReview", + content_type=obj_type, + object_id=leave.id, + id=1338, ) + reviewer = Reviewer.objects.get(review=review, user=self.boss) - @patch('small_small_hr.emails.send_email') - def test_overtime_processed_email(self, mock): - """ - Test overtime_processed_email - """ - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - overtime = mommy.make( - 'small_small_hr.OverTime', staff=self.staffprofile, start=start, - end=end, status=OverTime.REJECTED) - - overtime_processed_email(overtime) - - mock.assert_called_with( - name="Bob Ndoe", - email="bob@example.com", - subject="Your overtime application has been processed", - message="You overtime application status is Rejected. Log in for more info.", # noqa - obj=overtime, - cc_list=['ot@example.com'] + self.assertEqual( + "Mosh Pitt requested time off on 05 Jun to 10 Jun", mail.outbox[0].subject ) + self.assertEqual(["Mother Hen "], mail.outbox[0].to) + self.assertMatchSnapshot(mail.outbox[0].body) + self.assertMatchSnapshot(mail.outbox[0].alternatives[0][0]) - def test_send_email(self): - """ - Test send_email - """ - - message = "The quick brown fox." - + # approve the review data = { - 'name': 'Bob Munro', - 'email': 'bob@example.com', - 'subject': "I love oov", - 'message': message, - 'cc_list': settings.SSHR_ADMIN_EMAILS + "review": review.pk, + "reviewer": reviewer.pk, + "review_status": ModelReview.APPROVED, } - - send_email(**data) - - self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, 'I love oov') - self.assertEqual(mail.outbox[0].to, ['Bob Munro ']) - self.assertEqual(mail.outbox[0].cc, ['admin@example.com']) - self.assertEqual( - mail.outbox[0].body, - 'Hello Bob Munro,\n\nThe quick brown fox.\n\nThank you,\n\n' - 'example.com\n------\nhttp://example.com\n') + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() self.assertEqual( - mail.outbox[0].alternatives[0][0], - 'Hello Bob Munro,

The quick brown fox.



' - 'Thank you,
example.com
------
http://example.com') - - @patch('small_small_hr.emails.Site.objects.get_current') - @patch('small_small_hr.emails.render_to_string') - def test_send_email_templates(self, mock, site_mock): - """ - Test the templates used with send_email - """ - mock.return_value = "Some random text" - site_mock.return_value = 42 # ensure that this is predictable + "Your time off request of 05 Jun - 10 Jun has a response", + mail.outbox[1].subject, + ) + self.assertEqual(["Mosh Pitt "], mail.outbox[1].to) + self.assertMatchSnapshot(mail.outbox[1].body) + self.assertMatchSnapshot(mail.outbox[1].alternatives[0][0]) - # test generic + # then reject it data = { - 'name': 'Bob Munro', - 'email': 'bob@example.com', - 'subject': "I love oov", - 'message': "Its dangerous", + "review": review.pk, + "reviewer": reviewer.pk, + "review_status": ModelReview.REJECTED, } + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() + self.assertEqual( + "Your time off request of 05 Jun - 10 Jun has a response", + mail.outbox[2].subject, + ) + self.assertEqual(["Mosh Pitt "], mail.outbox[2].to) + self.assertMatchSnapshot(mail.outbox[2].body) + self.assertMatchSnapshot(mail.outbox[2].alternatives[0][0]) - send_email(**data) - - context = data.copy() - context.pop("email") - context["object"] = None - context["SITE"] = 42 - - expected_calls = [ - call( - "small_small_hr/email/generic_email_subject.txt", - context - ), - call( - "small_small_hr/email/generic_email_body.txt", - context - ), - call( - "small_small_hr/email/generic_email_body.html", - context - ) - ] - - mock.assert_has_calls(expected_calls) - - mock.reset_mock() - - # test leave + def test_overtime_emails(self): + """Test Overtime emails.""" + # apply for overtime start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - leave = mommy.make( - 'small_small_hr.Leave', staff=self.staffprofile, start=start, - end=end, leave_type=Leave.SICK, - status=Leave.PENDING) - - leave_application_email(leave) - - context = dict( - name="mosh", - subject="New Leave Application", - message="There has been a new leave application. Please log in to process it.", # noqa - object=leave, - SITE=42 + 2017, 6, 5, 16, 45, 0, tzinfo=pytz.timezone(settings.TIME_ZONE) ) + end = datetime(2017, 6, 5, 21, 30, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - expected_calls = [ - call( - "small_small_hr/email/leave_application_email_subject.txt", - context - ), - call( - "small_small_hr/email/leave_application_email_body.txt", - context - ), - call( - "small_small_hr/email/leave_application_email_body.html", - context - ) - ] - - mock.assert_has_calls(expected_calls) - - mock.reset_mock() - - # test overtime - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - overtime = mommy.make( - 'small_small_hr.OverTime', staff=self.staffprofile, start=start, - end=end, status=OverTime.PENDING) + data = { + "staff": self.staffprofile.id, + "date": start.date(), + "start": start.time(), + "end": end.time(), + "review_reason": "Extra work", + } - overtime_application_email(overtime) + form = ApplyOverTimeForm(data=data) + self.assertTrue(form.is_valid()) + overtime = form.save() + + obj_type = ContentType.objects.get_for_model(overtime) + # Hard code the pk for the snapshot test + # empty the test outbox so that we don't deal with the old review's emails + mail.outbox = [] + ModelReview.objects.get(content_type=obj_type, object_id=overtime.id).delete() + review = mommy.make( + "model_reviews.ModelReview", + content_type=obj_type, + object_id=overtime.id, + id=1337, + ) + reviewer = Reviewer.objects.get(review=review, user=self.boss) - context = dict( - name="mosh", - subject="New Overtime Application", - message="There has been a new overtime application. Please log in to process it.", # noqa - object=overtime, - SITE=42 + self.assertEqual( + "Mosh Pitt requested overtime on 05 Jun", mail.outbox[0].subject ) + self.assertEqual(["Mother Hen "], mail.outbox[0].to) + self.assertMatchSnapshot(mail.outbox[0].body) + self.assertMatchSnapshot(mail.outbox[0].alternatives[0][0]) - expected_calls = [ - call( - "small_small_hr/email/overtime_application_email_subject.txt", - context - ), - call( - "small_small_hr/email/overtime_application_email_body.txt", - context - ), - call( - "small_small_hr/email/overtime_application_email_body.html", - context - ) - ] + # approve the overtime + data = { + "review": review.pk, + "reviewer": reviewer.pk, + "review_status": ModelReview.APPROVED, + } + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() + self.assertEqual( + "Your overtime request of 05 Jun has a response", mail.outbox[1].subject, + ) + self.assertEqual(["Mosh Pitt "], mail.outbox[1].to) + self.assertMatchSnapshot(mail.outbox[1].body) + self.assertMatchSnapshot(mail.outbox[1].alternatives[0][0]) - mock.assert_has_calls(expected_calls) + # then reject it + data = { + "review": review.pk, + "reviewer": reviewer.pk, + "review_status": ModelReview.REJECTED, + } + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() + self.assertEqual( + "Your overtime request of 05 Jun has a response", mail.outbox[2].subject, + ) + self.assertEqual(["Mosh Pitt "], mail.outbox[2].to) + self.assertMatchSnapshot(mail.outbox[2].body) + self.assertMatchSnapshot(mail.outbox[2].alternatives[0][0]) diff --git a/tests/test_forms.py b/tests/test_forms.py index 4576117..819b81f 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -1,58 +1,60 @@ -""" -Module to test small_small_hr models -""" +"""Module to test small_small_hr models.""" +# pylint: disable=too-many-lines import os -from datetime import datetime, timedelta, date -from unittest.mock import patch +from datetime import date, datetime, timedelta -import pytz from django.conf import settings from django.contrib.auth.models import AnonymousUser +from django.contrib.contenttypes.models import ContentType from django.core.files.uploadedfile import SimpleUploadedFile from django.test import RequestFactory, TestCase, override_settings -from model_mommy import mommy -from small_small_hr.forms import (AnnualLeaveForm, ApplyLeaveForm, - ApplyOverTimeForm, LeaveForm, OverTimeForm, - RoleForm, StaffDocumentForm, - StaffProfileAdminCreateForm, - StaffProfileAdminForm, StaffProfileUserForm, - UserStaffDocumentForm, FreeDayForm) -from small_small_hr.models import (Leave, OverTime, StaffProfile, - get_taken_leave_days) +import pytz +from model_mommy import mommy +from model_reviews.models import ModelReview + +from small_small_hr.forms import ( + AnnualLeaveForm, + ApplyLeaveForm, + ApplyOverTimeForm, + FreeDayForm, + LeaveForm, + OverTimeForm, + RoleForm, + StaffDocumentForm, + StaffProfileAdminCreateForm, + StaffProfileAdminForm, + StaffProfileUserForm, + UserStaffDocumentForm, +) +from small_small_hr.models import Leave, OverTime, StaffProfile, get_taken_leave_days from small_small_hr.serializers import StaffProfileSerializer BASE_DIR = os.path.dirname(os.path.dirname(__file__)) -class TestForms(TestCase): - """ - Test class for forms - """ +class TestForms(TestCase): # pylint: disable=too-many-public-methods + """Test class for forms.""" def setUp(self): - """ - Setup test class - """ + """Set up test class.""" self.factory = RequestFactory() def test_annual_leave_form(self): - """ - Test AnnualLeaveForm - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test AnnualLeaveForm.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() data = { - 'staff': staffprofile.id, - 'year': 2018, - 'leave_type': Leave.REGULAR, - 'allowed_days': 21, - 'carried_over_days': 10 + "staff": staffprofile.id, + "year": 2018, + "leave_type": Leave.REGULAR, + "allowed_days": 21, + "carried_over_days": 10, } form = AnnualLeaveForm(data=data) @@ -65,11 +67,11 @@ def test_annual_leave_form(self): self.assertEqual(Leave.REGULAR, annual_leave.leave_type) data2 = { - 'staff': staffprofile.id, - 'year': 2017, - 'leave_type': Leave.REGULAR, - 'allowed_days': 21, - 'carried_over_days': 5 + "staff": staffprofile.id, + "year": 2017, + "leave_type": Leave.REGULAR, + "allowed_days": 21, + "carried_over_days": 5, } form = AnnualLeaveForm(data=data2, instance=annual_leave) @@ -83,41 +85,31 @@ def test_annual_leave_form(self): self.assertEqual(Leave.REGULAR, annual_leave.leave_type) def test_role_form(self): - """ - Test RoleForm - """ - request = self.factory.get('/') + """Test RoleForm.""" + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - data = { - 'name': 'Accountant', - 'description': 'Keep accounts' - } + data = {"name": "Accountant", "description": "Keep accounts"} form = RoleForm(data=data) self.assertTrue(form.is_valid()) role = form.save() - self.assertEqual('Accountant', role.name) - self.assertEqual('Keep accounts', role.description) + self.assertEqual("Accountant", role.name) + self.assertEqual("Keep accounts", role.description) def test_freeday_form(self): - """ - Test FreeDayForm - """ - request = self.factory.get('/') + """Test FreeDayForm.""" + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - data = { - 'name': 'Mosh Day', - 'date': '1/1/2017' - } + data = {"name": "Mosh Day", "date": "1/1/2017"} form = FreeDayForm(data=data) self.assertTrue(form.is_valid()) free_day = form.save() - self.assertEqual('Mosh Day', free_day.name) + self.assertEqual("Mosh Day", free_day.name) self.assertEqual(date(2017, 1, 1), free_day.date) # has to be unique @@ -125,34 +117,28 @@ def test_freeday_form(self): self.assertFalse(form2.is_valid()) self.assertEqual(1, len(form2.errors.keys())) self.assertEqual( - 'Free Day with this Date already exists.', - form2.errors['date'][0] + "Free Day with this Date already exists.", form2.errors["date"][0] ) - @patch('small_small_hr.forms.overtime_application_email') - def test_overtime_form_apply(self, mock): - """ - Test OverTimeForm - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + def test_overtime_form_apply(self): + """Test OverTimeForm.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 hours of overtime - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) data = { - 'staff': staffprofile.id, - 'date': start.date(), - 'start': start.time(), - 'end': end.time(), - 'reason': 'Extra work', + "staff": staffprofile.id, + "date": start.date(), + "start": start.time(), + "end": end.time(), + "review_reason": "Extra work", } form = ApplyOverTimeForm(data=data) @@ -163,83 +149,77 @@ def test_overtime_form_apply(self, mock): self.assertEqual(start.time(), overtime.start) self.assertEqual(end.time(), overtime.end) self.assertEqual( - timedelta(seconds=3600 * 6).seconds, - overtime.get_duration().seconds) - self.assertEqual('Extra work', overtime.reason) - self.assertEqual(OverTime.PENDING, overtime.status) - self.assertEqual('', overtime.comments) - mock.assert_called_with(overtime_obj=overtime) + timedelta(seconds=3600 * 6).seconds, overtime.get_duration().seconds + ) + self.assertEqual("Extra work", overtime.review_reason) + self.assertEqual(OverTime.PENDING, overtime.review_status) def test_overtime_form_apply_no_overlap(self): - """ - Test no overlaps on OverTime - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test no overlaps on OverTime.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 hours of overtime - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) mommy.make( - 'small_small_hr.OverTime', start=start.time(), end=end.time(), - status=OverTime.APPROVED, date=start.date, staff=staffprofile) + "small_small_hr.OverTime", + start=start.time(), + end=end.time(), + review_status=OverTime.APPROVED, + date=start.date, + staff=staffprofile, + ) data = { - 'staff': staffprofile.id, - 'date': start.date(), - 'start': start.time(), - 'end': end.time(), - 'reason': 'Extra work', + "staff": staffprofile.id, + "date": start.date(), + "start": start.time(), + "end": end.time(), + "review_reason": "Extra work", } form = ApplyOverTimeForm(data=data) self.assertFalse(form.is_valid()) self.assertEqual(3, len(form.errors.keys())) self.assertEqual( - 'you cannot have overlapping overtime hours on the same day', - form.errors['start'][0] + "you cannot have overlapping overtime hours on the same day", + form.errors["start"][0], ) self.assertEqual( - 'you cannot have overlapping overtime hours on the same day', - form.errors['date'][0] + "you cannot have overlapping overtime hours on the same day", + form.errors["date"][0], ) self.assertEqual( - 'you cannot have overlapping overtime hours on the same day', - form.errors['end'][0] + "you cannot have overlapping overtime hours on the same day", + form.errors["end"][0], ) def test_overtime_form_process(self): - """ - Test OverTimeForm - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test OverTimeForm.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 hours of overtime - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) data = { - 'staff': staffprofile.id, - 'date': start.date(), - 'start': start.time(), - 'end': end.time(), - 'reason': 'Extra work', - 'status': OverTime.APPROVED, - 'comments': 'Cool' + "staff": staffprofile.id, + "date": start.date(), + "start": start.time(), + "end": end.time(), + "review_reason": "Extra work", + "review_status": OverTime.APPROVED, } form = OverTimeForm(data=data) @@ -250,42 +230,41 @@ def test_overtime_form_process(self): self.assertEqual(start.time(), overtime.start) self.assertEqual(end.time(), overtime.end) self.assertEqual( - timedelta(seconds=3600 * 6).seconds, - overtime.get_duration().seconds) - self.assertEqual('Extra work', overtime.reason) - self.assertEqual(OverTime.APPROVED, overtime.status) - self.assertEqual('Cool', overtime.comments) + timedelta(seconds=3600 * 6).seconds, overtime.get_duration().seconds + ) + self.assertEqual("Extra work", overtime.review_reason) + self.assertEqual(OverTime.APPROVED, overtime.review_status) def test_overtime_form_process_with_overlap(self): - """ - Test OverTimeForm with overlap for existing objects - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test OverTimeForm with overlap for existing objects.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 hours of overtime - start = datetime( - 2017, 6, 5, 18, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 5, 19, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 18, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 5, 19, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # make sure object already exists mommy.make( - 'small_small_hr.OverTime', start=start.time(), end=end.time(), - status=OverTime.APPROVED, date=start.date, staff=staffprofile) + "small_small_hr.OverTime", + start=start.time(), + end=end.time(), + review_status=OverTime.APPROVED, + date=start.date, + staff=staffprofile, + ) data = { - 'staff': staffprofile.id, - 'date': start.date(), - 'start': start.time(), - 'end': end.time(), - 'reason': 'Extra work', - 'status': OverTime.REJECTED, - 'comments': 'Already there' + "staff": staffprofile.id, + "date": start.date(), + "start": start.time(), + "end": end.time(), + "review_reason": "Extra work", + "review_status": OverTime.REJECTED, } form = OverTimeForm(data=data) @@ -296,75 +275,67 @@ def test_overtime_form_process_with_overlap(self): self.assertEqual(start.time(), overtime.start) self.assertEqual(end.time(), overtime.end) self.assertEqual( - timedelta(seconds=3600).seconds, - overtime.get_duration().seconds) - self.assertEqual('Extra work', overtime.reason) - self.assertEqual(OverTime.REJECTED, overtime.status) - self.assertEqual('Already there', overtime.comments) + timedelta(seconds=3600).seconds, overtime.get_duration().seconds + ) + self.assertEqual("Extra work", overtime.review_reason) + self.assertEqual(OverTime.REJECTED, overtime.review_status) def test_overtime_form_start_end(self): - """ - Test OverTimeForm start end fields - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test OverTimeForm start end fields.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - start = datetime( - 2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 5, 5, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 5, 5, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) data = { - 'staff': staffprofile.id, - 'date': start.date(), - 'start': start.time(), - 'end': end.time(), - 'reason': 'Extra work', + "staff": staffprofile.id, + "date": start.date(), + "start": start.time(), + "end": end.time(), + "review_reason": "Extra work", } form = OverTimeForm(data=data) self.assertFalse(form.is_valid()) self.assertEqual(1, len(form.errors.keys())) - self.assertEqual( - 'end must be greater than start', - form.errors['end'][0] - ) + self.assertEqual("end must be greater than start", form.errors["end"][0]) @override_settings(SSHR_DEFAULT_TIME=7) - @patch('small_small_hr.forms.leave_application_email') - def test_leaveform_apply(self, mock): - """ - Test LeaveForm apply for leave - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + def test_leaveform_apply(self): + """Test LeaveForm apply for leave.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, carried_over_days=12) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + carried_over_days=12, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Need a break', + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -374,12 +345,9 @@ def test_leaveform_apply(self, mock): self.assertEqual(Leave.REGULAR, leave.leave_type) self.assertEqual(start, leave.start) self.assertEqual(end, leave.end) - self.assertEqual( - timedelta(days=5).days, (leave.end - leave.start).days) - self.assertEqual('Need a break', leave.reason) - self.assertEqual(Leave.PENDING, leave.status) - self.assertEqual('', leave.comments) - mock.assert_called_with(leave_obj=leave) + self.assertEqual(timedelta(days=5).days, (leave.end - leave.start).days) + self.assertEqual("Need a break", leave.review_reason) + self.assertEqual(Leave.PENDING, leave.review_status) @override_settings( SSHR_DEFAULT_TIME=7, @@ -392,38 +360,38 @@ def test_leaveform_apply(self, mock): 5: 1, # Friday 6: 1, # Saturday 7: 1, # Sunday - } + }, ) - @patch('small_small_hr.forms.leave_application_email') - def test_leave_oversubscribe(self, mock): - """ - Test leave oversubscribe works as expected - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + def test_leave_oversubscribe(self): + """Test leave oversubscribe works as expected.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 40 days of leave - start = datetime( - 2017, 6, 1, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 7, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 1, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 7, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, carried_over_days=0) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + carried_over_days=0, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Mini retirement', + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Mini retirement", } form = ApplyLeaveForm(data=data) @@ -431,24 +399,24 @@ def test_leave_oversubscribe(self, mock): leave = form.save() # make it approved - leave.status = Leave.APPROVED - leave.save() + obj_type = ContentType.objects.get_for_model(leave) + review = ModelReview.objects.get(content_type=obj_type, object_id=leave.id) + review.review_status = ModelReview.APPROVED + review.save() leave.refresh_from_db() self.assertEqual(staffprofile, leave.staff) self.assertEqual(Leave.REGULAR, leave.leave_type) self.assertEqual(start, leave.start) self.assertEqual(end, leave.end) - self.assertEqual( - timedelta(days=39).days, (leave.end - leave.start).days) - self.assertEqual('Mini retirement', leave.reason) - self.assertEqual(Leave.APPROVED, leave.status) - self.assertEqual('', leave.comments) - mock.assert_called_with(leave_obj=leave) + self.assertEqual(timedelta(days=39).days, (leave.end - leave.start).days) + self.assertEqual("Mini retirement", leave.review_reason) + self.assertEqual(Leave.APPROVED, leave.review_status) self.assertEqual( 40, get_taken_leave_days( - staffprofile, Leave.APPROVED, Leave.REGULAR, 2017, 2017) + staffprofile, Leave.APPROVED, Leave.REGULAR, 2017, 2017 + ), ) self.assertEqual(-19, staffprofile.get_available_leave_days(year=2017)) @@ -463,85 +431,83 @@ def test_leave_oversubscribe(self, mock): 5: 1, # Friday 6: 1, # Saturday 7: 1, # Sunday - } + }, ) - @patch('small_small_hr.forms.leave_application_email') - def test_leave_oversubscribe_off(self, mock): - """ - Test leave oversubscribe when SSHR_ALLOW_OVERSUBSCRIBE is False - """ - mock.return_value = None - - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + def test_leave_oversubscribe_off(self): + """Test leave oversubscribe when SSHR_ALLOW_OVERSUBSCRIBE is False.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 40 days of leave - start = datetime( - 2017, 6, 1, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 7, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 1, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 7, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, carried_over_days=0) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + carried_over_days=0, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Mini retirement', + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Mini retirement", } form = ApplyLeaveForm(data=data) self.assertFalse(form.is_valid()) self.assertEqual(2, len(form.errors.keys())) self.assertEqual( - 'Not enough leave days. Available leave days are 21.00', - form.errors['start'][0] + "Not enough leave days. Available leave days are 21.00", + form.errors["start"][0], ) self.assertEqual( - 'Not enough leave days. Available leave days are 21.00', - form.errors['end'][0] + "Not enough leave days. Available leave days are 21.00", + form.errors["end"][0], ) @override_settings(SSHR_DEFAULT_TIME=7) - @patch('small_small_hr.forms.leave_application_email') - def test_one_day_leave(self, mock): - """ - Test application for one day leave - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + def test_one_day_leave(self): + """Test application for one day leave.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 1 day of leave - start = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, carried_over_days=12) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + carried_over_days=12, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Need a break', + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -551,98 +517,100 @@ def test_one_day_leave(self, mock): self.assertEqual(Leave.REGULAR, leave.leave_type) self.assertEqual(start, leave.start) self.assertEqual(end, leave.end) - self.assertEqual( - timedelta(days=0).days, (leave.end - leave.start).days) - self.assertEqual('Need a break', leave.reason) - self.assertEqual(Leave.PENDING, leave.status) - self.assertEqual('', leave.comments) - mock.assert_called_with(leave_obj=leave) + self.assertEqual(timedelta(days=0).days, (leave.end - leave.start).days) + self.assertEqual("Need a break", leave.review_reason) + self.assertEqual(Leave.PENDING, leave.review_status) self.assertEqual( 1, get_taken_leave_days( - staffprofile, Leave.PENDING, Leave.REGULAR, 2017, 2017) + staffprofile, Leave.PENDING, Leave.REGULAR, 2017, 2017 + ), ) @override_settings(SSHR_DEFAULT_TIME=7) def test_leaveform_no_overlap(self): - """ - Test LeaveForm no overlap - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test LeaveForm no overlap.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, carried_over_days=12) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + carried_over_days=12, + ) - mommy.make('small_small_hr.Leave', leave_type=Leave.REGULAR, - start=start, end=end, status=Leave.APPROVED, - staff=staffprofile) + mommy.make( + "small_small_hr.Leave", + leave_type=Leave.REGULAR, + start=start, + end=end, + review_status=Leave.APPROVED, + staff=staffprofile, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Need a break', + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", } form = ApplyLeaveForm(data=data) self.assertFalse(form.is_valid()) self.assertEqual(2, len(form.errors.keys())) self.assertEqual( - 'you cannot have overlapping leave days', - form.errors['start'][0] + "you cannot have overlapping leave days", form.errors["start"][0] ) self.assertEqual( - 'you cannot have overlapping leave days', - form.errors['end'][0] + "you cannot have overlapping leave days", form.errors["end"][0] ) @override_settings(SSHR_DEFAULT_TIME=7) def test_leaveform_admin(self): - """ - Test LeaveForm apply for leave - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test LeaveForm apply for leave.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, carried_over_days=12) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + carried_over_days=12, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Need a break', - 'status': Leave.APPROVED, - 'comments': 'Okay' + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", + "review_status": Leave.APPROVED, } form = LeaveForm(data=data) @@ -652,44 +620,42 @@ def test_leaveform_admin(self): self.assertEqual(Leave.REGULAR, leave.leave_type) self.assertEqual(start, leave.start) self.assertEqual(end, leave.end) - self.assertEqual( - timedelta(days=5).days, (leave.end - leave.start).days) - self.assertEqual('Need a break', leave.reason) - self.assertEqual(Leave.APPROVED, leave.status) - self.assertEqual('Okay', leave.comments) + self.assertEqual(timedelta(days=5).days, (leave.end - leave.start).days) + self.assertEqual("Need a break", leave.review_reason) + self.assertEqual(Leave.APPROVED, leave.review_status) @override_settings(SSHR_DEFAULT_TIME=7) def test_leaveform_process(self): - """ - Test LeaveForm process - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test LeaveForm process.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, carried_over_days=4) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + carried_over_days=4, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Need a break', - 'comments': 'Just no', - 'status': Leave.REJECTED + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", + "review_status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -699,50 +665,52 @@ def test_leaveform_process(self): self.assertEqual(Leave.REGULAR, leave.leave_type) self.assertEqual(start, leave.start) self.assertEqual(end, leave.end) - self.assertEqual( - timedelta(days=5).days, (leave.end - leave.start).days) - self.assertEqual('Need a break', leave.reason) - self.assertEqual(Leave.REJECTED, leave.status) - self.assertEqual('Just no', leave.comments) + self.assertEqual(timedelta(days=5).days, (leave.end - leave.start).days) + self.assertEqual("Need a break", leave.review_reason) + self.assertEqual(Leave.REJECTED, leave.review_status) @override_settings(SSHR_DEFAULT_TIME=7) def test_leaveform_process_with_overlap(self): - """ - Test LeaveForm process works even if leave object exists - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test LeaveForm process works even if leave object exists.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # make sure leave obj already exists for said dates mommy.make( - 'small_small_hr.Leave', staff=staffprofile, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staffprofile, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, carried_over_days=4) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + carried_over_days=4, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Need a break', - 'comments': 'Already exists', - 'status': Leave.REJECTED + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", + "review_status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -752,42 +720,41 @@ def test_leaveform_process_with_overlap(self): self.assertEqual(Leave.REGULAR, leave.leave_type) self.assertEqual(start, leave.start) self.assertEqual(end, leave.end) - self.assertEqual( - timedelta(days=5).days, (leave.end - leave.start).days) - self.assertEqual('Need a break', leave.reason) - self.assertEqual(Leave.REJECTED, leave.status) - self.assertEqual('Already exists', leave.comments) + self.assertEqual(timedelta(days=5).days, (leave.end - leave.start).days) + self.assertEqual("Need a break", leave.review_reason) + self.assertEqual(Leave.REJECTED, leave.review_status) @override_settings(SSHR_DEFAULT_TIME=7) def test_sickleave_apply(self): - """ - Test LeaveForm apply for sick leave - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test LeaveForm apply for sick leave.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.SICK, carried_over_days=4) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.SICK, + carried_over_days=4, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.SICK, - 'start': start, - 'end': end, - 'reason': 'Need a break', + "staff": staffprofile.id, + "leave_type": Leave.SICK, + "start": start, + "end": end, + "review_reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -797,44 +764,42 @@ def test_sickleave_apply(self): self.assertEqual(Leave.SICK, leave.leave_type) self.assertEqual(start, leave.start) self.assertEqual(end, leave.end) - self.assertEqual( - timedelta(days=5).days, (leave.end - leave.start).days) - self.assertEqual('Need a break', leave.reason) - self.assertEqual(Leave.PENDING, leave.status) - self.assertEqual('', leave.comments) + self.assertEqual(timedelta(days=5).days, (leave.end - leave.start).days) + self.assertEqual("Need a break", leave.review_reason) + self.assertEqual(Leave.PENDING, leave.review_status) @override_settings(SSHR_DEFAULT_TIME=7) def test_sickleave_process(self): - """ - Test LeaveForm process sick leave - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test LeaveForm process sick leave.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.SICK, carried_over_days=4) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.SICK, + carried_over_days=4, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.SICK, - 'start': start, - 'end': end, - 'reason': 'Need a break', - 'comments': 'Just no', - 'status': Leave.REJECTED + "staff": staffprofile.id, + "leave_type": Leave.SICK, + "start": start, + "end": end, + "review_reason": "Need a break", + "review_status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -844,194 +809,187 @@ def test_sickleave_process(self): self.assertEqual(Leave.SICK, leave.leave_type) self.assertEqual(start, leave.start) self.assertEqual(end, leave.end) - self.assertEqual( - timedelta(days=5).days, (leave.end - leave.start).days) - self.assertEqual('Need a break', leave.reason) - self.assertEqual(Leave.REJECTED, leave.status) - self.assertEqual('Just no', leave.comments) + self.assertEqual(timedelta(days=5).days, (leave.end - leave.start).days) + self.assertEqual("Need a break", leave.review_reason) + self.assertEqual(Leave.REJECTED, leave.review_status) def test_leaveform_start_end(self): - """ - Test start and end - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test start and end.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.SICK, carried_over_days=4) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.SICK, + carried_over_days=4, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.SICK, - 'start': start, - 'end': end, - 'reason': 'Need a break' + "staff": staffprofile.id, + "leave_type": Leave.SICK, + "start": start, + "end": end, + "review_reason": "Need a break", } form = LeaveForm(data=data) self.assertFalse(form.is_valid()) self.assertEqual(1, len(form.errors.keys())) - self.assertEqual( - 'end must be greater than start', - form.errors['end'][0] - ) + self.assertEqual("end must be greater than start", form.errors["end"][0]) # end year and start year must be the same - end = datetime( - 2018, 6, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2018, 6, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) data2 = { - 'staff': staffprofile.id, - 'leave_type': Leave.SICK, - 'start': start, - 'end': end, - 'reason': 'Need a break' + "staff": staffprofile.id, + "leave_type": Leave.SICK, + "start": start, + "end": end, + "review_reason": "Need a break", } form2 = LeaveForm(data=data2) self.assertFalse(form2.is_valid()) self.assertEqual(2, len(form2.errors.keys())) self.assertEqual( - 'start and end must be from the same year', - form2.errors['start'][0] + "start and end must be from the same year", form2.errors["start"][0] ) self.assertEqual( - 'start and end must be from the same year', - form2.errors['end'][0] + "start and end must be from the same year", form2.errors["end"][0] ) @override_settings(SSHR_ALLOW_OVERSUBSCRIBE=False) def test_leaveform_max_days(self): - """ - Test leave days sufficient - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test leave days sufficient.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 7, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 7, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.REGULAR, allowed_days=21) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.REGULAR, + allowed_days=21, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.REGULAR, - 'start': start, - 'end': end, - 'reason': 'Need a break' + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", } form = LeaveForm(data=data) self.assertFalse(form.is_valid()) self.assertEqual(2, len(form.errors.keys())) self.assertEqual( - 'Not enough leave days. Available leave days are 21.00', - form.errors['start'][0] + "Not enough leave days. Available leave days are 21.00", + form.errors["start"][0], ) self.assertEqual( - 'Not enough leave days. Available leave days are 21.00', - form.errors['end'][0] + "Not enough leave days. Available leave days are 21.00", + form.errors["end"][0], ) @override_settings(SSHR_ALLOW_OVERSUBSCRIBE=False) def test_leaveform_max_sick_days(self): - """ - Test sick days sufficient - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test sick days sufficient.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() # 6 days of leave - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 20, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 20, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, - leave_type=Leave.SICK, carried_over_days=0, allowed_days=10) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staffprofile, + year=2017, + leave_type=Leave.SICK, + carried_over_days=0, + allowed_days=10, + ) data = { - 'staff': staffprofile.id, - 'leave_type': Leave.SICK, - 'start': start, - 'end': end, - 'reason': 'Need a break' + "staff": staffprofile.id, + "leave_type": Leave.SICK, + "start": start, + "end": end, + "review_reason": "Need a break", } form = LeaveForm(data=data) self.assertFalse(form.is_valid()) self.assertEqual(2, len(form.errors.keys())) self.assertEqual( - 'Not enough sick days. Available sick days are 10.00', - form.errors['start'][0] + "Not enough sick days. Available sick days are 10.00", + form.errors["start"][0], ) self.assertEqual( - 'Not enough sick days. Available sick days are 10.00', - form.errors['end'][0] + "Not enough sick days. Available sick days are 10.00", form.errors["end"][0] ) - @override_settings(PRIVATE_STORAGE_ROOT='/tmp/') + @override_settings(PRIVATE_STORAGE_ROOT="/tmp/") def test_staffdocumentform(self): - """ - Test StaffDocumentForm - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test StaffDocumentForm.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - path = os.path.join( - BASE_DIR, 'tests', 'fixtures', 'contract.pdf') + path = os.path.join(BASE_DIR, "tests", "fixtures", "contract.pdf") - with open(path, 'r+b') as contract_file: + with open(path, "r+b") as contract_file: data = { - 'staff': staffprofile.id, - 'name': 'Employment Contract', - 'description': 'This is the employment contract!', - 'file': contract_file, - 'public': True + "staff": staffprofile.id, + "name": "Employment Contract", + "description": "This is the employment contract!", + "file": contract_file, + "public": True, } file_dict = { - 'file': SimpleUploadedFile( + "file": SimpleUploadedFile( name=contract_file.name, content=contract_file.read(), - content_type='application/pdf' - )} + content_type="application/pdf", + ) + } form = StaffDocumentForm(data, file_dict) @@ -1039,549 +997,504 @@ def test_staffdocumentform(self): doc = form.save() self.assertEqual(staffprofile, doc.staff) - self.assertEqual('Employment Contract', doc.name) + self.assertEqual("Employment Contract", doc.name) self.assertEqual(True, doc.public) - self.assertEqual( - 'This is the employment contract!', doc.description) + self.assertEqual("This is the employment contract!", doc.description) - with open(path, 'r+b') as contract_file: + with open(path, "r+b") as contract_file: self.assertTrue(contract_file.read(), doc.file.read()) # on updating it, check that file is not required data2 = { - 'staff': staffprofile.id, - 'name': 'Employment Contract', - 'description': 'This is the employment contract!' + "staff": staffprofile.id, + "name": "Employment Contract", + "description": "This is the employment contract!", } form2 = StaffDocumentForm(data=data2, instance=doc, request=request) self.assertTrue(form2.is_valid()) - @override_settings(PRIVATE_STORAGE_ROOT='/tmp/') + @override_settings(PRIVATE_STORAGE_ROOT="/tmp/") def test_userstaffdocumentform(self): - """ - Test UserStaffDocumentForm - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test UserStaffDocumentForm.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = user - path = os.path.join( - BASE_DIR, 'tests', 'fixtures', 'contract.pdf') + path = os.path.join(BASE_DIR, "tests", "fixtures", "contract.pdf") - with open(path, 'r+b') as contract_file: + with open(path, "r+b") as contract_file: data = { - 'staff': staffprofile.id, - 'name': 'Employment Contract', - 'description': 'This is the employment contract!', - 'file': contract_file + "staff": staffprofile.id, + "name": "Employment Contract", + "description": "This is the employment contract!", + "file": contract_file, } file_dict = { - 'file': SimpleUploadedFile( + "file": SimpleUploadedFile( name=contract_file.name, content=contract_file.read(), - content_type='application/pdf' - )} + content_type="application/pdf", + ) + } - form = UserStaffDocumentForm( - data=data, files=file_dict, request=request) + form = UserStaffDocumentForm(data=data, files=file_dict, request=request) self.assertTrue(form.is_valid()) doc = form.save() self.assertEqual(staffprofile, doc.staff) - self.assertEqual('Employment Contract', doc.name) + self.assertEqual("Employment Contract", doc.name) self.assertEqual(False, doc.public) - self.assertEqual( - 'This is the employment contract!', doc.description) + self.assertEqual("This is the employment contract!", doc.description) - with open(path, 'r+b') as contract_file: + with open(path, "r+b") as contract_file: self.assertTrue(contract_file.read(), doc.file.read()) # on updating it, check that file is not required data2 = { - 'staff': staffprofile.id, - 'name': 'Employment Contract', - 'description': 'This is the employment contract!' + "staff": staffprofile.id, + "name": "Employment Contract", + "description": "This is the employment contract!", } form2 = StaffDocumentForm(data=data2, instance=doc, request=request) self.assertTrue(form2.is_valid()) def test_staff_profile_user_form(self): - """ - Test StaffProfileUserForm - """ - user = mommy.make('auth.User') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test StaffProfileUserForm.""" + user = mommy.make("auth.User") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - path = os.path.join( - BASE_DIR, 'tests', 'fixtures', 'profile.png') + path = os.path.join(BASE_DIR, "tests", "fixtures", "profile.png") - with open(path, 'r+b') as image_file: + with open(path, "r+b") as image_file: data = { - 'first_name': 'Bob', - 'last_name': 'Mbugua', - 'id_number': '123456789', - 'sex': StaffProfile.MALE, - 'nhif': '111111', - 'nssf': '222222', - 'pin_number': 'A0000000Y', - 'emergency_contact_name': 'Bob Father', - 'emergency_contact_relationship': 'Father', - 'emergency_contact_number': '+254722111111', - 'phone': '+254722111111', - 'address': 'This is the address.', - 'birthday': '1996-01-27', - 'image': image_file, + "first_name": "Bob", + "last_name": "Mbugua", + "id_number": "123456789", + "sex": StaffProfile.MALE, + "nhif": "111111", + "nssf": "222222", + "pin_number": "A0000000Y", + "emergency_contact_name": "Bob Father", + "emergency_contact_relationship": "Father", + "emergency_contact_number": "+254722111111", + "phone": "+254722111111", + "address": "This is the address.", + "birthday": "1996-01-27", + "image": image_file, } file_dict = { - 'image': SimpleUploadedFile( + "image": SimpleUploadedFile( name=image_file.name, content=image_file.read(), - content_type='image/png' - )} + content_type="image/png", + ) + } - form = StaffProfileUserForm(data=data, instance=staffprofile, - request=request, files=file_dict) + form = StaffProfileUserForm( + data=data, instance=staffprofile, request=request, files=file_dict + ) self.assertTrue(form.is_valid()) form.save() user.refresh_from_db() - self.assertEqual('Bob Mbugua', user.staffprofile.get_name()) + self.assertEqual("Bob Mbugua", user.staffprofile.get_name()) self.assertEqual(StaffProfile.MALE, staffprofile.sex) - self.assertEqual('+254722111111', staffprofile.phone.as_e164) - - self.assertEqual('This is the address.', staffprofile.address) - self.assertEqual('1996-01-27', str(staffprofile.birthday)) - - self.assertEqual('123456789', - staffprofile.data['id_number']) - self.assertEqual('111111', - staffprofile.data['nhif']) - self.assertEqual('222222', - staffprofile.data['nssf']) - self.assertEqual('A0000000Y', - staffprofile.data['pin_number']) - self.assertEqual('Bob Father', - staffprofile.data['emergency_contact_name']) + self.assertEqual("+254722111111", staffprofile.phone.as_e164) + + self.assertEqual("This is the address.", staffprofile.address) + self.assertEqual("1996-01-27", str(staffprofile.birthday)) + + self.assertEqual("123456789", staffprofile.data["id_number"]) + self.assertEqual("111111", staffprofile.data["nhif"]) + self.assertEqual("222222", staffprofile.data["nssf"]) + self.assertEqual("A0000000Y", staffprofile.data["pin_number"]) + self.assertEqual("Bob Father", staffprofile.data["emergency_contact_name"]) self.assertEqual( - 'Father', staffprofile.data['emergency_contact_relationship']) - self.assertEqual('+254722111111', - staffprofile.data['emergency_contact_number']) + "Father", staffprofile.data["emergency_contact_relationship"] + ) + self.assertEqual( + "+254722111111", staffprofile.data["emergency_contact_number"] + ) - with open(path, 'r+b') as image_file: + with open(path, "r+b") as image_file: self.assertTrue(image_file.read(), staffprofile.image.read()) def test_staffprofile_user_form_no_image(self): - """ - Test StaffProfileUserForm image not required on update - """ - user = mommy.make('auth.User') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test StaffProfileUserForm image not required on update.""" + user = mommy.make("auth.User") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - path = os.path.join( - BASE_DIR, 'tests', 'fixtures', 'profile.png') + path = os.path.join(BASE_DIR, "tests", "fixtures", "profile.png") - with open(path, 'r+b') as image_file: + with open(path, "r+b") as image_file: data = { - 'first_name': 'Bob', - 'last_name': 'Mbugua', - 'id_number': '123456789', - 'sex': StaffProfile.MALE, - 'nhif': '111111', - 'nssf': '222222', - 'pin_number': 'A0000000Y', - 'emergency_contact_name': 'Bob Father', - 'emergency_contact_relationship': 'Father', - 'emergency_contact_number': '+254722111111', - 'phone': '+254722111111', - 'address': 'This is the address.', - 'birthday': '1996-01-27', - 'image': image_file, + "first_name": "Bob", + "last_name": "Mbugua", + "id_number": "123456789", + "sex": StaffProfile.MALE, + "nhif": "111111", + "nssf": "222222", + "pin_number": "A0000000Y", + "emergency_contact_name": "Bob Father", + "emergency_contact_relationship": "Father", + "emergency_contact_number": "+254722111111", + "phone": "+254722111111", + "address": "This is the address.", + "birthday": "1996-01-27", + "image": image_file, } file_dict = { - 'image': SimpleUploadedFile( + "image": SimpleUploadedFile( name=image_file.name, content=image_file.read(), - content_type='image/png' - )} + content_type="image/png", + ) + } - form = StaffProfileUserForm(data=data, instance=staffprofile, - request=request, files=file_dict) + form = StaffProfileUserForm( + data=data, instance=staffprofile, request=request, files=file_dict + ) self.assertTrue(form.is_valid()) form.save() staffprofile.refresh_from_db() data2 = { - 'first_name': 'Bobbie', - 'last_name': 'B', - 'id_number': 6666, + "first_name": "Bobbie", + "last_name": "B", + "id_number": 6666, } - form2 = StaffProfileUserForm(data=data2, instance=staffprofile, - request=request) + form2 = StaffProfileUserForm(data=data2, instance=staffprofile, request=request) self.assertTrue(form2.is_valid()) form2.save() staffprofile.refresh_from_db() - self.assertEqual('Bobbie B', user.staffprofile.get_name()) + self.assertEqual("Bobbie B", user.staffprofile.get_name()) def test_staff_profile_admin_create_form(self): - """ - Test StaffProfileAdminCreateForm - """ - user = mommy.make('auth.User') + """Test StaffProfileAdminCreateForm.""" + user = mommy.make("auth.User") - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - path = os.path.join( - BASE_DIR, 'tests', 'fixtures', 'profile.png') + path = os.path.join(BASE_DIR, "tests", "fixtures", "profile.png") - with open(path, 'r+b') as image_file: + with open(path, "r+b") as image_file: data = { - 'user': user.id, - 'first_name': 'Bob', - 'last_name': 'Mbugua', - 'id_number': '123456789', - 'sex': StaffProfile.MALE, - 'nhif': '111111', - 'nssf': '222222', - 'pin_number': 'A0000000Y', - 'emergency_contact_name': 'Bob Father', - 'emergency_contact_number': '+254722111111', - 'phone': '+254722111111', - 'address': 'This is the address.', - 'birthday': '1996-01-27', - 'leave_days': 21, - 'sick_days': 9, - 'overtime_allowed': True, - 'start_date': '2017-09-25', - 'end_date': '2018-12-31', - 'image': image_file, + "user": user.id, + "first_name": "Bob", + "last_name": "Mbugua", + "id_number": "123456789", + "sex": StaffProfile.MALE, + "nhif": "111111", + "nssf": "222222", + "pin_number": "A0000000Y", + "emergency_contact_name": "Bob Father", + "emergency_contact_number": "+254722111111", + "phone": "+254722111111", + "address": "This is the address.", + "birthday": "1996-01-27", + "leave_days": 21, + "sick_days": 9, + "overtime_allowed": True, + "start_date": "2017-09-25", + "end_date": "2018-12-31", + "image": image_file, } file_dict = { - 'image': SimpleUploadedFile( + "image": SimpleUploadedFile( name=image_file.name, content=image_file.read(), - content_type='image/png' - )} + content_type="image/png", + ) + } form = StaffProfileAdminCreateForm( - data=data, files=file_dict, request=request) + data=data, files=file_dict, request=request + ) self.assertTrue(form.is_valid()) staffprofile = form.save() user.refresh_from_db() - self.assertEqual('Bob Mbugua', user.staffprofile.get_name()) + self.assertEqual("Bob Mbugua", user.staffprofile.get_name()) self.assertEqual(StaffProfile.MALE, staffprofile.sex) - self.assertEqual('+254722111111', staffprofile.phone.as_e164) + self.assertEqual("+254722111111", staffprofile.phone.as_e164) self.assertEqual(21, staffprofile.leave_days) self.assertEqual(9, staffprofile.sick_days) self.assertEqual(True, staffprofile.overtime_allowed) - self.assertEqual('This is the address.', staffprofile.address) - self.assertEqual('1996-01-27', str(staffprofile.birthday)) - self.assertEqual('2017-09-25', str(staffprofile.start_date)) - self.assertEqual('2018-12-31', str(staffprofile.end_date)) - - self.assertEqual('123456789', - staffprofile.data['id_number']) - self.assertEqual('111111', - staffprofile.data['nhif']) - self.assertEqual('222222', - staffprofile.data['nssf']) - self.assertEqual('A0000000Y', - staffprofile.data['pin_number']) - self.assertEqual('Bob Father', - staffprofile.data['emergency_contact_name']) - self.assertEqual('+254722111111', - staffprofile.data['emergency_contact_number']) - - with open(path, 'r+b') as image_file: + self.assertEqual("This is the address.", staffprofile.address) + self.assertEqual("1996-01-27", str(staffprofile.birthday)) + self.assertEqual("2017-09-25", str(staffprofile.start_date)) + self.assertEqual("2018-12-31", str(staffprofile.end_date)) + + self.assertEqual("123456789", staffprofile.data["id_number"]) + self.assertEqual("111111", staffprofile.data["nhif"]) + self.assertEqual("222222", staffprofile.data["nssf"]) + self.assertEqual("A0000000Y", staffprofile.data["pin_number"]) + self.assertEqual("Bob Father", staffprofile.data["emergency_contact_name"]) + self.assertEqual( + "+254722111111", staffprofile.data["emergency_contact_number"] + ) + + with open(path, "r+b") as image_file: self.assertTrue(image_file.read(), staffprofile.image.read()) def test_staff_profile_admin_form(self): - """ - Test StaffProfileAdminForm - """ - user = mommy.make('auth.User') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test StaffProfileAdminForm.""" + user = mommy.make("auth.User") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - path = os.path.join( - BASE_DIR, 'tests', 'fixtures', 'profile.png') + path = os.path.join(BASE_DIR, "tests", "fixtures", "profile.png") - with open(path, 'r+b') as image_file: + with open(path, "r+b") as image_file: data = { - 'user': user.id, - 'first_name': 'Bob', - 'last_name': 'Mbugua', - 'id_number': '123456789', - 'sex': StaffProfile.MALE, - 'nhif': '111111', - 'nssf': '222222', - 'pin_number': 'A0000000Y', - 'emergency_contact_name': 'Bob Father', - 'emergency_contact_number': '+254722111111', - 'phone': '+254722111111', - 'address': 'This is the address.', - 'birthday': '1996-01-27', - 'leave_days': 21, - 'sick_days': 9, - 'overtime_allowed': True, - 'start_date': '2017-09-25', - 'end_date': '2018-12-31', - 'image': image_file, + "user": user.id, + "first_name": "Bob", + "last_name": "Mbugua", + "id_number": "123456789", + "sex": StaffProfile.MALE, + "nhif": "111111", + "nssf": "222222", + "pin_number": "A0000000Y", + "emergency_contact_name": "Bob Father", + "emergency_contact_number": "+254722111111", + "phone": "+254722111111", + "address": "This is the address.", + "birthday": "1996-01-27", + "leave_days": 21, + "sick_days": 9, + "overtime_allowed": True, + "start_date": "2017-09-25", + "end_date": "2018-12-31", + "image": image_file, } file_dict = { - 'image': SimpleUploadedFile( + "image": SimpleUploadedFile( name=image_file.name, content=image_file.read(), - content_type='image/png' - )} + content_type="image/png", + ) + } - form = StaffProfileAdminForm(data=data, instance=staffprofile, - request=request, files=file_dict) + form = StaffProfileAdminForm( + data=data, instance=staffprofile, request=request, files=file_dict + ) self.assertTrue(form.is_valid()) form.save() user.refresh_from_db() - self.assertEqual('Bob Mbugua', user.staffprofile.get_name()) + self.assertEqual("Bob Mbugua", user.staffprofile.get_name()) self.assertEqual(StaffProfile.MALE, staffprofile.sex) - self.assertEqual('+254722111111', staffprofile.phone.as_e164) + self.assertEqual("+254722111111", staffprofile.phone.as_e164) self.assertEqual(21, staffprofile.leave_days) self.assertEqual(9, staffprofile.sick_days) self.assertEqual(True, staffprofile.overtime_allowed) - self.assertEqual('This is the address.', staffprofile.address) - self.assertEqual('1996-01-27', str(staffprofile.birthday)) - self.assertEqual('2017-09-25', str(staffprofile.start_date)) - self.assertEqual('2018-12-31', str(staffprofile.end_date)) - - self.assertEqual('123456789', - staffprofile.data['id_number']) - self.assertEqual('111111', - staffprofile.data['nhif']) - self.assertEqual('222222', - staffprofile.data['nssf']) - self.assertEqual('A0000000Y', - staffprofile.data['pin_number']) - self.assertEqual('Bob Father', - staffprofile.data['emergency_contact_name']) - self.assertEqual('+254722111111', - staffprofile.data['emergency_contact_number']) - - with open(path, 'r+b') as image_file: + self.assertEqual("This is the address.", staffprofile.address) + self.assertEqual("1996-01-27", str(staffprofile.birthday)) + self.assertEqual("2017-09-25", str(staffprofile.start_date)) + self.assertEqual("2018-12-31", str(staffprofile.end_date)) + + self.assertEqual("123456789", staffprofile.data["id_number"]) + self.assertEqual("111111", staffprofile.data["nhif"]) + self.assertEqual("222222", staffprofile.data["nssf"]) + self.assertEqual("A0000000Y", staffprofile.data["pin_number"]) + self.assertEqual("Bob Father", staffprofile.data["emergency_contact_name"]) + self.assertEqual( + "+254722111111", staffprofile.data["emergency_contact_number"] + ) + + with open(path, "r+b") as image_file: self.assertTrue(image_file.read(), staffprofile.image.read()) def test_staffprofile_admin_form_no_image(self): - """ - Test StaffProfileAdminForm image not required when editting - """ - user = mommy.make('auth.User') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + """Test StaffProfileAdminForm image not required when editting.""" + user = mommy.make("auth.User") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() - path = os.path.join( - BASE_DIR, 'tests', 'fixtures', 'profile.png') + path = os.path.join(BASE_DIR, "tests", "fixtures", "profile.png") - with open(path, 'r+b') as image_file: + with open(path, "r+b") as image_file: data = { - 'user': user.id, - 'first_name': 'Bob', - 'last_name': 'Mbugua', - 'id_number': '123456789', - 'sex': StaffProfile.MALE, - 'nhif': '111111', - 'nssf': '222222', - 'pin_number': 'A0000000Y', - 'emergency_contact_name': 'Bob Father', - 'emergency_contact_number': '+254722111111', - 'phone': '+254722111111', - 'address': 'This is the address.', - 'birthday': '1996-01-27', - 'leave_days': 21, - 'sick_days': 9, - 'overtime_allowed': True, - 'start_date': '2017-09-25', - 'end_date': '2018-12-31', - 'image': image_file, + "user": user.id, + "first_name": "Bob", + "last_name": "Mbugua", + "id_number": "123456789", + "sex": StaffProfile.MALE, + "nhif": "111111", + "nssf": "222222", + "pin_number": "A0000000Y", + "emergency_contact_name": "Bob Father", + "emergency_contact_number": "+254722111111", + "phone": "+254722111111", + "address": "This is the address.", + "birthday": "1996-01-27", + "leave_days": 21, + "sick_days": 9, + "overtime_allowed": True, + "start_date": "2017-09-25", + "end_date": "2018-12-31", + "image": image_file, } file_dict = { - 'image': SimpleUploadedFile( + "image": SimpleUploadedFile( name=image_file.name, content=image_file.read(), - content_type='image/png' - )} + content_type="image/png", + ) + } - form = StaffProfileAdminForm(data=data, instance=staffprofile, - request=request, files=file_dict) + form = StaffProfileAdminForm( + data=data, instance=staffprofile, request=request, files=file_dict + ) self.assertTrue(form.is_valid()) form.save() staffprofile.refresh_from_db() data2 = { - 'user': user.id, - 'first_name': 'Bobbie', - 'last_name': 'B', - 'id_number': 6666, + "user": user.id, + "first_name": "Bobbie", + "last_name": "B", + "id_number": 6666, } - form2 = StaffProfileAdminForm(data=data2, instance=staffprofile, - request=request) + form2 = StaffProfileAdminForm( + data=data2, instance=staffprofile, request=request + ) self.assertTrue(form2.is_valid()) form2.save() staffprofile.refresh_from_db() - self.assertEqual('Bobbie B', user.staffprofile.get_name()) + self.assertEqual("Bobbie B", user.staffprofile.get_name()) def test_staffprofile_unique_pin_number(self): - """ - Test unique pin_number - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) - staffprofile.data['id_number'] = '123456789' - staffprofile.data['pin_number'] = '123456789' + """Test unique pin_number.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) + staffprofile.data["id_number"] = "123456789" + staffprofile.data["pin_number"] = "123456789" staffprofile.save() - user2 = mommy.make('auth.User', first_name='Kyle', last_name='Ndoe') - staffprofile2 = mommy.make('small_small_hr.StaffProfile', user=user2) - staffprofile2.data['id_number'] = '9999999' + user2 = mommy.make("auth.User", first_name="Kyle", last_name="Ndoe") + staffprofile2 = mommy.make("small_small_hr.StaffProfile", user=user2) + staffprofile2.data["id_number"] = "9999999" staffprofile2.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() data = StaffProfileSerializer(staffprofile2).data - data['pin_number'] = '123456789' + data["pin_number"] = "123456789" - form = StaffProfileAdminForm(data=data, - instance=staffprofile2, - request=request) + form = StaffProfileAdminForm(data=data, instance=staffprofile2, request=request) self.assertFalse(form.is_valid()) self.assertEqual(1, len(form.errors.keys())) self.assertEqual( - 'This PIN number is already in use.', - form.errors['pin_number'][0] + "This PIN number is already in use.", form.errors["pin_number"][0] ) def test_staffprofile_unique_id_number(self): - """ - Test unique id_number - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) - staffprofile.data['id_number'] = '123456789' + """Test unique id_number.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) + staffprofile.data["id_number"] = "123456789" staffprofile.save() - user2 = mommy.make('auth.User', first_name='Kyle', last_name='Ndoe') - staffprofile2 = mommy.make('small_small_hr.StaffProfile', user=user2) + user2 = mommy.make("auth.User", first_name="Kyle", last_name="Ndoe") + staffprofile2 = mommy.make("small_small_hr.StaffProfile", user=user2) staffprofile2.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() data = StaffProfileSerializer(staffprofile2).data - data['id_number'] = '123456789' + data["id_number"] = "123456789" - form = StaffProfileAdminForm(data=data, - instance=staffprofile2, - request=request) + form = StaffProfileAdminForm(data=data, instance=staffprofile2, request=request) self.assertFalse(form.is_valid()) self.assertEqual(1, len(form.errors.keys())) self.assertEqual( - 'This id number is already in use.', - form.errors['id_number'][0] + "This id number is already in use.", form.errors["id_number"][0] ) def test_staffprofile_unique_nssf(self): - """ - Test unique NSSF - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) - staffprofile.data['id_number'] = '123456789' - staffprofile.data['nssf'] = '123456789' + """Test unique NSSF.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) + staffprofile.data["id_number"] = "123456789" + staffprofile.data["nssf"] = "123456789" staffprofile.save() - user2 = mommy.make('auth.User', first_name='Kyle', last_name='Ndoe') - staffprofile2 = mommy.make('small_small_hr.StaffProfile', user=user2) - staffprofile2.data['id_number'] = '9999999' + user2 = mommy.make("auth.User", first_name="Kyle", last_name="Ndoe") + staffprofile2 = mommy.make("small_small_hr.StaffProfile", user=user2) + staffprofile2.data["id_number"] = "9999999" staffprofile2.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() data = StaffProfileSerializer(staffprofile2).data - data['nssf'] = '123456789' + data["nssf"] = "123456789" - form = StaffProfileAdminForm(data=data, - instance=staffprofile2, - request=request) + form = StaffProfileAdminForm(data=data, instance=staffprofile2, request=request) self.assertFalse(form.is_valid()) self.assertEqual(1, len(form.errors.keys())) - self.assertEqual( - 'This NSSF number is already in use.', - form.errors['nssf'][0] - ) + self.assertEqual("This NSSF number is already in use.", form.errors["nssf"][0]) def test_staffprofile_unique_nhif(self): - """ - Test unique NHIF - """ - user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) - staffprofile.data['id_number'] = '123456789' - staffprofile.data['nhif'] = '123456789' + """Test unique NHIF.""" + user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) + staffprofile.data["id_number"] = "123456789" + staffprofile.data["nhif"] = "123456789" staffprofile.save() - user2 = mommy.make('auth.User', first_name='Kyle', last_name='Ndoe') - staffprofile2 = mommy.make('small_small_hr.StaffProfile', user=user2) - staffprofile2.data['id_number'] = '9999999' + user2 = mommy.make("auth.User", first_name="Kyle", last_name="Ndoe") + staffprofile2 = mommy.make("small_small_hr.StaffProfile", user=user2) + staffprofile2.data["id_number"] = "9999999" staffprofile2.save() - request = self.factory.get('/') + request = self.factory.get("/") request.session = {} request.user = AnonymousUser() data = StaffProfileSerializer(staffprofile2).data - data['nhif'] = '123456789' + data["nhif"] = "123456789" - form = StaffProfileAdminForm(data=data, - instance=staffprofile2, - request=request) + form = StaffProfileAdminForm(data=data, instance=staffprofile2, request=request) self.assertFalse(form.is_valid()) self.assertEqual(1, len(form.errors.keys())) - self.assertEqual( - 'This NHIF number is already in use.', - form.errors['nhif'][0] - ) + self.assertEqual("This NHIF number is already in use.", form.errors["nhif"][0]) diff --git a/tests/test_models.py b/tests/test_models.py index 5df4fd1..bc0b04f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,5 @@ -""" -Module to test small_small_hr models -""" -from datetime import datetime, timedelta, date +"""Module to test small_small_hr models.""" +from datetime import date, datetime, timedelta from django.conf import settings from django.test import TestCase, override_settings @@ -10,39 +8,37 @@ import pytz from model_mommy import mommy -from small_small_hr.models import Leave, get_taken_leave_days, FreeDay +from small_small_hr.models import FreeDay, Leave, get_taken_leave_days from small_small_hr.utils import create_free_days class TestModels(TestCase): - """ - Test class for models - """ + """Test class for models.""" def test_annualleave_str(self): - """ - Test the __str__ method on AnnualLeave - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test the __str__ method on AnnualLeave.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) self.assertEqual( - '2018: Mosh Pitt Regular Leave', + "2018: Mosh Pitt Regular Leave", mommy.make( - 'small_small_hr.AnnualLeave', staff=staff, year=2018, - leave_type=Leave.REGULAR).__str__() + "small_small_hr.AnnualLeave", + staff=staff, + year=2018, + leave_type=Leave.REGULAR, + ).__str__(), ) def test_freedays_str(self): - """ - Test the __str__ method on FreeDay - """ + """Test the __str__ method on FreeDay.""" the_date = date(day=27, month=1, year=2017) self.assertEqual( - '2017 - Friday 27 January 2017', + "2017 - Friday 27 January 2017", mommy.make( - 'small_small_hr.FreeDay', - name=the_date.strftime("%A %d %B %Y"), date=the_date, - ).__str__() + "small_small_hr.FreeDay", + name=the_date.strftime("%A %d %B %Y"), + date=the_date, + ).__str__(), ) @override_settings( @@ -57,44 +53,52 @@ def test_freedays_str(self): } ) def test_annualleave_get_available_leave_days(self): - """ - Test get_available_leave_days - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test get_available_leave_days.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) annual_leave = mommy.make( - 'small_small_hr.AnnualLeave', staff=staff, year=2017, - leave_type=Leave.REGULAR) + "small_small_hr.AnnualLeave", + staff=staff, + year=2017, + leave_type=Leave.REGULAR, + ) # 12 days of leave ==> Sat and Sun are counted - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # add some approved leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) # get some rejected and pending leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.REJECTED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.REJECTED, + ) mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.PENDING) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.PENDING, + ) annual_leave.refresh_from_db() # we should ave 21 - 12 leave days remaining - self.assertEqual( - 21 - 12, - annual_leave.get_available_leave_days() - ) + self.assertEqual(21 - 12, annual_leave.get_available_leave_days()) @override_settings( SSHR_DAY_LEAVE_VALUES={ @@ -107,114 +111,125 @@ def test_annualleave_get_available_leave_days(self): 7: 0, # Sunday }, SSHR_FREE_DAYS=[ - {'day': 8, 'month': 6}, # MOSH DAY - {'day': 6, 'month': 1}, # PITT DAY - ] # these are days that are not counted when getting taken leave days + {"day": 8, "month": 6}, # MOSH DAY + {"day": 6, "month": 1}, # PITT DAY + ], # these are days that are not counted when getting taken leave days ) def test_annualleave_cumulative_leave_taken(self): - """ - Test get_cumulative_leave_taken - """ + """Test get_cumulative_leave_taken.""" FreeDay.objects.all().delete() create_free_days(start_year=2017, number_of_years=24) - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) annual_leave = mommy.make( - 'small_small_hr.AnnualLeave', staff=staff, year=2017, - leave_type=Leave.REGULAR) + "small_small_hr.AnnualLeave", + staff=staff, + year=2017, + leave_type=Leave.REGULAR, + ) # note that 8/6/2017 is a FreeDay - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 8, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 8, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # add some 3 approved leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) # get some rejected and pending leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.REJECTED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.REJECTED, + ) mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.PENDING) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.PENDING, + ) annual_leave.refresh_from_db() - self.assertEqual( - 3, - annual_leave.get_cumulative_leave_taken() - ) + self.assertEqual(3, annual_leave.get_cumulative_leave_taken()) # add some 2 approved leave days that fall between years # 1 day in 2017 and one in 2018 # Dec 30 and Dec 31 are Sat, Sun which are not counted start = datetime( - 2017, 12, 29, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2018, 1, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + 2017, 12, 29, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE) + ) + end = datetime(2018, 1, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) annual_leave.refresh_from_db() # we should have 4 days in 2017 - self.assertEqual( - 4, - annual_leave.get_cumulative_leave_taken() - ) + self.assertEqual(4, annual_leave.get_cumulative_leave_taken()) # add 4 days of leave // 6/1/2018 is not counted - start = datetime( - 2018, 1, 2, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2018, 1, 6, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2018, 1, 2, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2018, 1, 6, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) # we should have 5 days in 2018 annual_leave_2018 = mommy.make( - 'small_small_hr.AnnualLeave', staff=staff, year=2018, - leave_type=Leave.REGULAR) - self.assertEqual( - 5, - annual_leave_2018.get_cumulative_leave_taken() + "small_small_hr.AnnualLeave", + staff=staff, + year=2018, + leave_type=Leave.REGULAR, ) + self.assertEqual(5, annual_leave_2018.get_cumulative_leave_taken()) def test_available_leave_days(self): - """ - Test available leave days at various times of the year - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test available leave days at various times of the year.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) annual_leave = mommy.make( - 'small_small_hr.AnnualLeave', staff=staff, year=2017, - leave_type=Leave.REGULAR, allowed_days=21, carried_over_days=0) + "small_small_hr.AnnualLeave", + staff=staff, + year=2017, + leave_type=Leave.REGULAR, + allowed_days=21, + carried_over_days=0, + ) months = range(1, 13) for month in months: self.assertEqual( - month * 1.75, - annual_leave.get_available_leave_days(month=month) + month * 1.75, annual_leave.get_available_leave_days(month=month) ) def test_staffprofile_str(self): - """ - Test that the __str__ method on StaffProfile works - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) - self.assertEqual('Mosh Pitt', staff.__str__()) + """Test that the __str__ method on StaffProfile works.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) + self.assertEqual("Mosh Pitt", staff.__str__()) @override_settings( SSHR_DAY_LEAVE_VALUES={ @@ -228,25 +243,30 @@ def test_staffprofile_str(self): } ) def test_get_approved_leave_days(self): - """ - Test get_approved_leave_days - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) - start = datetime( - 2018, 1, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + """Test get_approved_leave_days.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) + start = datetime(2018, 1, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=start + timedelta(days=6), leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=start + timedelta(days=6), + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) mommy.make( - 'small_small_hr.Leave', staff=staff, + "small_small_hr.Leave", + staff=staff, start=start + timedelta(days=10), - end=start + timedelta(days=14), leave_type=Leave.REGULAR, - status=Leave.APPROVED) - self.assertEqual(timedelta(days=12).days, - staff.get_approved_leave_days(year=start.year)) + end=start + timedelta(days=14), + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) + self.assertEqual( + timedelta(days=12).days, staff.get_approved_leave_days(year=start.year) + ) @override_settings( SSHR_DAY_LEAVE_VALUES={ @@ -260,25 +280,30 @@ def test_get_approved_leave_days(self): } ) def test_get_approved_sick_days(self): - """ - Test get_approved_sick_days - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) - start = datetime( - 2018, 1, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + """Test get_approved_sick_days.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) + start = datetime(2018, 1, 1, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=start + timedelta(days=4), leave_type=Leave.SICK, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=start + timedelta(days=4), + leave_type=Leave.SICK, + review_status=Leave.APPROVED, + ) mommy.make( - 'small_small_hr.Leave', staff=staff, + "small_small_hr.Leave", + staff=staff, start=start + timedelta(days=10), end=start + timedelta(days=15), - leave_type=Leave.SICK, status=Leave.APPROVED) - self.assertEqual(timedelta(days=9).days, - staff.get_approved_sick_days(year=start.year)) + leave_type=Leave.SICK, + review_status=Leave.APPROVED, + ) + self.assertEqual( + timedelta(days=9).days, staff.get_approved_sick_days(year=start.year) + ) @override_settings( SSHR_DAY_LEAVE_VALUES={ @@ -292,25 +317,30 @@ def test_get_approved_sick_days(self): } ) def test_staffprofile_get_available_leave_days(self): - """ - Test StaffProfile get_available_leave_days - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) - mommy.make('small_small_hr.AnnualLeave', staff=staff, year=2017, - leave_type=Leave.REGULAR, allowed_days=21) + """Test StaffProfile get_available_leave_days.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staff, + year=2017, + leave_type=Leave.REGULAR, + allowed_days=21, + ) # 10 days of leave because Saturday and Sunday are not counted - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # add some approved leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) staff.refresh_from_db() @@ -329,25 +359,30 @@ def test_staffprofile_get_available_leave_days(self): } ) def test_one_leave_day(self): - """ - Test one leave day - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) - mommy.make('small_small_hr.AnnualLeave', staff=staff, year=2017, - leave_type=Leave.REGULAR, allowed_days=21) + """Test one leave day.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staff, + year=2017, + leave_type=Leave.REGULAR, + allowed_days=21, + ) # ONE DAY of leave because Saturday and Sunday are not counted - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # add some approved leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) staff.refresh_from_db() @@ -367,97 +402,101 @@ def test_one_leave_day(self): } ) def test_staffprofile_get_available_sick_days(self): - """ - Test StaffProfile get_available_leave_days - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) - mommy.make('small_small_hr.AnnualLeave', staff=staff, year=2017, - leave_type=Leave.SICK, allowed_days=10) + """Test StaffProfile get_available_leave_days.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) + mommy.make( + "small_small_hr.AnnualLeave", + staff=staff, + year=2017, + leave_type=Leave.SICK, + allowed_days=10, + ) # 6 days of leave - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # add some approved leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.SICK, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.SICK, + review_status=Leave.APPROVED, + ) staff.refresh_from_db() # remaining should be 10 - 6 - self.assertEqual( - 10 - 6, - int(staff.get_available_sick_days(year=2017))) + self.assertEqual(10 - 6, int(staff.get_available_sick_days(year=2017))) def test_role_str(self): - """ - Test __str__ method on Role - """ + """Test __str__ method on Role.""" self.assertEqual( - 'Accountant', - mommy.make('small_small_hr.Role', name='Accountant').__str__() + "Accountant", mommy.make("small_small_hr.Role", name="Accountant").__str__() ) def test_staffdocument_str(self): - """ - Test __str__ method on StaffDocument - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test __str__ method on StaffDocument.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) self.assertEqual( - 'Mosh Pitt - Dossier', + "Mosh Pitt - Dossier", mommy.make( - 'small_small_hr.StaffDocument', - name='Dossier', staff=staff).__str__()) + "small_small_hr.StaffDocument", name="Dossier", staff=staff + ).__str__(), + ) def test_leave_str(self): - """ - Test __str__ method on Leave - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test __str__ method on Leave.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) now = timezone.now() end = now + timedelta(days=3) self.assertEqual( - f'Mosh Pitt: {now} to {end}', + f"Mosh Pitt: {now} to {end}", mommy.make( - 'small_small_hr.Leave', start=now, end=end, - staff=staff).__str__()) + "small_small_hr.Leave", start=now, end=end, staff=staff + ).__str__(), + ) def test_overtime_str(self): - """ - Test __str__ method on OverTime - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test __str__ method on OverTime.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) now = timezone.now() end = now + timedelta(seconds=60 * 60 * 3) self.assertEqual( - f'Mosh Pitt: {now.date()} from {now.time()} to {end.time()}', + f"Mosh Pitt: {now.date()} from {now.time()} to {end.time()}", mommy.make( - 'small_small_hr.OverTime', date=now.date(), - start=now.time(), end=end.time(), - staff=staff).__str__()) + "small_small_hr.OverTime", + date=now.date(), + start=now.time(), + end=end.time(), + staff=staff, + ).__str__(), + ) def test_overtime_duration(self): - """ - Test get_duration method on OverTime - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test get_duration method on OverTime.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) now = timezone.now() end = now + timedelta(seconds=60 * 60 * 3) self.assertEqual( timedelta(seconds=60 * 60 * 3).seconds, mommy.make( - 'small_small_hr.OverTime', date=now.date(), - start=now.time(), end=end.time(), - staff=staff).get_duration().seconds) + "small_small_hr.OverTime", + date=now.date(), + start=now.time(), + end=end.time(), + staff=staff, + ) + .get_duration() + .seconds, + ) @override_settings( SSHR_DAY_LEAVE_VALUES={ @@ -471,30 +510,31 @@ def test_overtime_duration(self): } ) def test_get_taken_leave_days(self): - """ - Test get_taken_leave_days - """ - user = mommy.make('auth.User', first_name='Mosh', last_name='Pitt') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test get_taken_leave_days.""" + user = mommy.make("auth.User", first_name="Mosh", last_name="Pitt") + staff = mommy.make("small_small_hr.StaffProfile", user=user) # 10 days of leave because Saturday and Sunday are not counted - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # add some approved leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) leave_days = get_taken_leave_days( staffprofile=staff, status=Leave.APPROVED, leave_type=Leave.REGULAR, start_year=2017, - end_year=2017) + end_year=2017, + ) self.assertEqual(10, leave_days) @@ -510,29 +550,30 @@ def test_get_taken_leave_days(self): } ) def test_get_taken_leave_days_half_saturday(self): - """ - Test get_taken_leave_days - """ - user = mommy.make('auth.User', first_name='Kel', last_name='Vin') - staff = mommy.make('small_small_hr.StaffProfile', user=user) + """Test get_taken_leave_days.""" + user = mommy.make("auth.User", first_name="Kel", last_name="Vin") + staff = mommy.make("small_small_hr.StaffProfile", user=user) # 10.5 days of leave because Saturday==0.5 and Sunday not counted - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) # add some approved leave days mommy.make( - 'small_small_hr.Leave', staff=staff, start=start, - end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED) + "small_small_hr.Leave", + staff=staff, + start=start, + end=end, + leave_type=Leave.REGULAR, + review_status=Leave.APPROVED, + ) leave_days = get_taken_leave_days( staffprofile=staff, status=Leave.APPROVED, leave_type=Leave.REGULAR, start_year=2017, - end_year=2017) + end_year=2017, + ) self.assertEqual(10.5, leave_days) diff --git a/tests/test_process.py b/tests/test_process.py new file mode 100644 index 0000000..5aa32ae --- /dev/null +++ b/tests/test_process.py @@ -0,0 +1,293 @@ +"""Test the leave/overtime application process.""" +from datetime import datetime +from unittest.mock import call, patch + +from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.test import RequestFactory, TestCase + +import pytz +from model_mommy import mommy +from model_mommy.recipe import Recipe +from model_reviews.forms import PerformReview +from model_reviews.models import ModelReview, Reviewer + +from small_small_hr.forms import ApplyLeaveForm, ApplyOverTimeForm +from small_small_hr.models import Leave, OverTime, StaffProfile + + +class TestProcess(TestCase): # pylint: disable=too-many-public-methods + """Test the leave/overtime application process.""" + + def setUp(self): + """Set up test class.""" + self.factory = RequestFactory() + StaffProfile.objects.rebuild() + hr_group = mommy.make("auth.Group", name=settings.SSHR_ADMIN_USER_GROUP_NAME) + self.boss = mommy.make( + "auth.User", first_name="Boss", last_name="Lady", email="boss@example.com" + ) + self.boss.groups.add(hr_group) + + self.manager = mommy.make( + "auth.User", first_name="Jane", last_name="Ndoe", email="jane@example.com" + ) + self.user = mommy.make( + "auth.User", first_name="Bob", last_name="Ndoe", email="bob@example.com" + ) + + manager_mommy = Recipe(StaffProfile, lft=None, rght=None, user=self.manager) + + staff_mommy = Recipe(StaffProfile, lft=None, rght=None, user=self.user) + + self.manager_profile = manager_mommy.make() + + self.staffprofile = staff_mommy.make() + + @patch("small_small_hr.emails.send_email") + def test_leave_review_process(self, mock): # pylint: disable=too-many-locals + """Test the Leave review process.""" + manager = self.manager + manager_profile = self.manager_profile + + staffprofile = self.staffprofile + staffprofile.supervisor = manager_profile + staffprofile.leave_days = 21 + staffprofile.sick_days = 10 + staffprofile.save() + + # apply for leave + start = datetime(2017, 6, 5, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 10, 7, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + data = { + "staff": staffprofile.id, + "leave_type": Leave.REGULAR, + "start": start, + "end": end, + "review_reason": "Need a break", + } + form = ApplyLeaveForm(data=data) + self.assertTrue(form.is_valid()) + leave = form.save() + + # check that a ModelReview object is created + obj_type = ContentType.objects.get_for_model(leave) + self.assertEqual( + 1, + ModelReview.objects.filter( + content_type=obj_type, object_id=leave.id + ).count(), + ) + review = ModelReview.objects.get(content_type=obj_type, object_id=leave.id) + self.assertTrue(Leave.PENDING, review.review_status) + + # check that email is sent once to the reviewer + reviewer = Reviewer.objects.get(review=review, user=manager) + boss_reviewer = Reviewer.objects.get(review=review, user=self.boss) + + expected_calls = [ + call( + name="Jane Ndoe", + email="jane@example.com", + subject="New Request For Approval", + message="There has been a new request that needs your attention.", + obj=review, + cc_list=None, + template="leave_application", + template_path="small_small_hr/email", + ), + call( + name="Boss Lady", + email="boss@example.com", + subject="New Request For Approval", + message="There has been a new request that needs your attention.", + obj=review, + cc_list=None, + template="leave_application", + template_path="small_small_hr/email", + ), + ] + + mock.assert_has_calls(expected_calls) + + # approve the review + data = { + "review": review.pk, + "reviewer": reviewer.pk, + "review_status": ModelReview.APPROVED, + } + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() + review.refresh_from_db() + leave.refresh_from_db() + self.assertEqual(ModelReview.APPROVED, review.review_status) + self.assertEqual(Leave.APPROVED, leave.review_status) + + expected_calls.append( + call( + name="Bob Ndoe", + email="bob@example.com", + subject="Your request has been processed", + message="Your request has been processed, please log in to view the status.", # noqa # pylint: disable=line-too-long + obj=review, + cc_list=None, + template="leave_completed", + template_path="small_small_hr/email", + ) + ) + mock.assert_has_calls(expected_calls) + + # boss rejects it + data = { + "review": review.pk, + "reviewer": boss_reviewer.pk, + "review_status": ModelReview.REJECTED, + } + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() + review.refresh_from_db() + leave.refresh_from_db() + self.assertEqual(ModelReview.REJECTED, review.review_status) + self.assertEqual(Leave.REJECTED, leave.review_status) + + expected_calls.append( + call( + name="Bob Ndoe", + email="bob@example.com", + subject="Your request has been processed", + message="Your request has been processed, please log in to view the status.", # noqa # pylint: disable=line-too-long + obj=review, + cc_list=None, + template="leave_completed", + template_path="small_small_hr/email", + ) + ) + mock.assert_has_calls(expected_calls) + + self.assertEqual(4, mock.call_count) + + @patch("small_small_hr.emails.send_email") + def test_overtime_review_process(self, mock): # pylint: disable=too-many-locals + """Test the OverTime review process.""" + manager = self.manager + manager_profile = self.manager_profile + + staffprofile = self.staffprofile + staffprofile.supervisor = manager_profile + staffprofile.save() + + # apply for overtime + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + data = { + "staff": staffprofile.id, + "date": start.date(), + "start": start.time(), + "end": end.time(), + "review_reason": "Extra work", + } + + form = ApplyOverTimeForm(data=data) + self.assertTrue(form.is_valid()) + overtime = form.save() + + # check that a ModelReview object is created + obj_type = ContentType.objects.get_for_model(overtime) + self.assertEqual( + 1, + ModelReview.objects.filter( + content_type=obj_type, object_id=overtime.id + ).count(), + ) + review = ModelReview.objects.get(content_type=obj_type, object_id=overtime.id) + self.assertTrue(Leave.PENDING, review.review_status) + + # check that email is sent once to the reviewer + reviewer = Reviewer.objects.get(review=review, user=manager) + boss_reviewer = Reviewer.objects.get(review=review, user=self.boss) + + expected_calls = [ + call( + name="Jane Ndoe", + email="jane@example.com", + subject="New Request For Approval", + message="There has been a new request that needs your attention.", + obj=review, + cc_list=None, + template="overtime_application", + template_path="small_small_hr/email", + ), + call( + name="Boss Lady", + email="boss@example.com", + subject="New Request For Approval", + message="There has been a new request that needs your attention.", + obj=review, + cc_list=None, + template="overtime_application", + template_path="small_small_hr/email", + ), + ] + + mock.assert_has_calls(expected_calls) + + # approve the review + data = { + "review": review.pk, + "reviewer": reviewer.pk, + "review_status": ModelReview.APPROVED, + } + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() + review.refresh_from_db() + overtime.refresh_from_db() + self.assertEqual(ModelReview.APPROVED, review.review_status) + self.assertEqual(OverTime.APPROVED, overtime.review_status) + + expected_calls.append( + call( + name="Bob Ndoe", + email="bob@example.com", + subject="Your request has been processed", + message="Your request has been processed, please log in to view the status.", # noqa # pylint: disable=line-too-long + obj=review, + cc_list=None, + template="overtime_completed", + template_path="small_small_hr/email", + ) + ) + mock.assert_has_calls(expected_calls) + + # boss rejects it + data = { + "review": review.pk, + "reviewer": boss_reviewer.pk, + "review_status": ModelReview.REJECTED, + } + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() + review.refresh_from_db() + overtime.refresh_from_db() + self.assertEqual(ModelReview.REJECTED, review.review_status) + self.assertEqual(OverTime.REJECTED, overtime.review_status) + + expected_calls.append( + call( + name="Bob Ndoe", + email="bob@example.com", + subject="Your request has been processed", + message="Your request has been processed, please log in to view the status.", # noqa # pylint: disable=line-too-long + obj=review, + cc_list=None, + template="overtime_completed", + template_path="small_small_hr/email", + ) + ) + mock.assert_has_calls(expected_calls) + + self.assertEqual(4, mock.call_count) diff --git a/tests/test_utils.py b/tests/test_utils.py index c752c31..5aca378 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,22 +1,18 @@ -""" -Module to test small_small_hr Signals -""" +"""Module to test small_small_hr Signals.""" from datetime import datetime -import pytz from django.conf import settings from django.test import TestCase, override_settings + +import pytz from model_mommy import mommy -from small_small_hr.models import Leave, StaffProfile, FreeDay -from small_small_hr.utils import (create_annual_leave, create_free_days, - get_carry_over) +from small_small_hr.models import FreeDay, Leave, StaffProfile +from small_small_hr.utils import create_annual_leave, create_free_days, get_carry_over class TestUtils(TestCase): - """ - Test class for utils - """ + """Test class for utils.""" @override_settings( SSHR_MAX_CARRY_OVER=10, @@ -28,52 +24,41 @@ class TestUtils(TestCase): 5: 1, # Friday 6: 0, # Saturday 7: 0, # Sunday - } + }, ) def test_get_carry_over(self): - """ - Test get_carry_over - """ - user = mommy.make('auth.User', id=23) + """Test get_carry_over.""" + user = mommy.make("auth.User", id=23) StaffProfile.objects.all().delete() - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) - self.assertEqual( - 0, - get_carry_over(staffprofile, 2017, Leave.REGULAR) - ) + self.assertEqual(0, get_carry_over(staffprofile, 2017, Leave.REGULAR)) create_annual_leave(staffprofile, 2017, Leave.REGULAR) # carry over should be 10 because the balance is 21 - self.assertEqual( - 10, - get_carry_over(staffprofile, 2018, Leave.REGULAR) - ) + self.assertEqual(10, get_carry_over(staffprofile, 2018, Leave.REGULAR)) # 12 days of leave, Sat & Sun not counted - start = datetime( - 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2017, 6, 20, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - - mommy.make('small_small_hr.Leave', leave_type=Leave.REGULAR, - start=start, end=end, status=Leave.APPROVED, - staff=staffprofile) + start = datetime(2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2017, 6, 20, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + mommy.make( + "small_small_hr.Leave", + leave_type=Leave.REGULAR, + start=start, + end=end, + review_status=Leave.APPROVED, + staff=staffprofile, + ) # carry over should be 9 => 21 - 12 - self.assertEqual( - 9, - get_carry_over(staffprofile, 2018, Leave.REGULAR) - ) + self.assertEqual(9, get_carry_over(staffprofile, 2018, Leave.REGULAR)) # no sick leave carry over create_annual_leave(staffprofile, 2017, Leave.SICK) - self.assertEqual( - 0, - get_carry_over(staffprofile, 2018, Leave.SICK) - ) + self.assertEqual(0, get_carry_over(staffprofile, 2018, Leave.SICK)) @override_settings( SSHR_DAY_LEAVE_VALUES={ @@ -87,12 +72,10 @@ def test_get_carry_over(self): } ) def test_create_annual_leave(self): - """ - Test create_annual_leave - """ - user = mommy.make('auth.User', id=56) + """Test create_annual_leave.""" + user = mommy.make("auth.User", id=56) StaffProfile.objects.all().delete() - staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) obj = create_annual_leave(staffprofile, 2016, Leave.REGULAR) @@ -103,14 +86,17 @@ def test_create_annual_leave(self): self.assertEqual(Leave.REGULAR, obj.leave_type) # 12 days of leave - start = datetime( - 2016, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - end = datetime( - 2016, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) - - mommy.make('small_small_hr.Leave', leave_type=Leave.REGULAR, - start=start, end=end, status=Leave.APPROVED, - staff=staffprofile) + start = datetime(2016, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime(2016, 6, 16, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + mommy.make( + "small_small_hr.Leave", + leave_type=Leave.REGULAR, + start=start, + end=end, + review_status=Leave.APPROVED, + staff=staffprofile, + ) obj2 = create_annual_leave(staffprofile, 2017, Leave.REGULAR) self.assertEqual(staffprofile, obj2.staff) @@ -128,61 +114,75 @@ def test_create_annual_leave(self): @override_settings( SSHR_FREE_DAYS=[ - {'day': 1, 'month': 1}, # New year - {'day': 1, 'month': 5}, # labour day - {'day': 1, 'month': 6}, # Madaraka day - {'day': 20, 'month': 10}, # Mashujaa day - {'day': 12, 'month': 12}, # Jamhuri day - {'day': 25, 'month': 12}, # Christmas - {'day': 26, 'month': 12}, # Boxing day + {"day": 1, "month": 1}, # New year + {"day": 1, "month": 5}, # labour day + {"day": 1, "month": 6}, # Madaraka day + {"day": 20, "month": 10}, # Mashujaa day + {"day": 12, "month": 12}, # Jamhuri day + {"day": 25, "month": 12}, # Christmas + {"day": 26, "month": 12}, # Boxing day ] ) def test_create_free_days(self): - """ - Test create_free_days - """ + """Test create_free_days.""" FreeDay.objects.all().delete() create_free_days(start_year=2014, number_of_years=2) self.assertEqual(14, FreeDay.objects.count()) self.assertTrue( - FreeDay.objects.filter( - date__year=2014, date__day=1, date__month=1).exists()) + FreeDay.objects.filter(date__year=2014, date__day=1, date__month=1).exists() + ) self.assertTrue( - FreeDay.objects.filter( - date__year=2014, date__day=1, date__month=5).exists()) + FreeDay.objects.filter(date__year=2014, date__day=1, date__month=5).exists() + ) self.assertTrue( - FreeDay.objects.filter( - date__year=2014, date__day=1, date__month=6).exists()) + FreeDay.objects.filter(date__year=2014, date__day=1, date__month=6).exists() + ) self.assertTrue( FreeDay.objects.filter( - date__year=2014, date__day=20, date__month=10).exists()) + date__year=2014, date__day=20, date__month=10 + ).exists() + ) self.assertTrue( FreeDay.objects.filter( - date__year=2014, date__day=12, date__month=12).exists()) + date__year=2014, date__day=12, date__month=12 + ).exists() + ) self.assertTrue( FreeDay.objects.filter( - date__year=2014, date__day=25, date__month=12).exists()) + date__year=2014, date__day=25, date__month=12 + ).exists() + ) self.assertTrue( FreeDay.objects.filter( - date__year=2014, date__day=26, date__month=12).exists()) + date__year=2014, date__day=26, date__month=12 + ).exists() + ) self.assertTrue( - FreeDay.objects.filter( - date__year=2015, date__day=1, date__month=1).exists()) + FreeDay.objects.filter(date__year=2015, date__day=1, date__month=1).exists() + ) self.assertTrue( - FreeDay.objects.filter( - date__year=2015, date__day=1, date__month=5).exists()) + FreeDay.objects.filter(date__year=2015, date__day=1, date__month=5).exists() + ) self.assertTrue( - FreeDay.objects.filter( - date__year=2015, date__day=1, date__month=6).exists()) + FreeDay.objects.filter(date__year=2015, date__day=1, date__month=6).exists() + ) self.assertTrue( FreeDay.objects.filter( - date__year=2015, date__day=20, date__month=10).exists()) + date__year=2015, date__day=20, date__month=10 + ).exists() + ) self.assertTrue( FreeDay.objects.filter( - date__year=2015, date__day=12, date__month=12).exists()) + date__year=2015, date__day=12, date__month=12 + ).exists() + ) self.assertTrue( FreeDay.objects.filter( - date__year=2015, date__day=25, date__month=12).exists()) + date__year=2015, date__day=25, date__month=12 + ).exists() + ) self.assertTrue( FreeDay.objects.filter( - date__year=2015, date__day=26, date__month=12).exists()) + date__year=2015, date__day=26, date__month=12 + ).exists() + ) diff --git a/tests/urls.py b/tests/urls.py new file mode 100644 index 0000000..e5b4f76 --- /dev/null +++ b/tests/urls.py @@ -0,0 +1,14 @@ +"""URLs for testing.""" +from django.http import HttpResponse +from django.urls import include, path + + +def homeview(request): + """Return home page.""" + return HttpResponse("

home page

") + + +urlpatterns = [ + path("", homeview), + path("reviews/", include("model_reviews.urls")), +]