From cfd2bab01efbe57c177147111ec746da48fbe833 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 13:41:29 +0300 Subject: [PATCH 01/83] Install django-model-reviews v1.0.0 --- requirements/dev.txt | 7 +++++-- setup.py | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 3e1d5ce..0b37549 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -18,10 +18,13 @@ 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-model-reviews==1.0.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-phonenumber-field, djangorestframework, model-mommy djangorestframework==3.11.0 filelock==3.0.12 # via tox, virtualenv flake8==3.8.2 @@ -64,7 +67,7 @@ pyparsing==2.4.7 # via packaging 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 +six==1.15.0 # via astroid, django-braces, django-contrib-comments, packaging, tox, traitlets, virtualenv snowballstemmer==2.0.0 # via pydocstyle sorl-thumbnail==12.6.3 sqlparse==0.3.1 # via django diff --git a/setup.py b/setup.py index 9e7b673..3149cf9 100644 --- a/setup.py +++ b/setup.py @@ -23,11 +23,12 @@ url="https://github.com/moshthepitt/small-small-hr", packages=find_packages(exclude=["docs", "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", From e4fe2dc4cb5c3f72696e30d48a2f8d91c3927ae2 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 13:56:44 +0300 Subject: [PATCH 02/83] Set include_package_data --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 3149cf9..f5d969a 100644 --- a/setup.py +++ b/setup.py @@ -45,4 +45,5 @@ "Framework :: Django :: 2.2", "Framework :: Django :: 3.0", ], + include_package_data=True, ) From 6122add8184ab3feeb5002b077fc00ae1e9b2180 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 13:59:06 +0300 Subject: [PATCH 03/83] Upgrade django-model-reviews --- requirements/dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 0b37549..1f88301 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -21,7 +21,7 @@ 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-model-reviews==1.0.0 +django-model-reviews==1.0.1 django-phonenumber-field==4.0.0 django-private-storage==2.2.2 django==3.0.7 # via django-braces, django-contrib-comments, django-model-reviews, django-phonenumber-field, djangorestframework, model-mommy @@ -66,7 +66,7 @@ pylint==2.5.2 pyparsing==2.4.7 # via packaging pytz==2020.1 # via babel, django pyyaml==5.3.1 # via pre-commit -regex==2020.5.14 # via black +regex==2020.6.7 # via black six==1.15.0 # via astroid, django-braces, django-contrib-comments, packaging, tox, traitlets, virtualenv snowballstemmer==2.0.0 # via pydocstyle sorl-thumbnail==12.6.3 From c01d5f20395a664a7bf1e80c6561855d0fa650d6 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:08:45 +0300 Subject: [PATCH 04/83] Add model_reviews to settings --- tests/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/settings.py b/tests/settings.py index f837e54..c0ea1b2 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -23,6 +23,7 @@ 'rest_framework', # custom 'small_small_hr', + 'model_reviews', ] DATABASES = { From 0d7aa4c8492855f8b30185b843b6b787c5ffaf66 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:09:14 +0300 Subject: [PATCH 05/83] Implement AbstractReview in BaseStaffRequest --- small_small_hr/models.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index ce1ab7d..e8ebe01 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -11,6 +11,7 @@ from django.utils import timezone from django.utils.translation import ugettext as _ +from model_reviews.models import AbstractReview from phonenumber_field.modelfields import PhoneNumberField from private_storage.fields import PrivateFileField from sorl.thumbnail import ImageField @@ -226,33 +227,23 @@ def __str__(self): return f'{self.staff.get_name()} - {self.name}' -class BaseStaffRequest(TimeStampedModel, models.Model): +class BaseStaffRequest(TimeStampedModel, AbstractReview): """ Abstract model class for Leave & Overtime tracking """ - APPROVED = '1' - REJECTED = '2' - PENDING = '3' - - STATUS_CHOICES = ( - (APPROVED, _('Approved')), - (PENDING, _('Pending')), - (REJECTED, _('Rejected')) - ) - 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) + 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) comments = models.TextField(_('Comments'), blank=True, default='') class Meta: # pylint: disable=too-few-public-methods """ - Meta options for StaffDocument + Meta options for BaseStaffRequest """ abstract = True From 5a42871434d5b81849f950ad0fd5263c73d7fa36 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:10:23 +0300 Subject: [PATCH 06/83] Add migrations for django-model-reviews --- .../migrations/0008_auto_20200607_1407.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 small_small_hr/migrations/0008_auto_20200607_1407.py diff --git a/small_small_hr/migrations/0008_auto_20200607_1407.py b/small_small_hr/migrations/0008_auto_20200607_1407.py new file mode 100644 index 0000000..df75753 --- /dev/null +++ b/small_small_hr/migrations/0008_auto_20200607_1407.py @@ -0,0 +1,63 @@ +# Generated by Django 3.0.7 on 2020-06-07 11:07 +# 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.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", + ), + ), + ] From da9ecc2754e59ed26939e59c59b6c79a25c4b5b8 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:20:15 +0300 Subject: [PATCH 07/83] Fix pylint issues --- small_small_hr/models.py | 430 ++++++++++++++++++++------------------- 1 file changed, 223 insertions(+), 207 deletions(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index e8ebe01..b71cbad 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -1,6 +1,4 @@ -""" -Models module for small_small_hr -""" +"""Models module for small_small_hr.""" from datetime import datetime, timedelta from decimal import Decimal @@ -23,289 +21,306 @@ 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): """ - 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) + 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"] 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 - """ + """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 + end_year=year, ) def get_approved_sick_days(self, year: int = datetime.today().year): - """ - Get approved leave days in the current year - """ + """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 + end_year=year, ) def get_available_leave_days(self, year: int = datetime.today().year): - """ - Get available leave days - """ + """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 + ) 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 - """ + """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 + ) 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, AbstractReview): - """ - Abstract model class for Leave & Overtime tracking - """ + """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')) - review_reason = models.TextField(_('Reason'), 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) - comments = models.TextField(_('Comments'), blank=True, default='') + _("Status"), + max_length=1, + choices=AbstractReview.STATUS_CHOICES, + default=AbstractReview.PENDING, + blank=True, + db_index=True, + ) + comments = models.TextField(_("Comments"), blank=True, default="") class Meta: # pylint: disable=too-few-public-methods - """ - Meta options for BaseStaffRequest - """ + """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() 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}") 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) 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 @@ -313,46 +328,54 @@ def get_duration(self): 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 """ @@ -361,13 +384,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: @@ -393,24 +414,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) @@ -419,30 +440,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, 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 From fa111c70b40ab846184a0445397436716dcb49b8 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:30:12 +0300 Subject: [PATCH 08/83] Use the correct filter field --- small_small_hr/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index b71cbad..921d901 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -453,7 +453,7 @@ def get_taken_leave_days( # pylint: disable=bad-continuation date__year__gte=start_year, date__year__lte=end_year ).values_list("date", flat=True) queryset = Leave.objects.filter( - staff=staffprofile, status=status, leave_type=leave_type + 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) From 314e516f843c27c7639c1910310147c1398ee2a9 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:32:35 +0300 Subject: [PATCH 09/83] Fix pylint and mypy issues --- small_small_hr/utils.py | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) 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() From fc1d43f1c5fbd5bf35a9287c8a77f193c518880a Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:32:43 +0300 Subject: [PATCH 10/83] Fix broken tests --- tests/test_models.py | 539 +++++++++++++++++++++++-------------------- 1 file changed, 290 insertions(+), 249 deletions(-) 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) From b576fb63c8c39d55d8f2d2e5136520d3f49611b4 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:41:45 +0300 Subject: [PATCH 11/83] Fix pylint warnings --- tests/test_forms.py | 1598 +++++++++++++++++++++---------------------- 1 file changed, 768 insertions(+), 830 deletions(-) diff --git a/tests/test_forms.py b/tests/test_forms.py index 4576117..0671254 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -1,58 +1,59 @@ -""" -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 datetime import date, datetime, timedelta from unittest.mock import patch -import pytz from django.conf import settings from django.contrib.auth.models import AnonymousUser from django.core.files.uploadedfile import SimpleUploadedFile from django.test import RequestFactory, TestCase, override_settings + +import pytz 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) +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 +66,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 +84,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 +116,29 @@ 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') + @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) + """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(), + "reason": "Extra work", } form = ApplyOverTimeForm(data=data) @@ -163,83 +149,80 @@ 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) + timedelta(seconds=3600 * 6).seconds, overtime.get_duration().seconds + ) + self.assertEqual("Extra work", overtime.review_reason) + self.assertEqual(OverTime.PENDING, overtime.review_status) + self.assertEqual("", overtime.comments) mock.assert_called_with(overtime_obj=overtime) 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(), + 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(), + "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(), + "reason": "Extra work", + "status": OverTime.APPROVED, + "comments": "Cool", } form = OverTimeForm(data=data) @@ -250,42 +233,43 @@ 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) + self.assertEqual("Cool", overtime.comments) 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(), + 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(), + "reason": "Extra work", + "status": OverTime.REJECTED, + "comments": "Already there", } form = OverTimeForm(data=data) @@ -296,75 +280,69 @@ 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) + self.assertEqual("Already there", overtime.comments) 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(), + "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') + @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) + """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, + "reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -374,11 +352,10 @@ 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) + 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) + self.assertEqual("", leave.comments) mock.assert_called_with(leave_obj=leave) @override_settings( @@ -392,38 +369,39 @@ def test_leaveform_apply(self, mock): 5: 1, # Friday 6: 1, # Saturday 7: 1, # Sunday - } + }, ) - @patch('small_small_hr.forms.leave_application_email') + @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) + """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, + "reason": "Mini retirement", } form = ApplyLeaveForm(data=data) @@ -431,7 +409,7 @@ def test_leave_oversubscribe(self, mock): leave = form.save() # make it approved - leave.status = Leave.APPROVED + leave.review_status = Leave.APPROVED leave.save() leave.refresh_from_db() @@ -439,16 +417,16 @@ def test_leave_oversubscribe(self, mock): 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) + 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("", leave.comments) mock.assert_called_with(leave_obj=leave) 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 +441,87 @@ def test_leave_oversubscribe(self, mock): 5: 1, # Friday 6: 1, # Saturday 7: 1, # Sunday - } + }, ) - @patch('small_small_hr.forms.leave_application_email') + @patch("small_small_hr.forms.leave_application_email") def test_leave_oversubscribe_off(self, mock): - """ - Test leave oversubscribe when SSHR_ALLOW_OVERSUBSCRIBE is False - """ + """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) + 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, + "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') + @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) + """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, + "reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -551,98 +531,103 @@ 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) + 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("", leave.comments) mock.assert_called_with(leave_obj=leave) 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, + 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, + "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, + "reason": "Need a break", + "status": Leave.APPROVED, + "comments": "Okay", } form = LeaveForm(data=data) @@ -652,44 +637,44 @@ 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) + self.assertEqual("Okay", leave.comments) @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, + "reason": "Need a break", + "comments": "Just no", + "status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -699,50 +684,54 @@ 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) + self.assertEqual("Just no", leave.comments) @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, + 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, + "reason": "Need a break", + "comments": "Already exists", + "status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -752,42 +741,42 @@ 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) + self.assertEqual("Already exists", leave.comments) @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, + "reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -797,44 +786,44 @@ 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) + self.assertEqual("", leave.comments) @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, + "reason": "Need a break", + "comments": "Just no", + "status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -844,194 +833,188 @@ 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) + self.assertEqual("Just no", leave.comments) 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, + "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, + "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, + "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, + "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 +1022,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]) From 57f452e35345ee5e58d36db2eae0d08cd0d4f780 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:53:22 +0300 Subject: [PATCH 12/83] Pylint disable bad-continuation --- .pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 43d2e79..b611e76 100644 --- a/.pylintrc +++ b/.pylintrc @@ -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 From 8cad359b0c0cf9d1fb4fdd5da3c8572098cd3a34 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:53:26 +0300 Subject: [PATCH 13/83] Fix pylint warnings --- small_small_hr/forms.py | 902 ++++++++++++++++++---------------------- 1 file changed, 416 insertions(+), 486 deletions(-) diff --git a/small_small_hr/forms.py b/small_small_hr/forms.py index 81cffda..92dd273 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,184 @@ 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.emails import leave_application_email, overtime_application_email +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' - ] + fields = ["staff", "date", "start", "end", "reason", "status", "comments"] 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("reason",), + Field("status",), + Field("comments"), + 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") + status = cleaned_data.get("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, 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) + 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", + "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 +199,163 @@ 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("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' - ] + fields = ["staff", "leave_type", "start", "end", "reason", "status", "comments"] 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("reason",), + Field("status",), + Field("comments"), + 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") + status = cleaned_data.get("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, 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) + 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", + "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 +363,87 @@ 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("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 +452,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"),), ) From 661362753ba4fdf34f69659082c16402c41645a3 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 14:56:10 +0300 Subject: [PATCH 14/83] Fix reason and status fields --- small_small_hr/forms.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/small_small_hr/forms.py b/small_small_hr/forms.py index 92dd273..6ae58e1 100644 --- a/small_small_hr/forms.py +++ b/small_small_hr/forms.py @@ -120,7 +120,15 @@ class Meta: # pylint: disable=too-few-public-methods """Class meta options.""" model = OverTime - fields = ["staff", "date", "start", "end", "reason", "status", "comments"] + fields = [ + "staff", + "date", + "start", + "end", + "review_reason", + "review_status", + "comments", + ] def __init__(self, *args, **kwargs): """Initialize the form.""" @@ -138,8 +146,8 @@ def __init__(self, *args, **kwargs): Field("date",), Field("start",), Field("end",), - Field("reason",), - Field("status",), + Field("review_reason",), + Field("review_status",), Field("comments"), FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) @@ -185,7 +193,7 @@ class Meta: # pylint: disable=too-few-public-methods "date", "start", "end", - "reason", + "review_reason", ] def __init__(self, *args, **kwargs): @@ -214,7 +222,7 @@ def __init__(self, *args, **kwargs): Field("date",), Field("start",), Field("end",), - Field("reason",), + Field("review_reason",), FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) @@ -235,7 +243,15 @@ class Meta: # pylint: disable=too-few-public-methods """Class meta options.""" model = Leave - fields = ["staff", "leave_type", "start", "end", "reason", "status", "comments"] + fields = [ + "staff", + "leave_type", + "start", + "end", + "review_reason", + "review_status", + "comments", + ] def __init__(self, *args, **kwargs): """Initialize the form.""" @@ -253,8 +269,8 @@ def __init__(self, *args, **kwargs): Field("leave_type",), Field("start",), Field("end",), - Field("reason",), - Field("status",), + Field("review_reason",), + Field("review_status",), Field("comments"), FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) @@ -349,7 +365,7 @@ class Meta: # pylint: disable=too-few-public-methods "leave_type", "start", "end", - "reason", + "review_reason", ] def __init__(self, *args, **kwargs): @@ -378,7 +394,7 @@ def __init__(self, *args, **kwargs): Field("leave_type",), Field("start",), Field("end",), - Field("reason",), + Field("review_reason",), FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) From 5183c0fbf224df2c217f8ff349197bc0e6a1f768 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:01:09 +0300 Subject: [PATCH 15/83] Use review_status instead of status --- small_small_hr/forms.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/small_small_hr/forms.py b/small_small_hr/forms.py index 6ae58e1..e7b40af 100644 --- a/small_small_hr/forms.py +++ b/small_small_hr/forms.py @@ -159,7 +159,7 @@ def clean(self): start = cleaned_data.get("start") date = cleaned_data.get("date") staff = cleaned_data.get("staff") - status = cleaned_data.get("status") + review_status = cleaned_data.get("review_status") # end must be later than start if end <= start: @@ -168,13 +168,13 @@ def clean(self): # 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 + 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: + 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) @@ -302,7 +302,7 @@ def clean(self): staff = cleaned_data.get("staff") end = cleaned_data.get("end") start = cleaned_data.get("start") - status = cleaned_data.get("status") + review_status = cleaned_data.get("review_status") if all([staff, leave_type, start, end]): # end year and start year must be the same @@ -341,13 +341,13 @@ def clean(self): # 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 + 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: + 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) From c40d46400ec562a6cab0558c49c526a636588097 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:01:45 +0300 Subject: [PATCH 16/83] Fix status and reason fields These fields should now be review_status and review_reason, respectively. --- tests/test_forms.py | 58 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/test_forms.py b/tests/test_forms.py index 0671254..59cb661 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -138,7 +138,7 @@ def test_overtime_form_apply(self, mock): "date": start.date(), "start": start.time(), "end": end.time(), - "reason": "Extra work", + "review_reason": "Extra work", } form = ApplyOverTimeForm(data=data) @@ -173,7 +173,7 @@ def test_overtime_form_apply_no_overlap(self): "small_small_hr.OverTime", start=start.time(), end=end.time(), - status=OverTime.APPROVED, + review_status=OverTime.APPROVED, date=start.date, staff=staffprofile, ) @@ -183,7 +183,7 @@ def test_overtime_form_apply_no_overlap(self): "date": start.date(), "start": start.time(), "end": end.time(), - "reason": "Extra work", + "review_reason": "Extra work", } form = ApplyOverTimeForm(data=data) @@ -220,8 +220,8 @@ def test_overtime_form_process(self): "date": start.date(), "start": start.time(), "end": end.time(), - "reason": "Extra work", - "status": OverTime.APPROVED, + "review_reason": "Extra work", + "review_status": OverTime.APPROVED, "comments": "Cool", } @@ -257,7 +257,7 @@ def test_overtime_form_process_with_overlap(self): "small_small_hr.OverTime", start=start.time(), end=end.time(), - status=OverTime.APPROVED, + review_status=OverTime.APPROVED, date=start.date, staff=staffprofile, ) @@ -267,8 +267,8 @@ def test_overtime_form_process_with_overlap(self): "date": start.date(), "start": start.time(), "end": end.time(), - "reason": "Extra work", - "status": OverTime.REJECTED, + "review_reason": "Extra work", + "review_status": OverTime.REJECTED, "comments": "Already there", } @@ -303,7 +303,7 @@ def test_overtime_form_start_end(self): "date": start.date(), "start": start.time(), "end": end.time(), - "reason": "Extra work", + "review_reason": "Extra work", } form = OverTimeForm(data=data) @@ -342,7 +342,7 @@ def test_leaveform_apply(self, mock): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -401,7 +401,7 @@ def test_leave_oversubscribe(self, mock): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Mini retirement", + "review_reason": "Mini retirement", } form = ApplyLeaveForm(data=data) @@ -475,7 +475,7 @@ def test_leave_oversubscribe_off(self, mock): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Mini retirement", + "review_reason": "Mini retirement", } form = ApplyLeaveForm(data=data) @@ -521,7 +521,7 @@ def test_one_day_leave(self, mock): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -573,7 +573,7 @@ def test_leaveform_no_overlap(self): leave_type=Leave.REGULAR, start=start, end=end, - status=Leave.APPROVED, + review_status=Leave.APPROVED, staff=staffprofile, ) @@ -582,7 +582,7 @@ def test_leaveform_no_overlap(self): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -625,8 +625,8 @@ def test_leaveform_admin(self): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Need a break", - "status": Leave.APPROVED, + "review_reason": "Need a break", + "review_status": Leave.APPROVED, "comments": "Okay", } @@ -672,9 +672,9 @@ def test_leaveform_process(self): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", "comments": "Just no", - "status": Leave.REJECTED, + "review_status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -713,7 +713,7 @@ def test_leaveform_process_with_overlap(self): start=start, end=end, leave_type=Leave.REGULAR, - status=Leave.APPROVED, + review_status=Leave.APPROVED, ) mommy.make( @@ -729,9 +729,9 @@ def test_leaveform_process_with_overlap(self): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", "comments": "Already exists", - "status": Leave.REJECTED, + "review_status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -776,7 +776,7 @@ def test_sickleave_apply(self): "leave_type": Leave.SICK, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", } form = ApplyLeaveForm(data=data) @@ -821,9 +821,9 @@ def test_sickleave_process(self): "leave_type": Leave.SICK, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", "comments": "Just no", - "status": Leave.REJECTED, + "review_status": Leave.REJECTED, } form = LeaveForm(data=data) @@ -867,7 +867,7 @@ def test_leaveform_start_end(self): "leave_type": Leave.SICK, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", } form = LeaveForm(data=data) @@ -884,7 +884,7 @@ def test_leaveform_start_end(self): "leave_type": Leave.SICK, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", } form2 = LeaveForm(data=data2) @@ -927,7 +927,7 @@ def test_leaveform_max_days(self): "leave_type": Leave.REGULAR, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", } form = LeaveForm(data=data) @@ -973,7 +973,7 @@ def test_leaveform_max_sick_days(self): "leave_type": Leave.SICK, "start": start, "end": end, - "reason": "Need a break", + "review_reason": "Need a break", } form = LeaveForm(data=data) From b935aa340af7bda29f43fef02c59094c34d929e0 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:08:39 +0300 Subject: [PATCH 17/83] Fix broken test --- tests/test_forms.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_forms.py b/tests/test_forms.py index 59cb661..f061998 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -6,11 +6,13 @@ 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 import pytz from model_mommy import mommy +from model_reviews.models import ModelReview from small_small_hr.forms import ( AnnualLeaveForm, @@ -409,8 +411,10 @@ def test_leave_oversubscribe(self, mock): leave = form.save() # make it approved - leave.review_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) From 818caacd2b45294ea76892a9a332c3844a09e3ae Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:10:40 +0300 Subject: [PATCH 18/83] Fix pylint issues --- tests/test_utils.py | 166 ++++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index c752c31..9e52cd8 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, + 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, + 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() + ) From c8d0902c1d9ec83887b4a03792a3608be00137d3 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:11:36 +0300 Subject: [PATCH 19/83] Fix broken tests --- tests/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 9e52cd8..5aca378 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -48,7 +48,7 @@ def test_get_carry_over(self): leave_type=Leave.REGULAR, start=start, end=end, - status=Leave.APPROVED, + review_status=Leave.APPROVED, staff=staffprofile, ) @@ -94,7 +94,7 @@ def test_create_annual_leave(self): leave_type=Leave.REGULAR, start=start, end=end, - status=Leave.APPROVED, + review_status=Leave.APPROVED, staff=staffprofile, ) From c800307fdc6b7700a053dae00f8cc341b65b9c0a Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:17:54 +0300 Subject: [PATCH 20/83] Use get_review_status_display --- small_small_hr/emails.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/small_small_hr/emails.py b/small_small_hr/emails.py index b53ec32..19ca0ed 100644 --- a/small_small_hr/emails.py +++ b/small_small_hr/emails.py @@ -1,6 +1,4 @@ -""" -Emails module for scam app -""" +"""Emails module for scam app.""" from django.conf import settings from django.contrib.sites.models import Site from django.core.mail import EmailMultiAlternatives @@ -21,7 +19,7 @@ def send_email( # pylint: disable=too-many-arguments,too-many-locals,bad-contin template_path: str = "small_small_hr/email", ): """ - Sends a generic email + Send a generic email. :param name: name of person :param email: email address to send to @@ -62,9 +60,7 @@ def send_email( # pylint: disable=too-many-arguments,too-many-locals,bad-contin def leave_application_email(leave_obj: Leave): - """ - Sends an email to admins when a leave application is made - """ + """Send an email to admins when a leave application is made.""" msg = getattr( settings, "SSHR_LEAVE_APPLICATION_EMAIL_TXT", @@ -87,16 +83,14 @@ def leave_application_email(leave_obj: Leave): def leave_processed_email(leave_obj: Leave): - """ - Sends an email to admins when a leave application is processed - """ + """Send 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." + f"{leave_obj.get_review_status_display()}. Log in for more info." ), ) subj = getattr( @@ -116,9 +110,7 @@ def leave_processed_email(leave_obj: Leave): def overtime_application_email(overtime_obj: OverTime): - """ - Sends an email to admins when an overtime application is made - """ + """Send an email to admins when an overtime application is made.""" msg = getattr( settings, "SSHR_OVERTIME_APPLICATION_EMAIL_TXT", @@ -144,9 +136,7 @@ def overtime_application_email(overtime_obj: OverTime): def overtime_processed_email(overtime_obj: OverTime): - """ - Sends an email to admins when an overtime application is processed - """ + """Send an email to admins when an overtime application is processed.""" if overtime_obj.staff.user.email: msg = getattr( @@ -154,7 +144,7 @@ def overtime_processed_email(overtime_obj: OverTime): "SSHR_OVERTIME_PROCESSED_EMAIL_TXT", _( f"You overtime application status is " - f"{overtime_obj.get_status_display()}. Log in for more info." + f"{overtime_obj.get_review_status_display()}. Log in for more info." ), ) subj = getattr( From 1cfd0260dccce99b2e60f33764f8c61d685330ac Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:19:25 +0300 Subject: [PATCH 21/83] Fix broken tests --- tests/test_emails.py | 256 +++++++++++++++++++------------------------ 1 file changed, 114 insertions(+), 142 deletions(-) diff --git a/tests/test_emails.py b/tests/test_emails.py index 2ec2846..3768008 100644 --- a/tests/test_emails.py +++ b/tests/test_emails.py @@ -1,6 +1,4 @@ -""" -Module to test small_small_hr Emails -""" +"""Module to test small_small_hr Emails.""" from datetime import datetime from unittest.mock import call, patch @@ -11,10 +9,13 @@ import pytz from model_mommy import mommy -from small_small_hr.emails import (leave_application_email, - leave_processed_email, - overtime_application_email, - overtime_processed_email, send_email) +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 @@ -22,36 +23,31 @@ SSHR_ADMIN_EMAILS=["admin@example.com"], SSHR_ADMIN_LEAVE_EMAILS=["hr@example.com"], SSHR_ADMIN_OVERTIME_EMAILS=["ot@example.com"], - SSHR_ADMIN_NAME="mosh" + SSHR_ADMIN_NAME="mosh", ) class TestEmails(TestCase): - """ - Test class for emails - """ + """Test class for emails.""" 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) + "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') + @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)) + """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) + "small_small_hr.Leave", + staff=self.staffprofile, + start=start, + end=end, + leave_type=Leave.SICK, + review_status=Leave.PENDING, + ) leave_application_email(leave) @@ -59,24 +55,24 @@ def test_leave_application_email(self, mock): 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 + message="There has been a new leave application. Please log in to process it.", # noqa # pylint: disable=line-too-long obj=leave, template="leave_application", ) - @patch('small_small_hr.emails.send_email') + @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)) + """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) + "small_small_hr.Leave", + staff=self.staffprofile, + start=start, + end=end, + leave_type=Leave.SICK, + review_status=Leave.APPROVED, + ) leave_processed_email(leave) @@ -84,23 +80,23 @@ def test_leave_processed_email(self, mock): 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 + message="You leave application status is Approved. Log in for more info.", # noqa # pylint: disable=line-too-long obj=leave, - cc_list=['hr@example.com'] + cc_list=["hr@example.com"], ) - @patch('small_small_hr.emails.send_email') + @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)) + """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) + "small_small_hr.OverTime", + staff=self.staffprofile, + start=start, + end=end, + review_status=OverTime.PENDING, + ) overtime_application_email(overtime) @@ -108,23 +104,23 @@ def test_overtime_application_email(self, mock): 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 + message="There has been a new overtime application. Please log in to process it.", # noqa # pylint: disable=line-too-long obj=overtime, template="overtime_application", ) - @patch('small_small_hr.emails.send_email') + @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)) + """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) + "small_small_hr.OverTime", + staff=self.staffprofile, + start=start, + end=end, + review_status=OverTime.REJECTED, + ) overtime_processed_email(overtime) @@ -132,56 +128,53 @@ def test_overtime_processed_email(self, mock): 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 + message="You overtime application status is Rejected. Log in for more info.", # noqa # pylint: disable=line-too-long obj=overtime, - cc_list=['ot@example.com'] + cc_list=["ot@example.com"], ) def test_send_email(self): - """ - Test send_email - """ - + """Test send_email.""" message = "The quick brown fox." data = { - 'name': 'Bob Munro', - 'email': 'bob@example.com', - 'subject': "I love oov", - 'message': message, - 'cc_list': settings.SSHR_ADMIN_EMAILS + "name": "Bob Munro", + "email": "bob@example.com", + "subject": "I love oov", + "message": message, + "cc_list": settings.SSHR_ADMIN_EMAILS, } 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].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') + "Hello Bob Munro,\n\nThe quick brown fox.\n\nThank you,\n\n" + "example.com\n------\nhttp://example.com\n", + ) self.assertEqual( mail.outbox[0].alternatives[0][0], - 'Hello Bob Munro,

The quick brown fox.



' - 'Thank you,
example.com
------
http://example.com') + "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') + @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 - """ + """Test the templates used with send_email.""" mock.return_value = "Some random text" site_mock.return_value = 42 # ensure that this is predictable # test generic data = { - 'name': 'Bob Munro', - 'email': 'bob@example.com', - 'subject': "I love oov", - 'message': "Its dangerous", + "name": "Bob Munro", + "email": "bob@example.com", + "subject": "I love oov", + "message": "Its dangerous", } send_email(**data) @@ -192,18 +185,9 @@ def test_send_email_templates(self, mock, site_mock): 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 - ) + 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) @@ -211,38 +195,31 @@ def test_send_email_templates(self, mock, site_mock): mock.reset_mock() # test 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)) leave = mommy.make( - 'small_small_hr.Leave', staff=self.staffprofile, start=start, - end=end, leave_type=Leave.SICK, - status=Leave.PENDING) + "small_small_hr.Leave", + staff=self.staffprofile, + start=start, + end=end, + leave_type=Leave.SICK, + review_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 + message="There has been a new leave application. Please log in to process it.", # noqa # pylint: disable=line-too-long object=leave, - SITE=42 + SITE=42, ) 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 - ) + 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) @@ -250,37 +227,32 @@ def test_send_email_templates(self, mock, site_mock): 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)) + 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) + "small_small_hr.OverTime", + staff=self.staffprofile, + start=start, + end=end, + review_status=OverTime.PENDING, + ) overtime_application_email(overtime) context = dict( name="mosh", subject="New Overtime Application", - message="There has been a new overtime application. Please log in to process it.", # noqa + message="There has been a new overtime application. Please log in to process it.", # noqa # pylint: disable=line-too-long object=overtime, - SITE=42 + SITE=42, ) expected_calls = [ call( - "small_small_hr/email/overtime_application_email_subject.txt", - context + "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 - ) + call("small_small_hr/email/overtime_application_email_body.txt", context), + call("small_small_hr/email/overtime_application_email_body.html", context), ] mock.assert_has_calls(expected_calls) From 25ac028c1f9e8d4f6ff940c68be23a97b5b4217d Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:27:21 +0300 Subject: [PATCH 22/83] Use send_email from django-model-reviews --- small_small_hr/emails.py | 60 ++++------------------------------------ 1 file changed, 6 insertions(+), 54 deletions(-) diff --git a/small_small_hr/emails.py b/small_small_hr/emails.py index 19ca0ed..dd43263 100644 --- a/small_small_hr/emails.py +++ b/small_small_hr/emails.py @@ -1,62 +1,10 @@ """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 - +from model_reviews.emails import send_email -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", -): - """ - Send 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) +from small_small_hr.models import Leave, OverTime def leave_application_email(leave_obj: Leave): @@ -79,6 +27,7 @@ def leave_application_email(leave_obj: Leave): message=msg, obj=leave_obj, template="leave_application", + template_path="small_small_hr/email", ) @@ -106,6 +55,7 @@ def leave_processed_email(leave_obj: Leave): message=msg, obj=leave_obj, cc_list=settings.SSHR_ADMIN_LEAVE_EMAILS, + template_path="small_small_hr/email", ) @@ -132,6 +82,7 @@ def overtime_application_email(overtime_obj: OverTime): message=msg, obj=overtime_obj, template="overtime_application", + template_path="small_small_hr/email", ) @@ -160,4 +111,5 @@ def overtime_processed_email(overtime_obj: OverTime): message=msg, obj=overtime_obj, cc_list=settings.SSHR_ADMIN_OVERTIME_EMAILS, + template_path="small_small_hr/email", ) From 9a44f257c491e909b543bc9ca7bdf3a1be7f7954 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:27:50 +0300 Subject: [PATCH 23/83] Fix broken tests for emails - Remove tests for send_email - Fix the other tests --- tests/test_emails.py | 132 ++----------------------------------------- 1 file changed, 5 insertions(+), 127 deletions(-) diff --git a/tests/test_emails.py b/tests/test_emails.py index 3768008..bf4137b 100644 --- a/tests/test_emails.py +++ b/tests/test_emails.py @@ -1,9 +1,8 @@ """Module to test small_small_hr Emails.""" from datetime import datetime -from unittest.mock import call, patch +from unittest.mock import patch from django.conf import settings -from django.core import mail from django.test import TestCase, override_settings import pytz @@ -14,7 +13,6 @@ leave_processed_email, overtime_application_email, overtime_processed_email, - send_email, ) from small_small_hr.models import Leave, OverTime @@ -58,6 +56,7 @@ def test_leave_application_email(self, mock): message="There has been a new leave application. Please log in to process it.", # noqa # pylint: disable=line-too-long obj=leave, template="leave_application", + template_path="small_small_hr/email", ) @patch("small_small_hr.emails.send_email") @@ -83,6 +82,7 @@ def test_leave_processed_email(self, mock): message="You leave application status is Approved. Log in for more info.", # noqa # pylint: disable=line-too-long obj=leave, cc_list=["hr@example.com"], + template_path="small_small_hr/email", ) @patch("small_small_hr.emails.send_email") @@ -107,6 +107,7 @@ def test_overtime_application_email(self, mock): message="There has been a new overtime application. Please log in to process it.", # noqa # pylint: disable=line-too-long obj=overtime, template="overtime_application", + template_path="small_small_hr/email", ) @patch("small_small_hr.emails.send_email") @@ -131,128 +132,5 @@ def test_overtime_processed_email(self, mock): message="You overtime application status is Rejected. Log in for more info.", # noqa # pylint: disable=line-too-long obj=overtime, cc_list=["ot@example.com"], + template_path="small_small_hr/email", ) - - def test_send_email(self): - """Test send_email.""" - message = "The quick brown fox." - - data = { - "name": "Bob Munro", - "email": "bob@example.com", - "subject": "I love oov", - "message": message, - "cc_list": settings.SSHR_ADMIN_EMAILS, - } - - 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", - ) - 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 - - # test generic - data = { - "name": "Bob Munro", - "email": "bob@example.com", - "subject": "I love oov", - "message": "Its dangerous", - } - - 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 - 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, - review_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 # pylint: disable=line-too-long - object=leave, - SITE=42, - ) - - 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, - review_status=OverTime.PENDING, - ) - - overtime_application_email(overtime) - - context = dict( - name="mosh", - subject="New Overtime Application", - message="There has been a new overtime application. Please log in to process it.", # noqa # pylint: disable=line-too-long - object=overtime, - SITE=42, - ) - - 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), - ] - - mock.assert_has_calls(expected_calls) From 642df2272cc24960011eb46eec5a3a73ba8ea507 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:38:14 +0300 Subject: [PATCH 24/83] Remove comments field --- small_small_hr/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index 921d901..7da762c 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -253,7 +253,6 @@ class BaseStaffRequest(TimeStampedModel, AbstractReview): blank=True, db_index=True, ) - comments = models.TextField(_("Comments"), blank=True, default="") class Meta: # pylint: disable=too-few-public-methods """Meta options for BaseStaffRequest.""" From 94d7376576a33dd198f5eefab91cb43f357af7bb Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:39:00 +0300 Subject: [PATCH 25/83] Recreate migrations to remove comments field --- ...{0008_auto_20200607_1407.py => 0008_auto_20200607_1537.py} | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename small_small_hr/migrations/{0008_auto_20200607_1407.py => 0008_auto_20200607_1537.py} (91%) diff --git a/small_small_hr/migrations/0008_auto_20200607_1407.py b/small_small_hr/migrations/0008_auto_20200607_1537.py similarity index 91% rename from small_small_hr/migrations/0008_auto_20200607_1407.py rename to small_small_hr/migrations/0008_auto_20200607_1537.py index df75753..dd9f1bd 100644 --- a/small_small_hr/migrations/0008_auto_20200607_1407.py +++ b/small_small_hr/migrations/0008_auto_20200607_1537.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.7 on 2020-06-07 11:07 +# 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 @@ -22,6 +22,8 @@ class Migration(migrations.Migration): 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", From 05deef9f553ee24e9b87551a1a96af8401dfab59 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 15:42:28 +0300 Subject: [PATCH 26/83] Remove comments field --- small_small_hr/forms.py | 4 ---- tests/test_forms.py | 17 ----------------- 2 files changed, 21 deletions(-) diff --git a/small_small_hr/forms.py b/small_small_hr/forms.py index e7b40af..40d3dd0 100644 --- a/small_small_hr/forms.py +++ b/small_small_hr/forms.py @@ -127,7 +127,6 @@ class Meta: # pylint: disable=too-few-public-methods "end", "review_reason", "review_status", - "comments", ] def __init__(self, *args, **kwargs): @@ -148,7 +147,6 @@ def __init__(self, *args, **kwargs): Field("end",), Field("review_reason",), Field("review_status",), - Field("comments"), FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) @@ -250,7 +248,6 @@ class Meta: # pylint: disable=too-few-public-methods "end", "review_reason", "review_status", - "comments", ] def __init__(self, *args, **kwargs): @@ -271,7 +268,6 @@ def __init__(self, *args, **kwargs): Field("end",), Field("review_reason",), Field("review_status",), - Field("comments"), FormActions(Submit("submitBtn", _("Submit"), css_class="btn-primary"),), ) diff --git a/tests/test_forms.py b/tests/test_forms.py index f061998..921fa26 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -155,7 +155,6 @@ def test_overtime_form_apply(self, mock): ) self.assertEqual("Extra work", overtime.review_reason) self.assertEqual(OverTime.PENDING, overtime.review_status) - self.assertEqual("", overtime.comments) mock.assert_called_with(overtime_obj=overtime) def test_overtime_form_apply_no_overlap(self): @@ -224,7 +223,6 @@ def test_overtime_form_process(self): "end": end.time(), "review_reason": "Extra work", "review_status": OverTime.APPROVED, - "comments": "Cool", } form = OverTimeForm(data=data) @@ -239,7 +237,6 @@ def test_overtime_form_process(self): ) self.assertEqual("Extra work", overtime.review_reason) self.assertEqual(OverTime.APPROVED, overtime.review_status) - self.assertEqual("Cool", overtime.comments) def test_overtime_form_process_with_overlap(self): """Test OverTimeForm with overlap for existing objects.""" @@ -271,7 +268,6 @@ def test_overtime_form_process_with_overlap(self): "end": end.time(), "review_reason": "Extra work", "review_status": OverTime.REJECTED, - "comments": "Already there", } form = OverTimeForm(data=data) @@ -286,7 +282,6 @@ def test_overtime_form_process_with_overlap(self): ) self.assertEqual("Extra work", overtime.review_reason) self.assertEqual(OverTime.REJECTED, overtime.review_status) - self.assertEqual("Already there", overtime.comments) def test_overtime_form_start_end(self): """Test OverTimeForm start end fields.""" @@ -357,7 +352,6 @@ def test_leaveform_apply(self, mock): 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) - self.assertEqual("", leave.comments) mock.assert_called_with(leave_obj=leave) @override_settings( @@ -424,7 +418,6 @@ def test_leave_oversubscribe(self, mock): 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("", leave.comments) mock.assert_called_with(leave_obj=leave) self.assertEqual( 40, @@ -538,7 +531,6 @@ def test_one_day_leave(self, mock): 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("", leave.comments) mock.assert_called_with(leave_obj=leave) self.assertEqual( 1, @@ -631,7 +623,6 @@ def test_leaveform_admin(self): "end": end, "review_reason": "Need a break", "review_status": Leave.APPROVED, - "comments": "Okay", } form = LeaveForm(data=data) @@ -644,7 +635,6 @@ def test_leaveform_admin(self): 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) - self.assertEqual("Okay", leave.comments) @override_settings(SSHR_DEFAULT_TIME=7) def test_leaveform_process(self): @@ -677,7 +667,6 @@ def test_leaveform_process(self): "start": start, "end": end, "review_reason": "Need a break", - "comments": "Just no", "review_status": Leave.REJECTED, } @@ -691,7 +680,6 @@ def test_leaveform_process(self): 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) - self.assertEqual("Just no", leave.comments) @override_settings(SSHR_DEFAULT_TIME=7) def test_leaveform_process_with_overlap(self): @@ -734,7 +722,6 @@ def test_leaveform_process_with_overlap(self): "start": start, "end": end, "review_reason": "Need a break", - "comments": "Already exists", "review_status": Leave.REJECTED, } @@ -748,7 +735,6 @@ def test_leaveform_process_with_overlap(self): 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) - self.assertEqual("Already exists", leave.comments) @override_settings(SSHR_DEFAULT_TIME=7) def test_sickleave_apply(self): @@ -793,7 +779,6 @@ def test_sickleave_apply(self): 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) - self.assertEqual("", leave.comments) @override_settings(SSHR_DEFAULT_TIME=7) def test_sickleave_process(self): @@ -826,7 +811,6 @@ def test_sickleave_process(self): "start": start, "end": end, "review_reason": "Need a break", - "comments": "Just no", "review_status": Leave.REJECTED, } @@ -840,7 +824,6 @@ def test_sickleave_process(self): 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) - self.assertEqual("Just no", leave.comments) def test_leaveform_start_end(self): """Test start and end.""" From 9f63af2ae2be02a6bb3d87f43ae3115fbb164534 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 17:46:33 +0300 Subject: [PATCH 27/83] Add module to test the process --- tests/test_process.py | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/test_process.py diff --git a/tests/test_process.py b/tests/test_process.py new file mode 100644 index 0000000..aefc248 --- /dev/null +++ b/tests/test_process.py @@ -0,0 +1,69 @@ +"""Test the leave/overtime application process.""" +from datetime import datetime + +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_reviews.forms import PerformReview +from model_reviews.models import ModelReview + +from small_small_hr.forms import ApplyLeaveForm +from small_small_hr.models import Leave + + +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() + + def test_xxx(self): + """Test xxx.""" + 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() + + # 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 = Leave.none() + self.fail() + + # perform the review + data = { + "review": review.pk, + # "reviewer": reviewer.pk, + "review_status": ModelReview.APPROVED, + } + form = PerformReview(data=data) + self.assertTrue(form.is_valid()) + form.save() From a65011d2de17730ce8a3f3b1a36441d789add7ba Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 18:53:35 +0300 Subject: [PATCH 28/83] Install django-mptt --- requirements/dev.txt | 4 +++- setup.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 1f88301..ec134a8 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -21,10 +21,12 @@ 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.1 +django-mptt==0.11.0 django-phonenumber-field==4.0.0 django-private-storage==2.2.2 -django==3.0.7 # via django-braces, django-contrib-comments, django-model-reviews, 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 filelock==3.0.12 # via tox, virtualenv flake8==3.8.2 diff --git a/setup.py b/setup.py index f5d969a..c94b296 100644 --- a/setup.py +++ b/setup.py @@ -7,8 +7,8 @@ # read the contents of your README file with open( - path.join(path.abspath(path.dirname(__file__)), "README.md"), - encoding="utf-8") as f: + path.join(path.abspath(path.dirname(__file__)), "README.md"), encoding="utf-8" +) as f: LONG_DESCRIPTION = f.read() setup( @@ -35,6 +35,7 @@ "djangorestframework", "sorl-thumbnail", "Pillow", + "django-mptt", ], classifiers=[ "Programming Language :: Python", From 2060f6a4e3b3224b847f8cad483ba30e8589cf2b Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 22:45:32 +0300 Subject: [PATCH 29/83] Add supervisor field --- small_small_hr/models.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index 7da762c..673b3a2 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -10,6 +10,7 @@ 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 @@ -53,7 +54,7 @@ def __str__(self): return self.name -class StaffProfile(TimeStampedModel, models.Model): +class StaffProfile(TimeStampedModel, MPTTModel): """ StaffProfile model class. @@ -75,6 +76,14 @@ class StaffProfile(TimeStampedModel, models.Model): ) user = models.OneToOneField(USER, verbose_name=_("User"), on_delete=models.CASCADE) + supervisor = TreeForeignKey( + "self", + verbose_name=_("Supervisor"), + on_delete=models.PROTECT, + null=True, + blank=True, + related_name="children", + ) image = ImageField( upload_to="staff-images/", max_length=255, From c1f63c0a8d422bfd7a94ece5672b8f4860222cfd Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sun, 7 Jun 2020 22:45:59 +0300 Subject: [PATCH 30/83] Add migrations for supervisor --- .../migrations/0009_auto_20200607_2244.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 small_small_hr/migrations/0009_auto_20200607_2244.py 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..f60960e --- /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=0, editable=False), + preserve_default=False, + ), + migrations.AddField( + model_name="staffprofile", + name="lft", + field=models.PositiveIntegerField(default=0, editable=False), + preserve_default=False, + ), + migrations.AddField( + model_name="staffprofile", + name="rght", + field=models.PositiveIntegerField(default=0, 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, + ), + ] From 22e25a9c32dd4eb082198d5d287b66253b9a916b Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 12 Jun 2020 23:24:45 +0300 Subject: [PATCH 31/83] Add MPTTMeta --- small_small_hr/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index 673b3a2..5419f98 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -78,7 +78,7 @@ class StaffProfile(TimeStampedModel, MPTTModel): user = models.OneToOneField(USER, verbose_name=_("User"), on_delete=models.CASCADE) supervisor = TreeForeignKey( "self", - verbose_name=_("Supervisor"), + verbose_name=_("Manager"), on_delete=models.PROTECT, null=True, blank=True, @@ -149,6 +149,11 @@ class Meta: # pylint: disable=too-few-public-methods 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): """Return the staff member's name.""" # pylint: disable=no-member From 4d96f9ae5e59fa425d290335b9c77081f7dbe904 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 00:12:19 +0300 Subject: [PATCH 32/83] Add constants module --- small_small_hr/constants.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 small_small_hr/constants.py diff --git a/small_small_hr/constants.py b/small_small_hr/constants.py new file mode 100644 index 0000000..6b8660b --- /dev/null +++ b/small_small_hr/constants.py @@ -0,0 +1,2 @@ +"""Constants.""" +STAFF = "staff" From ac082b684723752e1aae5fb7b17e293e4b6905a5 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 00:12:37 +0300 Subject: [PATCH 33/83] Add reviews module which contains - set_staff_request_review_user - set_staff_request_reviewer --- small_small_hr/reviews.py | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 small_small_hr/reviews.py diff --git a/small_small_hr/reviews.py b/small_small_hr/reviews.py new file mode 100644 index 0000000..afaaff2 --- /dev/null +++ b/small_small_hr/reviews.py @@ -0,0 +1,44 @@ +"""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: + reviewer = Reviewer(review=review_obj, user=manager.user) + reviewer.save() # ensure save method is called + + hr_group = settings.SSHR_ADMIN_USER_GROUP_NAME + for user in User.objects.filter(groups__name=hr_group): + reviewer = Reviewer(review=review_obj, user=user) + reviewer.save() # ensure save method is called From a829a5491e9b126b480e598b8e225770d74275b2 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 00:13:31 +0300 Subject: [PATCH 34/83] Add SSHR_ADMIN_USER_GROUP_NAME --- small_small_hr/settings.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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] From 8e978ae5fe2da8b94f62de00f3c5c6563aa4bf29 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 00:14:09 +0300 Subject: [PATCH 35/83] Add model review options to BaseStaffRequest --- small_small_hr/models.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index 5419f98..0cbb6d6 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -1,6 +1,7 @@ """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 @@ -268,6 +269,16 @@ class BaseStaffRequest(TimeStampedModel, AbstractReview): 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 BaseStaffRequest.""" From 56bcf267936778b2f52136b612ac74da4b7bc1b6 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 00:16:01 +0300 Subject: [PATCH 36/83] Add test for the review process --- tests/test_process.py | 69 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/tests/test_process.py b/tests/test_process.py index aefc248..e0fffac 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -1,5 +1,6 @@ """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 @@ -8,10 +9,10 @@ import pytz from model_mommy import mommy from model_reviews.forms import PerformReview -from model_reviews.models import ModelReview +from model_reviews.models import ModelReview, Reviewer from small_small_hr.forms import ApplyLeaveForm -from small_small_hr.models import Leave +from small_small_hr.models import Leave, StaffProfile class TestProcess(TestCase): # pylint: disable=too-many-public-methods @@ -20,11 +21,20 @@ class TestProcess(TestCase): # pylint: disable=too-many-public-methods 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") + self.boss.groups.add(hr_group) + + @patch("model_reviews.emails.send_email") + def test_review_process(self, mock): # pylint: disable=too-many-locals + """Test the review process.""" + manager = mommy.make("auth.User", first_name="Jane", last_name="Ndoe") + manager_profile = mommy.make("small_small_hr.StaffProfile", user=manager) - def test_xxx(self): - """Test xxx.""" user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) + staffprofile.supervisor = manager_profile staffprofile.leave_days = 21 staffprofile.sick_days = 10 staffprofile.save() @@ -55,15 +65,58 @@ def test_xxx(self): self.assertTrue(Leave.PENDING, review.review_status) # check that email is sent once to the reviewer - # reviewer = Leave.none() - self.fail() + reviewer = Reviewer.objects.get(review=review, user=manager) + boss_reviewer = Reviewer.objects.get(review=review, user=self.boss) + + expected_calls = [ + call( + name="r1", + email="r1@example.com", + subject="New Request For Approval", + message="There has been a new request that needs your attention.", + obj=review, + cc_list=None, + template="generic", + template_path="model_reviews/email", + ), + call( + name="Jane Doe", + email="r2@example.com", + subject="New Request For Approval", + message="There has been a new request that needs your attention.", + obj=review, + cc_list=None, + template="generic", + template_path="model_reviews/email", + ), + ] + + mock.assert_has_calls(expected_calls) - # perform the review + # approve the review data = { "review": review.pk, - # "reviewer": reviewer.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) + + # 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) From 84075618bf87d9484531209edc38c719782f2e41 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 00:27:03 +0300 Subject: [PATCH 37/83] Test that emails are sent to reviewers --- tests/test_process.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/test_process.py b/tests/test_process.py index e0fffac..5576d46 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -23,16 +23,22 @@ def setUp(self): 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") + self.boss = mommy.make( + "auth.User", first_name="Boss", last_name="Lady", email="boss@example.com" + ) self.boss.groups.add(hr_group) @patch("model_reviews.emails.send_email") def test_review_process(self, mock): # pylint: disable=too-many-locals """Test the review process.""" - manager = mommy.make("auth.User", first_name="Jane", last_name="Ndoe") + manager = mommy.make( + "auth.User", first_name="Jane", last_name="Ndoe", email="jane@example.com" + ) manager_profile = mommy.make("small_small_hr.StaffProfile", user=manager) - user = mommy.make("auth.User", first_name="Bob", last_name="Ndoe") + user = mommy.make( + "auth.User", first_name="Bob", last_name="Ndoe", email="bob@example.com" + ) staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) staffprofile.supervisor = manager_profile staffprofile.leave_days = 21 @@ -70,8 +76,8 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals expected_calls = [ call( - name="r1", - email="r1@example.com", + 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, @@ -80,8 +86,8 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals template_path="model_reviews/email", ), call( - name="Jane Doe", - email="r2@example.com", + 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, From 8dfb8a97fc101f309004e69b2b10f1d7cabd8117 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 00:28:03 +0300 Subject: [PATCH 38/83] Test that emails are sent to reviewers --- tests/test_process.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_process.py b/tests/test_process.py index 5576d46..1e9bf60 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -113,6 +113,8 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals self.assertEqual(ModelReview.APPROVED, review.review_status) self.assertEqual(Leave.APPROVED, leave.review_status) + mock.assert_has_calls(expected_calls) + # boss rejects it data = { "review": review.pk, From 894e35b2c5e9c471e6b85a52ca82479f07fac440 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 00:52:59 +0300 Subject: [PATCH 39/83] Add test for review completion emails --- tests/test_process.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_process.py b/tests/test_process.py index 1e9bf60..c6b9431 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -113,6 +113,18 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals 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="generic", + template_path="model_reviews/email", + ) + ) mock.assert_has_calls(expected_calls) # boss rejects it @@ -128,3 +140,19 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals 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="generic", + template_path="model_reviews/email", + ) + ) + mock.assert_has_calls(expected_calls) + + self.assertEqual(4, mock.call_count) From 23d1855d7d94f31a3f1e21350f30b381e699421d Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 01:03:22 +0300 Subject: [PATCH 40/83] Ensure multiple reviewers are not created --- small_small_hr/reviews.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/small_small_hr/reviews.py b/small_small_hr/reviews.py index afaaff2..17b43b7 100644 --- a/small_small_hr/reviews.py +++ b/small_small_hr/reviews.py @@ -34,11 +34,17 @@ def set_staff_request_reviewer(review_obj: models.Model): if review_obj.user: staff_member = review_obj.user.staffprofile manager = staff_member.supervisor - if manager: + 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 = settings.SSHR_ADMIN_USER_GROUP_NAME for user in User.objects.filter(groups__name=hr_group): - reviewer = Reviewer(review=review_obj, user=user) - reviewer.save() # ensure save method is called + 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 From 4b3d9168a11ca511a356a135d94549543a948724 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 01:04:40 +0300 Subject: [PATCH 41/83] Make var name clearer --- small_small_hr/reviews.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/small_small_hr/reviews.py b/small_small_hr/reviews.py index 17b43b7..c4e2f70 100644 --- a/small_small_hr/reviews.py +++ b/small_small_hr/reviews.py @@ -43,8 +43,8 @@ def set_staff_request_reviewer(review_obj: models.Model): reviewer = Reviewer(review=review_obj, user=manager.user) reviewer.save() # ensure save method is called - hr_group = settings.SSHR_ADMIN_USER_GROUP_NAME - for user in User.objects.filter(groups__name=hr_group): + 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 From 330c289ba970192b6987f8bd07685fa2665a7524 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 12:35:26 +0300 Subject: [PATCH 42/83] Don't send an email after leave/overtime applicaiton form We no longer need to send an email when the form is completed, because emails are sent by the model_reviews package. --- small_small_hr/forms.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/small_small_hr/forms.py b/small_small_hr/forms.py index 40d3dd0..2ba0dfe 100644 --- a/small_small_hr/forms.py +++ b/small_small_hr/forms.py @@ -14,7 +14,6 @@ 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, @@ -227,7 +226,6 @@ def __init__(self, *args, **kwargs): def save(self, commit=True): """Save the form.""" overtime = super().save() - overtime_application_email(overtime_obj=overtime) return overtime @@ -397,7 +395,6 @@ def __init__(self, *args, **kwargs): def save(self, commit=True): """Save the form.""" leave = super().save() - leave_application_email(leave_obj=leave) return leave From 126146a72826942660e184d11cf2ca8ba5c5897d Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 12:45:28 +0300 Subject: [PATCH 43/83] Add email template constants --- small_small_hr/constants.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/small_small_hr/constants.py b/small_small_hr/constants.py index 6b8660b..effa60a 100644 --- a/small_small_hr/constants.py +++ b/small_small_hr/constants.py @@ -1,2 +1,5 @@ """Constants.""" STAFF = "staff" +OVERTIME_EMAIL_TEMPLATE = "overtime" +LEAVE_EMAIL_TEMPLATE = "leave" +EMAIL_TEMPLATE_PATH = "small_small_hr/email" From 1a0ce8eca6c18417436d7a79456ec16bf228c0f6 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 12:46:31 +0300 Subject: [PATCH 44/83] Add email template options to Leave and OverTime models --- small_small_hr/models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index 0cbb6d6..244179b 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -16,6 +16,11 @@ from private_storage.fields import PrivateFileField from sorl.thumbnail import ImageField +from small_small_hr.constants import ( + EMAIL_TEMPLATE_PATH, + LEAVE_EMAIL_TEMPLATE, + OVERTIME_EMAIL_TEMPLATE, +) from small_small_hr.managers import LeaveManager USER = settings.AUTH_USER_MODEL @@ -307,6 +312,10 @@ class Leave(BaseStaffRequest): objects = LeaveManager() + # MODEL REVIEW OPTIONS + email_template = LEAVE_EMAIL_TEMPLATE + email_template_path = EMAIL_TEMPLATE_PATH + class Meta: # pylint: disable=too-few-public-methods """Meta options for Leave.""" @@ -330,6 +339,10 @@ class OverTime(BaseStaffRequest): 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 = OVERTIME_EMAIL_TEMPLATE + email_template_path = EMAIL_TEMPLATE_PATH + class Meta: # pylint: disable=too-few-public-methods """Meta options for OverTime.""" From ddd0c08e6097625f63237e24f344ceb20629a3e1 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 12:46:58 +0300 Subject: [PATCH 45/83] Fix broken tests --- tests/test_process.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_process.py b/tests/test_process.py index c6b9431..dbd7178 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -82,8 +82,8 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals message="There has been a new request that needs your attention.", obj=review, cc_list=None, - template="generic", - template_path="model_reviews/email", + template="leave", + template_path="small_small_hr/email", ), call( name="Boss Lady", @@ -92,8 +92,8 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals message="There has been a new request that needs your attention.", obj=review, cc_list=None, - template="generic", - template_path="model_reviews/email", + template="leave", + template_path="small_small_hr/email", ), ] @@ -121,8 +121,8 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals 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="generic", - template_path="model_reviews/email", + template="leave", + template_path="small_small_hr/email", ) ) mock.assert_has_calls(expected_calls) @@ -149,8 +149,8 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals 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="generic", - template_path="model_reviews/email", + template="leave", + template_path="small_small_hr/email", ) ) mock.assert_has_calls(expected_calls) From e4929d5c2204a3895b16325529565096e92cfaf3 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 12:55:03 +0300 Subject: [PATCH 46/83] Add test for overtime review process --- tests/test_process.py | 137 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 4 deletions(-) diff --git a/tests/test_process.py b/tests/test_process.py index dbd7178..737b6ba 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -11,8 +11,8 @@ from model_reviews.forms import PerformReview from model_reviews.models import ModelReview, Reviewer -from small_small_hr.forms import ApplyLeaveForm -from small_small_hr.models import Leave, StaffProfile +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 @@ -29,8 +29,8 @@ def setUp(self): self.boss.groups.add(hr_group) @patch("model_reviews.emails.send_email") - def test_review_process(self, mock): # pylint: disable=too-many-locals - """Test the review process.""" + def test_leave_review_process(self, mock): # pylint: disable=too-many-locals + """Test the Leave review process.""" manager = mommy.make( "auth.User", first_name="Jane", last_name="Ndoe", email="jane@example.com" ) @@ -156,3 +156,132 @@ def test_review_process(self, mock): # pylint: disable=too-many-locals mock.assert_has_calls(expected_calls) self.assertEqual(4, mock.call_count) + + @patch("model_reviews.emails.send_email") + def test_overtime_review_process(self, mock): # pylint: disable=too-many-locals + """Test the OverTime review process.""" + manager = mommy.make( + "auth.User", first_name="Jane", last_name="Ndoe", email="jane@example.com" + ) + manager_profile = mommy.make("small_small_hr.StaffProfile", user=manager) + + user = mommy.make( + "auth.User", first_name="Bob", last_name="Ndoe", email="bob@example.com" + ) + staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) + 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", + 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", + 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", + 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", + template_path="small_small_hr/email", + ) + ) + mock.assert_has_calls(expected_calls) + + self.assertEqual(4, mock.call_count) From 6eccfb13ef3a1e8834220b3ec1a3fe5a9cd37f94 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 13:18:30 +0300 Subject: [PATCH 47/83] Add email templates for leave and overtime review completed --- .../small_small_hr/email/leave_completed_email_body.html | 7 +++++++ .../small_small_hr/email/leave_completed_email_body.txt | 9 +++++++++ .../email/leave_completed_email_subject.txt | 1 + .../email/overtime_completed_email_body.html | 7 +++++++ .../email/overtime_completed_email_body.txt | 9 +++++++++ .../email/overtime_completed_email_subjec.txt | 1 + 6 files changed, 34 insertions(+) create mode 100644 small_small_hr/templates/small_small_hr/email/leave_completed_email_body.html create mode 100644 small_small_hr/templates/small_small_hr/email/leave_completed_email_body.txt create mode 100644 small_small_hr/templates/small_small_hr/email/leave_completed_email_subject.txt create mode 100644 small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.html create mode 100644 small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.txt create mode 100644 small_small_hr/templates/small_small_hr/email/overtime_completed_email_subjec.txt 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..f0d3095 --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/leave_completed_email_body.html @@ -0,0 +1,7 @@ +Hello,

+{{message|linebreaks}} +

+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..a1e77af --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/leave_completed_email_body.txt @@ -0,0 +1,9 @@ +Hello, + +{{message}} + +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..ac892a3 --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/leave_completed_email_subject.txt @@ -0,0 +1 @@ +{{subject}} 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..f0d3095 --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.html @@ -0,0 +1,7 @@ +Hello,

+{{message|linebreaks}} +

+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..a1e77af --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_body.txt @@ -0,0 +1,9 @@ +Hello, + +{{message}} + +Thank you, + +{{SITE.name}} +------ +http://{{SITE.domain}} diff --git a/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subjec.txt b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subjec.txt new file mode 100644 index 0000000..ac892a3 --- /dev/null +++ b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subjec.txt @@ -0,0 +1 @@ +{{subject}} From c6e880d5a38a55180d037bc823f78af9d0727e90 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 13:19:00 +0300 Subject: [PATCH 48/83] Separate constants for application and completed reviews --- small_small_hr/constants.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/small_small_hr/constants.py b/small_small_hr/constants.py index effa60a..bf97072 100644 --- a/small_small_hr/constants.py +++ b/small_small_hr/constants.py @@ -1,5 +1,7 @@ """Constants.""" STAFF = "staff" -OVERTIME_EMAIL_TEMPLATE = "overtime" -LEAVE_EMAIL_TEMPLATE = "leave" +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" From 184be08608edc9e83a351bc3ebb55033270c02f2 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 13:19:50 +0300 Subject: [PATCH 49/83] Configure which functions are used to send Leave and Overtime emails --- small_small_hr/emails.py | 75 +++++++++++++++++++++++++++++++++++++++- small_small_hr/models.py | 24 +++++++++---- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/small_small_hr/emails.py b/small_small_hr/emails.py index dd43263..180d2e6 100644 --- a/small_small_hr/emails.py +++ b/small_small_hr/emails.py @@ -2,11 +2,84 @@ from django.conf import settings from django.utils.translation import ugettext as _ -from model_reviews.emails import send_email +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, +) from small_small_hr.models import Leave, OverTime +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=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 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=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 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, + ) + + def leave_application_email(leave_obj: Leave): """Send an email to admins when a leave application is made.""" msg = getattr( diff --git a/small_small_hr/models.py b/small_small_hr/models.py index 244179b..46841cc 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -16,11 +16,7 @@ from private_storage.fields import PrivateFileField from sorl.thumbnail import ImageField -from small_small_hr.constants import ( - EMAIL_TEMPLATE_PATH, - LEAVE_EMAIL_TEMPLATE, - OVERTIME_EMAIL_TEMPLATE, -) +from small_small_hr.constants import EMAIL_TEMPLATE_PATH from small_small_hr.managers import LeaveManager USER = settings.AUTH_USER_MODEL @@ -313,8 +309,15 @@ class Leave(BaseStaffRequest): objects = LeaveManager() # MODEL REVIEW OPTIONS - email_template = LEAVE_EMAIL_TEMPLATE 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.""" @@ -340,8 +343,15 @@ class OverTime(BaseStaffRequest): end = models.TimeField(_("End"), auto_now=False, auto_now_add=False) # MODEL REVIEW OPTIONS - email_template = OVERTIME_EMAIL_TEMPLATE 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.""" From 106389a36a5fd9205630f8ea0c56cbb21b1b0b97 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 13:20:44 +0300 Subject: [PATCH 50/83] Fix broken tests --- tests/test_process.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_process.py b/tests/test_process.py index 737b6ba..0f22b3c 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -28,7 +28,7 @@ def setUp(self): ) self.boss.groups.add(hr_group) - @patch("model_reviews.emails.send_email") + @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 = mommy.make( @@ -82,7 +82,7 @@ def test_leave_review_process(self, mock): # pylint: disable=too-many-locals message="There has been a new request that needs your attention.", obj=review, cc_list=None, - template="leave", + template="leave_application", template_path="small_small_hr/email", ), call( @@ -92,7 +92,7 @@ def test_leave_review_process(self, mock): # pylint: disable=too-many-locals message="There has been a new request that needs your attention.", obj=review, cc_list=None, - template="leave", + template="leave_application", template_path="small_small_hr/email", ), ] @@ -121,7 +121,7 @@ def test_leave_review_process(self, mock): # pylint: disable=too-many-locals 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", + template="leave_completed", template_path="small_small_hr/email", ) ) @@ -149,7 +149,7 @@ def test_leave_review_process(self, mock): # pylint: disable=too-many-locals 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", + template="leave_completed", template_path="small_small_hr/email", ) ) @@ -157,7 +157,7 @@ def test_leave_review_process(self, mock): # pylint: disable=too-many-locals self.assertEqual(4, mock.call_count) - @patch("model_reviews.emails.send_email") + @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 = mommy.make( @@ -211,7 +211,7 @@ def test_overtime_review_process(self, mock): # pylint: disable=too-many-locals message="There has been a new request that needs your attention.", obj=review, cc_list=None, - template="overtime", + template="overtime_application", template_path="small_small_hr/email", ), call( @@ -221,7 +221,7 @@ def test_overtime_review_process(self, mock): # pylint: disable=too-many-locals message="There has been a new request that needs your attention.", obj=review, cc_list=None, - template="overtime", + template="overtime_application", template_path="small_small_hr/email", ), ] @@ -250,7 +250,7 @@ def test_overtime_review_process(self, mock): # pylint: disable=too-many-locals 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", + template="overtime_completed", template_path="small_small_hr/email", ) ) @@ -278,7 +278,7 @@ def test_overtime_review_process(self, mock): # pylint: disable=too-many-locals 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", + template="overtime_completed", template_path="small_small_hr/email", ) ) From 634082ee35f6a1563296a1091d0dbb981d7bdac8 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 13:26:13 +0300 Subject: [PATCH 51/83] Remove deprecated functions --- small_small_hr/emails.py | 112 --------------------------------------- 1 file changed, 112 deletions(-) diff --git a/small_small_hr/emails.py b/small_small_hr/emails.py index 180d2e6..d2b991f 100644 --- a/small_small_hr/emails.py +++ b/small_small_hr/emails.py @@ -1,7 +1,4 @@ """Emails module for scam app.""" -from django.conf import settings -from django.utils.translation import ugettext as _ - from model_reviews.emails import get_display_name, send_email from model_reviews.models import ModelReview, Reviewer @@ -11,7 +8,6 @@ OVERTIME_APPLICATION_EMAIL_TEMPLATE, OVERTIME_COMPLETED_EMAIL_TEMPLATE, ) -from small_small_hr.models import Leave, OverTime def send_request_for_leave_review(reviewer: Reviewer): @@ -78,111 +74,3 @@ def send_overtime_review_complete_notice(review_obj: ModelReview): template=OVERTIME_COMPLETED_EMAIL_TEMPLATE, template_path=source.email_template_path, ) - - -def leave_application_email(leave_obj: Leave): - """Send 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: - send_email( - name=settings.SSHR_ADMIN_NAME, - email=admin_email, - subject=subj, - message=msg, - obj=leave_obj, - template="leave_application", - template_path="small_small_hr/email", - ) - - -def leave_processed_email(leave_obj: Leave): - """Send 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_review_status_display()}. Log in for more info." - ), - ) - subj = getattr( - settings, - "SSHR_LEAVE_PROCESSED_EMAIL_SUBJ", - _("Your leave application has been processed"), - ) - - 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, - template_path="small_small_hr/email", - ) - - -def overtime_application_email(overtime_obj: OverTime): - """Send 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", - template_path="small_small_hr/email", - ) - - -def overtime_processed_email(overtime_obj: OverTime): - """Send 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_review_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, - template_path="small_small_hr/email", - ) From f29267ecd90253a2292d86ad32ab062bea9dd88a Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 13:27:17 +0300 Subject: [PATCH 52/83] Temporarily disable email tests --- tests/test_emails.py | 272 +++++++++++++++++++++---------------------- 1 file changed, 136 insertions(+), 136 deletions(-) diff --git a/tests/test_emails.py b/tests/test_emails.py index bf4137b..7cccd67 100644 --- a/tests/test_emails.py +++ b/tests/test_emails.py @@ -1,136 +1,136 @@ -"""Module to test small_small_hr Emails.""" -from datetime import datetime -from unittest.mock import patch - -from django.conf import settings -from django.test import TestCase, override_settings - -import pytz -from model_mommy import mommy - -from small_small_hr.emails import ( - leave_application_email, - leave_processed_email, - overtime_application_email, - overtime_processed_email, -) -from small_small_hr.models import Leave, OverTime - - -@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", -) -class TestEmails(TestCase): - """Test class for emails.""" - - def setUp(self): - """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, - review_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 # pylint: disable=line-too-long - obj=leave, - template="leave_application", - template_path="small_small_hr/email", - ) - - @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, - review_status=Leave.APPROVED, - ) - - leave_processed_email(leave) - - 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 # pylint: disable=line-too-long - obj=leave, - cc_list=["hr@example.com"], - template_path="small_small_hr/email", - ) - - @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, - review_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 # pylint: disable=line-too-long - obj=overtime, - template="overtime_application", - template_path="small_small_hr/email", - ) - - @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, - review_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 # pylint: disable=line-too-long - obj=overtime, - cc_list=["ot@example.com"], - template_path="small_small_hr/email", - ) +# """Module to test small_small_hr Emails.""" +# from datetime import datetime +# from unittest.mock import patch + +# from django.conf import settings +# from django.test import TestCase, override_settings + +# import pytz +# from model_mommy import mommy + +# from small_small_hr.emails import ( +# leave_application_email, +# leave_processed_email, +# overtime_application_email, +# overtime_processed_email, +# ) +# from small_small_hr.models import Leave, OverTime + + +# @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", +# ) +# class TestEmails(TestCase): +# """Test class for emails.""" + +# def setUp(self): +# """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, +# review_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 # pylint: disable=line-too-long +# obj=leave, +# template="leave_application", +# template_path="small_small_hr/email", +# ) + +# @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, +# review_status=Leave.APPROVED, +# ) + +# leave_processed_email(leave) + +# 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 # pylint: disable=line-too-long +# obj=leave, +# cc_list=["hr@example.com"], +# template_path="small_small_hr/email", +# ) + +# @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, +# review_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 # pylint: disable=line-too-long +# obj=overtime, +# template="overtime_application", +# template_path="small_small_hr/email", +# ) + +# @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, +# review_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 # pylint: disable=line-too-long +# obj=overtime, +# cc_list=["ot@example.com"], +# template_path="small_small_hr/email", +# ) From e5d0a678a2ea822668f8a9aa86f24da1a362c250 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Sat, 13 Jun 2020 13:31:06 +0300 Subject: [PATCH 53/83] Fix broken test --- tests/test_forms.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/tests/test_forms.py b/tests/test_forms.py index 921fa26..819b81f 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -2,7 +2,6 @@ # pylint: disable=too-many-lines import os from datetime import date, datetime, timedelta -from unittest.mock import patch from django.conf import settings from django.contrib.auth.models import AnonymousUser @@ -121,8 +120,7 @@ def test_freeday_form(self): "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): + 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) @@ -155,7 +153,6 @@ def test_overtime_form_apply(self, mock): ) self.assertEqual("Extra work", overtime.review_reason) self.assertEqual(OverTime.PENDING, overtime.review_status) - mock.assert_called_with(overtime_obj=overtime) def test_overtime_form_apply_no_overlap(self): """Test no overlaps on OverTime.""" @@ -309,8 +306,7 @@ def test_overtime_form_start_end(self): 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): + 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) @@ -352,7 +348,6 @@ def test_leaveform_apply(self, mock): 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) - mock.assert_called_with(leave_obj=leave) @override_settings( SSHR_DEFAULT_TIME=7, @@ -367,8 +362,7 @@ def test_leaveform_apply(self, mock): 7: 1, # Sunday }, ) - @patch("small_small_hr.forms.leave_application_email") - def test_leave_oversubscribe(self, mock): + 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) @@ -418,7 +412,6 @@ def test_leave_oversubscribe(self, mock): 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) - mock.assert_called_with(leave_obj=leave) self.assertEqual( 40, get_taken_leave_days( @@ -440,11 +433,8 @@ def test_leave_oversubscribe(self, mock): 7: 1, # Sunday }, ) - @patch("small_small_hr.forms.leave_application_email") - def test_leave_oversubscribe_off(self, mock): + def test_leave_oversubscribe_off(self): """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) staffprofile.leave_days = 21 @@ -488,8 +478,7 @@ def test_leave_oversubscribe_off(self, mock): ) @override_settings(SSHR_DEFAULT_TIME=7) - @patch("small_small_hr.forms.leave_application_email") - def test_one_day_leave(self, mock): + 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) @@ -531,7 +520,6 @@ def test_one_day_leave(self, mock): 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) - mock.assert_called_with(leave_obj=leave) self.assertEqual( 1, get_taken_leave_days( From fd9d2aba7eaeeff6595fe8768a9d64d185c52926 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 21:30:18 +0300 Subject: [PATCH 54/83] Install snapshottest --- .pylintrc | 2 +- requirements/dev.in | 1 + requirements/dev.txt | 28 ++++++++++-------- setup.cfg | 2 +- tests/settings.py | 69 +++++++++++++++++++++++--------------------- 5 files changed, 55 insertions(+), 47 deletions(-) diff --git a/.pylintrc b/.pylintrc index b611e76..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. diff --git a/requirements/dev.in b/requirements/dev.in index 4107e7c..c8ff292 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -20,3 +20,4 @@ black pre-commit mypy tblib +snapshottest diff --git a/requirements/dev.txt b/requirements/dev.txt index ec134a8..ded418c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -6,12 +6,12 @@ # appdirs==1.4.4 # via black, virtualenv -asgiref==3.2.7 # via django -astroid==2.4.1 # via pylint +asgiref==3.2.8 # 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 @@ -28,11 +28,12 @@ django-phonenumber-field==4.0.0 django-private-storage==2.2.2 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 +flake8==3.8.3 identify==1.4.19 # 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 +importlib-resources==2.0.1 # via pre-commit, virtualenv ipdb==0.13.2 ipython-genutils==0.2.0 # via traitlets ipython==7.15.0 # via ipdb @@ -53,35 +54,38 @@ phonenumberslite==8.12.5 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 pytz==2020.1 # via babel, django pyyaml==5.3.1 # via pre-commit -regex==2020.6.7 # via black -six==1.15.0 # via astroid, django-braces, django-contrib-comments, packaging, tox, traitlets, virtualenv +regex==2020.6.8 # via black +six==1.15.0 # via astroid, django-braces, django-contrib-comments, packaging, 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.23 # via pre-commit, tox voluptuous==0.11.7 -wcwidth==0.2.3 # via prompt-toolkit +wasmer==0.4.1 # via fastdiff +wcwidth==0.2.4 # 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/tests/settings.py b/tests/settings.py index c0ea1b2..172c8c4 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -8,31 +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', - 'model_reviews', + "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", } } @@ -48,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 From 1c7578f3c619a8fc9b5c7a9ed468d3c55068350b Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 21:31:33 +0300 Subject: [PATCH 55/83] Install freezegun --- requirements/dev.in | 1 + requirements/dev.txt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/requirements/dev.in b/requirements/dev.in index c8ff292..9b989f4 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -21,3 +21,4 @@ pre-commit mypy tblib snapshottest +freezegun diff --git a/requirements/dev.txt b/requirements/dev.txt index ded418c..33fd7a0 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -31,6 +31,7 @@ djangorestframework==3.11.0 fastdiff==0.2.0 # via snapshottest filelock==3.0.12 # via tox, virtualenv flake8==3.8.3 +freezegun==0.3.15 identify==1.4.19 # via pre-commit importlib-metadata==1.6.1 # via flake8, importlib-resources, pluggy, pre-commit, tox, virtualenv importlib-resources==2.0.1 # via pre-commit, virtualenv @@ -67,10 +68,11 @@ pylint-django==2.0.15 pylint-plugin-utils==0.6 # via pylint-django 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.6.8 # via black -six==1.15.0 # via astroid, django-braces, django-contrib-comments, packaging, snapshottest, tox, traitlets, virtualenv +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 From 9e9b3a180786965a4dc7e5acf87e7484ae4ea6f6 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 21:54:50 +0300 Subject: [PATCH 56/83] Exclude snapshots from pre-commit --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) 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 From 77021521e747dffc782edfa33a55453457d94bf1 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 21:57:27 +0300 Subject: [PATCH 57/83] Add model methods for convenience - Get the current year easily - Get duration easily --- small_small_hr/models.py | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/small_small_hr/models.py b/small_small_hr/models.py index 46841cc..17fbade 100644 --- a/small_small_hr/models.py +++ b/small_small_hr/models.py @@ -8,6 +8,7 @@ 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 @@ -161,45 +162,50 @@ def get_name(self): # pylint: disable=no-member return f"{self.user.first_name} {self.user.last_name}" - def get_approved_leave_days(self, year: int = datetime.today().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): + 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): + 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): + 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) @@ -332,6 +338,15 @@ def __str__(self): # pylint: disable=no-member 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.""" @@ -372,6 +387,11 @@ def get_duration(self): 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): """ From f1f6328c6427cedbc553289048929d680fed4290 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 21:58:51 +0300 Subject: [PATCH 58/83] Update leave application email templates --- .../email/leave_application_email_body.html | 7 +++++-- .../email/leave_application_email_body.txt | 9 +++++++-- .../email/leave_application_email_subject.txt | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) 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..db6e17d 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..e44bf63 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" }} From 8a2025ad83d82919d0345dd568b25b5a880ac5af Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 21:59:15 +0300 Subject: [PATCH 59/83] Add urls for tests --- tests/urls.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/urls.py 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")), +] From 9ee4cf3c69935ada1347ad3198fdf34a48ce1ee7 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 21:59:31 +0300 Subject: [PATCH 60/83] Add snapshot tests --- tests/snapshots/__init__.py | 0 tests/snapshots/snap_test_emails.py | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/snapshots/__init__.py create mode 100644 tests/snapshots/snap_test_emails.py 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..a821243 --- /dev/null +++ b/tests/snapshots/snap_test_emails.py @@ -0,0 +1,26 @@ +# -*- 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/1 + +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/1

Thank you,
example.com
------
http://example.com' From 7e142631eff9987af1e059882ce4184517cc7eb6 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 22:02:05 +0300 Subject: [PATCH 61/83] Add tests for leave application emails --- tests/test_emails.py | 207 +++++++++++++++---------------------------- 1 file changed, 71 insertions(+), 136 deletions(-) diff --git a/tests/test_emails.py b/tests/test_emails.py index 7cccd67..8e4e061 100644 --- a/tests/test_emails.py +++ b/tests/test_emails.py @@ -1,136 +1,71 @@ -# """Module to test small_small_hr Emails.""" -# from datetime import datetime -# from unittest.mock import patch - -# from django.conf import settings -# from django.test import TestCase, override_settings - -# import pytz -# from model_mommy import mommy - -# from small_small_hr.emails import ( -# leave_application_email, -# leave_processed_email, -# overtime_application_email, -# overtime_processed_email, -# ) -# from small_small_hr.models import Leave, OverTime - - -# @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", -# ) -# class TestEmails(TestCase): -# """Test class for emails.""" - -# def setUp(self): -# """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, -# review_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 # pylint: disable=line-too-long -# obj=leave, -# template="leave_application", -# template_path="small_small_hr/email", -# ) - -# @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, -# review_status=Leave.APPROVED, -# ) - -# leave_processed_email(leave) - -# 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 # pylint: disable=line-too-long -# obj=leave, -# cc_list=["hr@example.com"], -# template_path="small_small_hr/email", -# ) - -# @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, -# review_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 # pylint: disable=line-too-long -# obj=overtime, -# template="overtime_application", -# template_path="small_small_hr/email", -# ) - -# @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, -# review_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 # pylint: disable=line-too-long -# obj=overtime, -# cc_list=["ot@example.com"], -# template_path="small_small_hr/email", -# ) +"""Module to test small_small_hr Emails.""" +from datetime import datetime + +from django.conf import settings +from django.core import mail +from django.test import override_settings + +import pytz +from freezegun import freeze_time +from model_mommy import mommy +from snapshottest.django import TestCase + +from small_small_hr.forms import ApplyLeaveForm +from small_small_hr.models import Leave, StaffProfile +from small_small_hr.utils import create_annual_leave + + +@override_settings(ROOT_URLCONF="tests.urls") +class TestEmails(TestCase): + """Test class for emails.""" + + maxDiff = None + + def setUp(self): + """Set up.""" + self.user = mommy.make( + "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() + + create_annual_leave(self.staffprofile, 2017, Leave.REGULAR) + + StaffProfile.objects.rebuild() + + 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" + ) + 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()) + form.save() + 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]) + # import ipdb; ipdb.set_trace() + # leave = form.save() + # obj_type = ContentType.objects.get_for_model(leave) + # review = ModelReview.objects.get(content_type=obj_type, object_id=leave.id) + # reviewer = Reviewer.objects.get(review=review, user=self.boss) From 519e0b47b6a0bbb26f284a39d0433fb5410e1fd4 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 22:24:35 +0300 Subject: [PATCH 62/83] Add templates for leave application completed --- .../email/leave_completed_email_body.html | 8 ++++++-- .../email/leave_completed_email_body.txt | 10 ++++++++-- .../email/leave_completed_email_subject.txt | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) 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 index f0d3095..a2e32b4 100644 --- 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 @@ -1,5 +1,9 @@ -Hello,

-{{message|linebreaks}} +{{ 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}}
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 index a1e77af..955f22f 100644 --- 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 @@ -1,9 +1,15 @@ -Hello, +{{ object.content_object.staff.get_name }}, -{{message}} +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 index ac892a3..0a0819f 100644 --- 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 @@ -1 +1 @@ -{{subject}} +Your time off request of {{ object.content_object.start|date:"d M" }} - {{ object.content_object.end|date:"d M" }} has a response From fab6ace5f4d93d333c97fbc228f843aa812814a0 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Mon, 15 Jun 2020 22:25:14 +0300 Subject: [PATCH 63/83] Add test for leave application completed --- tests/snapshots/snap_test_emails.py | 38 +++++++++++++++++++++++ tests/test_emails.py | 47 ++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/tests/snapshots/snap_test_emails.py b/tests/snapshots/snap_test_emails.py index a821243..6c32392 100644 --- a/tests/snapshots/snap_test_emails.py +++ b/tests/snapshots/snap_test_emails.py @@ -24,3 +24,41 @@ ''' 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/1

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' diff --git a/tests/test_emails.py b/tests/test_emails.py index 8e4e061..2a60185 100644 --- a/tests/test_emails.py +++ b/tests/test_emails.py @@ -2,12 +2,15 @@ from datetime import datetime from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.core import mail 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.forms import ApplyLeaveForm @@ -64,8 +67,42 @@ def test_leave_emails(self): self.assertEqual(["Mother Hen "], mail.outbox[0].to) self.assertMatchSnapshot(mail.outbox[0].body) self.assertMatchSnapshot(mail.outbox[0].alternatives[0][0]) - # import ipdb; ipdb.set_trace() - # leave = form.save() - # obj_type = ContentType.objects.get_for_model(leave) - # review = ModelReview.objects.get(content_type=obj_type, object_id=leave.id) - # reviewer = Reviewer.objects.get(review=review, user=self.boss) + + leave = form.save() + obj_type = ContentType.objects.get_for_model(leave) + review = ModelReview.objects.get(content_type=obj_type, object_id=leave.id) + reviewer = Reviewer.objects.get(review=review, user=self.boss) + + # 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() + self.assertEqual( + "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]) + + # 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 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]) From d486a8a0f328dfae0ff6acee2b49f39976270f52 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 22:50:42 +0300 Subject: [PATCH 64/83] Add more contants --- small_small_hr/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/small_small_hr/constants.py b/small_small_hr/constants.py index bf97072..f229471 100644 --- a/small_small_hr/constants.py +++ b/small_small_hr/constants.py @@ -1,4 +1,6 @@ """Constants.""" +HOURS = "hours" +MINUTES = "minutes" STAFF = "staff" OVERTIME_APPLICATION_EMAIL_TEMPLATE = "overtime_application" OVERTIME_COMPLETED_EMAIL_TEMPLATE = "overtime_completed" From cea36ed4cc8eac865bbf298043b7539bf556e0ed Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 22:51:48 +0300 Subject: [PATCH 65/83] Add overtime_duration template filter --- small_small_hr/templatetags/__init__.py | 0 small_small_hr/templatetags/small_small_hr.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 small_small_hr/templatetags/__init__.py create mode 100644 small_small_hr/templatetags/small_small_hr.py 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)}" From d317fb7ab0491ca00418536cd36ed1ce82489368 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 22:53:18 +0300 Subject: [PATCH 66/83] Add correct content for overtime complated templates --- .../email/overtime_completed_email_body.html | 8 +++++--- .../email/overtime_completed_email_body.txt | 8 ++++++-- .../email/overtime_completed_email_subjec.txt | 1 - .../email/overtime_completed_email_subject.txt | 1 + 4 files changed, 12 insertions(+), 6 deletions(-) delete mode 100644 small_small_hr/templates/small_small_hr/email/overtime_completed_email_subjec.txt create mode 100644 small_small_hr/templates/small_small_hr/email/overtime_completed_email_subject.txt 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 index f0d3095..571349c 100644 --- 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 @@ -1,6 +1,8 @@ -Hello,

-{{message|linebreaks}} -

+{% 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}}
------
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 index a1e77af..4b90eca 100644 --- 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 @@ -1,6 +1,10 @@ -Hello, +{% load small_small_hr %}{{ object.content_object.staff.get_name }}, -{{message}} +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, diff --git a/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subjec.txt b/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subjec.txt deleted file mode 100644 index ac892a3..0000000 --- a/small_small_hr/templates/small_small_hr/email/overtime_completed_email_subjec.txt +++ /dev/null @@ -1 +0,0 @@ -{{subject}} 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 From 2a4c94829f448da949d1a6d99a5127d73fa1d737 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 22:53:47 +0300 Subject: [PATCH 67/83] Add correct content for overtime application templates --- .../email/overtime_application_email_body.html | 6 ++++-- .../email/overtime_application_email_body.txt | 8 ++++++-- .../email/overtime_application_email_subject.txt | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) 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" }} From 3206929df2f686e3f2807f9ae5c6d5c2628b2a97 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 22:54:14 +0300 Subject: [PATCH 68/83] Remove fullstop --- .../small_small_hr/email/leave_application_email_body.html | 4 ++-- .../small_small_hr/email/leave_application_email_body.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 db6e17d..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,6 +1,6 @@ {{ 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" }}.
+{{ 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 }}

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 e44bf63..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,7 +1,7 @@ {{ 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" }}. +{{ 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 }} From 60695209c933d789801f2ded4fa3386b3d881e6b Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 22:54:41 +0300 Subject: [PATCH 69/83] Add tests for overtime emails - Add overtime emails content tests - Ensure review objects pk is hard coded so that it does not change in snapshots --- tests/test_emails.py | 95 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 7 deletions(-) diff --git a/tests/test_emails.py b/tests/test_emails.py index 2a60185..e5026e7 100644 --- a/tests/test_emails.py +++ b/tests/test_emails.py @@ -13,7 +13,7 @@ from model_reviews.models import ModelReview, Reviewer from snapshottest.django import TestCase -from small_small_hr.forms import ApplyLeaveForm +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 @@ -60,7 +60,21 @@ def test_leave_emails(self): } form = ApplyLeaveForm(data=data) self.assertTrue(form.is_valid()) - form.save() + 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) + self.assertEqual( "Mosh Pitt requested time off on 05 Jun to 10 Jun", mail.outbox[0].subject ) @@ -68,11 +82,6 @@ def test_leave_emails(self): self.assertMatchSnapshot(mail.outbox[0].body) self.assertMatchSnapshot(mail.outbox[0].alternatives[0][0]) - leave = form.save() - obj_type = ContentType.objects.get_for_model(leave) - review = ModelReview.objects.get(content_type=obj_type, object_id=leave.id) - reviewer = Reviewer.objects.get(review=review, user=self.boss) - # approve the review data = { "review": review.pk, @@ -106,3 +115,75 @@ def test_leave_emails(self): self.assertEqual(["Mosh Pitt "], mail.outbox[2].to) self.assertMatchSnapshot(mail.outbox[2].body) self.assertMatchSnapshot(mail.outbox[2].alternatives[0][0]) + + def test_overtime_emails(self): + """Test Overtime emails.""" + # apply for overtime + start = datetime( + 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)) + + data = { + "staff": self.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() + + 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) + + 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]) + + # 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]) + + # 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]) From 8d81be1cc67bc7e1f217a9d82f4c1baa0421c397 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 22:55:48 +0300 Subject: [PATCH 70/83] Add snapshot tests --- tests/snapshots/snap_test_emails.py | 59 +++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/tests/snapshots/snap_test_emails.py b/tests/snapshots/snap_test_emails.py index 6c32392..aa64913 100644 --- a/tests/snapshots/snap_test_emails.py +++ b/tests/snapshots/snap_test_emails.py @@ -9,11 +9,11 @@ snapshots['TestEmails::test_leave_emails 1'] = '''Mosh Pitt requested time off: -5 days of Regular Leave. -Mon, 05 Jun 2017 - Sat, 10 Jun 2017. +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/1 +Please log in to process the above: http://example.com/reviews/1338 Thank you, @@ -23,7 +23,24 @@ 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/1

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, @@ -62,3 +79,37 @@ ''' 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' From f2613f407109430550513f2fa3c93be176df8ef3 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 23:29:19 +0300 Subject: [PATCH 71/83] Set mptt defaults to 1 All these fields have DB constraints that check they are >= 1 --- small_small_hr/migrations/0009_auto_20200607_2244.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/small_small_hr/migrations/0009_auto_20200607_2244.py b/small_small_hr/migrations/0009_auto_20200607_2244.py index f60960e..14b0e95 100644 --- a/small_small_hr/migrations/0009_auto_20200607_2244.py +++ b/small_small_hr/migrations/0009_auto_20200607_2244.py @@ -16,19 +16,19 @@ class Migration(migrations.Migration): migrations.AddField( model_name="staffprofile", name="level", - field=models.PositiveIntegerField(default=0, editable=False), + field=models.PositiveIntegerField(default=1, editable=False), preserve_default=False, ), migrations.AddField( model_name="staffprofile", name="lft", - field=models.PositiveIntegerField(default=0, editable=False), + field=models.PositiveIntegerField(default=1, editable=False), preserve_default=False, ), migrations.AddField( model_name="staffprofile", name="rght", - field=models.PositiveIntegerField(default=0, editable=False), + field=models.PositiveIntegerField(default=1, editable=False), preserve_default=False, ), migrations.AddField( From 0433efa01eea09b243365fbaa8b033555b7fc9be Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 16 Jun 2020 23:35:06 +0300 Subject: [PATCH 72/83] Add mptt_callback --- small_small_hr/apps.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) 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)) From 83e5d8c853eba8782f0f173e30aa24eb659cdf4f Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 19 Jun 2020 22:30:19 +0300 Subject: [PATCH 73/83] Redo migrations This is so that django-mptt behaves better. We still have the liverty to do this because we have not yet hit version 1.0 :) --- small_small_hr/migrations/0001_initial.py | 769 +++++++++++------- .../migrations/0002_auto_20180722_1808.py | 36 - .../migrations/0003_staffdocument_public.py | 21 - .../migrations/0004_auto_20180725_2127.py | 19 - .../migrations/0005_auto_20181115_2112.py | 28 - .../migrations/0006_auto_20181209_0108.py | 77 -- .../migrations/0007_auto_20190625_2111.py | 34 - .../migrations/0008_auto_20200607_1537.py | 65 -- .../migrations/0009_auto_20200607_2244.py | 52 -- 9 files changed, 474 insertions(+), 627 deletions(-) delete mode 100644 small_small_hr/migrations/0002_auto_20180722_1808.py delete mode 100644 small_small_hr/migrations/0003_staffdocument_public.py delete mode 100644 small_small_hr/migrations/0004_auto_20180725_2127.py delete mode 100644 small_small_hr/migrations/0005_auto_20181115_2112.py delete mode 100644 small_small_hr/migrations/0006_auto_20181209_0108.py delete mode 100644 small_small_hr/migrations/0007_auto_20190625_2111.py delete mode 100644 small_small_hr/migrations/0008_auto_20200607_1537.py delete mode 100644 small_small_hr/migrations/0009_auto_20200607_2244.py diff --git a/small_small_hr/migrations/0001_initial.py b/small_small_hr/migrations/0001_initial.py index 01ac00a..b70f612 100644 --- a/small_small_hr/migrations/0001_initial.py +++ b/small_small_hr/migrations/0001_initial.py @@ -1,12 +1,15 @@ -# Generated by Django 2.0.6 on 2018-06-19 19:22 - -from django.conf import settings +# Generated by Django 3.0.7 on 2020-06-19 19:28 +# pylint: disable=invalid-name,missing-module-docstring,missing-class-docstring import django.contrib.postgres.fields.jsonb -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + +import mptt.fields import phonenumber_field.modelfields import private_storage.fields import private_storage.storage.files +import sorl.thumbnail.fields class Migration(migrations.Migration): @@ -19,331 +22,507 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='AnnualLeave', + name="FreeDay", fields=[ - ('id', - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID')), - ('created', - models.DateTimeField( - auto_now_add=True, verbose_name='Created')), - ('modified', - models.DateTimeField(auto_now=True, verbose_name='Modified')), - ('year', - models.PositiveIntegerField( - choices=[(2017, 2017), (2018, 2018), (2019, 2019), - (2020, 2020), (2021, 2021), (2022, 2022)], - db_index=True, - default=2018, - verbose_name='Year')), - ('leave_type', - models.CharField( - choices=[('1', 'Sick Leave'), ('2', 'Regular Leave')], - db_index=True, - max_length=1, - verbose_name='Type')), - ('allowed_days', - models.PositiveIntegerField( - blank=True, - default=21, - help_text='Number of leave days allowed in a year.', - verbose_name='Allowed Leave days')), - ('carried_over_days', - models.PositiveIntegerField( - blank=True, - default=0, - help_text= - 'Number of leave days carried over into this year.', - verbose_name='Carried Over Leave days')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="Name")), + ("date", models.DateField(unique=True, verbose_name="Date")), ], options={ - 'verbose_name': 'Annual Leave', - 'verbose_name_plural': 'Annual Leave', - 'ordering': ['year', 'leave_type', 'staff'], + "verbose_name": "Free Day", + "verbose_name_plural": "Free Days", + "ordering": ["-date"], }, ), migrations.CreateModel( - name='Leave', + name="Role", fields=[ - ('id', - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID')), - ('created', - models.DateTimeField( - auto_now_add=True, verbose_name='Created')), - ('modified', - models.DateTimeField(auto_now=True, verbose_name='Modified')), - ('start', models.DateTimeField(verbose_name='Start Date')), - ('end', models.DateTimeField(verbose_name='End Date')), - ('reason', - models.TextField( - blank=True, default='', verbose_name='Reason')), - ('status', - models.CharField( - blank=True, - choices=[('1', 'Approved'), ('3', 'Pending'), - ('2', 'Rejected')], - db_index=True, - default='3', - max_length=1, - verbose_name='Status')), - ('comments', - models.TextField( - blank=True, default='', verbose_name='Comments')), - ('leave_type', - models.CharField( - blank=True, - choices=[('1', 'Sick Leave'), ('2', 'Regular Leave')], - db_index=True, - default='2', - max_length=1, - verbose_name='Type')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField(auto_now_add=True, verbose_name="Created"), + ), + ( + "modified", + models.DateTimeField(auto_now=True, verbose_name="Modified"), + ), + ("name", models.CharField(max_length=255, verbose_name="Name")), + ( + "description", + models.TextField( + blank=True, default="", verbose_name="Description" + ), + ), ], options={ - 'verbose_name': 'Leave', - 'verbose_name_plural': 'Leave', - 'ordering': ['staff', 'start'], - 'abstract': False, + "verbose_name": "Role", + "verbose_name_plural": "Roles", + "ordering": ["name", "created"], + "abstract": False, }, ), migrations.CreateModel( - name='OverTime', + name="StaffProfile", fields=[ - ('id', - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID')), - ('created', - models.DateTimeField( - auto_now_add=True, verbose_name='Created')), - ('modified', - models.DateTimeField(auto_now=True, verbose_name='Modified')), - ('reason', - models.TextField( - blank=True, default='', verbose_name='Reason')), - ('status', - models.CharField( - blank=True, - choices=[('1', 'Approved'), ('3', 'Pending'), - ('2', 'Rejected')], - db_index=True, - default='3', - max_length=1, - verbose_name='Status')), - ('comments', - models.TextField( - blank=True, default='', verbose_name='Comments')), - ('date', models.DateField(db_index=True, verbose_name='Date')), - ('start', models.TimeField(verbose_name='Start')), - ('end', models.TimeField(verbose_name='End')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField(auto_now_add=True, verbose_name="Created"), + ), + ( + "modified", + models.DateTimeField(auto_now=True, verbose_name="Modified"), + ), + ( + "image", + sorl.thumbnail.fields.ImageField( + blank=True, + help_text="A square image works best", + max_length=255, + upload_to="staff-images/", + verbose_name="Profile Image", + ), + ), + ( + "sex", + models.CharField( + blank=True, + choices=[ + ("0", "Not Known"), + ("1", "Male"), + ("2", "Female"), + ("9", "Not Applicable"), + ], + db_index=True, + default="0", + max_length=1, + verbose_name="Gender", + ), + ), + ( + "phone", + phonenumber_field.modelfields.PhoneNumberField( + blank=True, + default="", + max_length=128, + region=None, + verbose_name="Phone", + ), + ), + ( + "address", + models.TextField(blank=True, default="", verbose_name="Addresss"), + ), + ( + "birthday", + models.DateField( + blank=True, default=None, null=True, verbose_name="Birthday" + ), + ), + ( + "leave_days", + models.PositiveIntegerField( + blank=True, + default=21, + help_text="Number of leave days allowed in a year.", + verbose_name="Leave days", + ), + ), + ( + "sick_days", + models.PositiveIntegerField( + blank=True, + default=10, + help_text="Number of sick days allowed in a year.", + verbose_name="Sick days", + ), + ), + ( + "overtime_allowed", + models.BooleanField( + blank=True, default=False, verbose_name="Overtime allowed" + ), + ), + ( + "start_date", + models.DateField( + blank=True, + default=None, + help_text="The start date of employment", + null=True, + verbose_name="Start Date", + ), + ), + ( + "end_date", + models.DateField( + blank=True, + default=None, + help_text="The end date of employment", + null=True, + verbose_name="End Date", + ), + ), + ( + "data", + django.contrib.postgres.fields.jsonb.JSONField( + blank=True, default=dict, verbose_name="Data" + ), + ), + ("lft", models.PositiveIntegerField(editable=False)), + ("rght", models.PositiveIntegerField(editable=False)), + ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)), + ("level", models.PositiveIntegerField(editable=False)), + ( + "role", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="small_small_hr.Role", + verbose_name="Role", + ), + ), + ( + "supervisor", + mptt.fields.TreeForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="children", + to="small_small_hr.StaffProfile", + verbose_name="Manager", + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), ], options={ - 'verbose_name': 'Overtime', - 'verbose_name_plural': 'Overtime', - 'ordering': ['staff', 'date', 'start'], - 'abstract': False, + "verbose_name": "Staff Profile", + "verbose_name_plural": "Staff Profiles", + "ordering": [ + "user__first_name", + "user__last_name", + "user__username", + "created", + ], + "abstract": False, }, ), migrations.CreateModel( - name='Role', + name="StaffDocument", fields=[ - ('id', - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID')), - ('created', - models.DateTimeField( - auto_now_add=True, verbose_name='Created')), - ('modified', - models.DateTimeField(auto_now=True, verbose_name='Modified')), - ('name', models.CharField(max_length=255, - verbose_name='Name')), - ('description', - models.TextField( - blank=True, default='', verbose_name='Description')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField(auto_now_add=True, verbose_name="Created"), + ), + ( + "modified", + models.DateTimeField(auto_now=True, verbose_name="Modified"), + ), + ("name", models.CharField(max_length=255, verbose_name="Name")), + ( + "description", + models.TextField( + blank=True, default="", verbose_name="Description" + ), + ), + ( + "file", + private_storage.fields.PrivateFileField( + help_text="Upload staff member document", + storage=private_storage.storage.files.PrivateFileSystemStorage(), + upload_to="staff-documents/", + verbose_name="File", + ), + ), + ( + "public", + models.BooleanField( + blank=True, + default=False, + help_text="If public, it will be available to everyone.", + verbose_name="Public", + ), + ), + ( + "staff", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="small_small_hr.StaffProfile", + verbose_name="Staff Member", + ), + ), ], options={ - 'verbose_name': 'Role', - 'verbose_name_plural': 'Roles', - 'ordering': ['name', 'created'], - 'abstract': False, + "verbose_name": "Staff Document", + "verbose_name_plural": "Staff Documents", + "ordering": ["staff", "name", "-created"], + "abstract": False, }, ), migrations.CreateModel( - name='StaffDocument', + name="OverTime", fields=[ - ('id', - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID')), - ('created', - models.DateTimeField( - auto_now_add=True, verbose_name='Created')), - ('modified', - models.DateTimeField(auto_now=True, verbose_name='Modified')), - ('name', models.CharField(max_length=255, - verbose_name='Name')), - ('description', - models.TextField( - blank=True, default='', verbose_name='Description')), - ('file', - private_storage.fields.PrivateFileField( - help_text='Upload staff member drocument', - storage=private_storage.storage.files. - PrivateFileSystemStorage(), - upload_to='staff-documents/', - verbose_name='File')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "review_date", + models.DateTimeField( + blank=True, default=None, null=True, verbose_name="Review Date" + ), + ), + ( + "created", + models.DateTimeField(auto_now_add=True, verbose_name="Created"), + ), + ( + "modified", + models.DateTimeField(auto_now=True, verbose_name="Modified"), + ), + ( + "review_reason", + models.TextField(blank=True, default="", verbose_name="Reason"), + ), + ( + "review_status", + models.CharField( + blank=True, + choices=[ + ("1", "Approved"), + ("3", "Pending"), + ("2", "Rejected"), + ], + db_index=True, + default="3", + max_length=1, + verbose_name="Status", + ), + ), + ("date", models.DateField(db_index=True, verbose_name="Date")), + ("start", models.TimeField(verbose_name="Start")), + ("end", models.TimeField(verbose_name="End")), + ( + "staff", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="small_small_hr.StaffProfile", + verbose_name="Staff Member", + ), + ), ], options={ - 'verbose_name': 'Staff Document', - 'verbose_name_plural': 'Staff Documents', - 'ordering': ['staff', 'name', 'created'], - 'abstract': False, + "verbose_name": "Overtime", + "verbose_name_plural": "Overtime", + "ordering": ["staff", "-date", "start"], + "abstract": False, }, ), migrations.CreateModel( - name='StaffProfile', + name="Leave", fields=[ - ('id', - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID')), - ('created', - models.DateTimeField( - auto_now_add=True, verbose_name='Created')), - ('modified', - models.DateTimeField(auto_now=True, verbose_name='Modified')), - ('sex', - models.CharField( - blank=True, - choices=[('0', 'Not Known'), ('1', 'Male'), - ('2', 'Female'), ('9', 'Not Applicable')], - db_index=True, - default='0', - max_length=1, - verbose_name='Gender')), - ('phone', - phonenumber_field.modelfields.PhoneNumberField( - blank=True, - default='', - max_length=128, - verbose_name='Phone')), - ('address', - models.TextField( - blank=True, default='', verbose_name='Addresss')), - ('birthday', - models.DateField( - blank=True, - default=None, - null=True, - verbose_name='Birth day')), - ('leave_days', - models.PositiveIntegerField( - blank=True, - default=21, - help_text='Number of leave days allowed in a year.', - verbose_name='Leave days')), - ('sick_days', - models.PositiveIntegerField( - blank=True, - default=10, - help_text='Number of sick days allowed in a year.', - verbose_name='Sick days')), - ('overtime_allowed', - models.BooleanField( - default=False, verbose_name='Overtime allowed')), - ('start_date', - models.DateField( - blank=True, - default=None, - help_text='The start date of employment', - null=True, - verbose_name='Start Date')), - ('end_date', - models.DateField( - blank=True, - default=None, - help_text='The end date of employment', - null=True, - verbose_name='End Date')), - ('data', - django.contrib.postgres.fields.jsonb.JSONField( - blank=True, default=dict, verbose_name='Data')), - ('role', - models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to='small_small_hr.Role', - verbose_name='Role')), - ('user', - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - verbose_name='User')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "review_date", + models.DateTimeField( + blank=True, default=None, null=True, verbose_name="Review Date" + ), + ), + ( + "created", + models.DateTimeField(auto_now_add=True, verbose_name="Created"), + ), + ( + "modified", + models.DateTimeField(auto_now=True, verbose_name="Modified"), + ), + ("start", models.DateTimeField(verbose_name="Start Date")), + ("end", models.DateTimeField(verbose_name="End Date")), + ( + "review_reason", + models.TextField(blank=True, default="", verbose_name="Reason"), + ), + ( + "review_status", + models.CharField( + blank=True, + choices=[ + ("1", "Approved"), + ("3", "Pending"), + ("2", "Rejected"), + ], + db_index=True, + default="3", + max_length=1, + verbose_name="Status", + ), + ), + ( + "leave_type", + models.CharField( + blank=True, + choices=[("1", "Sick Leave"), ("2", "Regular Leave")], + db_index=True, + default="2", + max_length=1, + verbose_name="Type", + ), + ), + ( + "staff", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="small_small_hr.StaffProfile", + verbose_name="Staff Member", + ), + ), ], options={ - 'verbose_name': - 'Staff Profile', - 'verbose_name_plural': - 'Staff Profiles', - 'ordering': [ - 'user__first_name', 'user__last_name', 'user__username', - 'created' - ], - 'abstract': - False, + "verbose_name": "Leave", + "verbose_name_plural": "Leave", + "ordering": ["staff", "-start"], + "abstract": False, }, ), - migrations.AddField( - model_name='staffdocument', - name='staff', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to='small_small_hr.StaffProfile', - verbose_name='Staff Member'), - ), - migrations.AddField( - model_name='overtime', - name='staff', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to='small_small_hr.StaffProfile', - verbose_name='Staff Member'), - ), - migrations.AddField( - model_name='leave', - name='staff', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to='small_small_hr.StaffProfile', - verbose_name='Staff Member'), - ), - migrations.AddField( - model_name='annualleave', - name='staff', - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to='small_small_hr.StaffProfile', - verbose_name='Staff Member'), - ), - migrations.AlterUniqueTogether( - name='annualleave', - unique_together={('year', 'staff', 'leave_type')}, + migrations.CreateModel( + name="AnnualLeave", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField(auto_now_add=True, verbose_name="Created"), + ), + ( + "modified", + models.DateTimeField(auto_now=True, verbose_name="Modified"), + ), + ( + "year", + 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", + ), + ), + ( + "leave_type", + models.CharField( + choices=[("1", "Sick Leave"), ("2", "Regular Leave")], + db_index=True, + max_length=1, + verbose_name="Type", + ), + ), + ( + "allowed_days", + models.PositiveIntegerField( + blank=True, + default=21, + help_text="Number of leave days allowed in a year.", + verbose_name="Allowed Leave days", + ), + ), + ( + "carried_over_days", + models.PositiveIntegerField( + blank=True, + default=0, + help_text="Number of leave days carried over into this year.", + verbose_name="Carried Over Leave days", + ), + ), + ( + "staff", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="small_small_hr.StaffProfile", + verbose_name="Staff Member", + ), + ), + ], + options={ + "verbose_name": "Annual Leave", + "verbose_name_plural": "Annual Leave", + "ordering": ["-year", "leave_type", "staff"], + "unique_together": {("year", "staff", "leave_type")}, + }, ), ] diff --git a/small_small_hr/migrations/0002_auto_20180722_1808.py b/small_small_hr/migrations/0002_auto_20180722_1808.py deleted file mode 100644 index 3efed98..0000000 --- a/small_small_hr/migrations/0002_auto_20180722_1808.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 2.0.7 on 2018-07-22 15:08 - -from django.db import migrations -import private_storage.fields -import private_storage.storage.files -import sorl.thumbnail.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('small_small_hr', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='staffprofile', - name='image', - field=sorl.thumbnail.fields.ImageField( - blank=True, - help_text='A square image works best', - max_length=255, - upload_to='staff-images/', - verbose_name='Profile Image'), - ), - migrations.AlterField( - model_name='staffdocument', - name='file', - field=private_storage.fields.PrivateFileField( - help_text='Upload staff member document', - storage=private_storage.storage.files.PrivateFileSystemStorage( - ), - upload_to='staff-documents/', - verbose_name='File'), - ), - ] diff --git a/small_small_hr/migrations/0003_staffdocument_public.py b/small_small_hr/migrations/0003_staffdocument_public.py deleted file mode 100644 index 8cc0c90..0000000 --- a/small_small_hr/migrations/0003_staffdocument_public.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.0.7 on 2018-07-25 16:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('small_small_hr', '0002_auto_20180722_1808'), - ] - - operations = [ - migrations.AddField( - model_name='staffdocument', - name='public', - field=models.BooleanField( - default=False, - help_text='If public, it will be available to everyone.', - verbose_name='Public'), - ), - ] diff --git a/small_small_hr/migrations/0004_auto_20180725_2127.py b/small_small_hr/migrations/0004_auto_20180725_2127.py deleted file mode 100644 index f694e35..0000000 --- a/small_small_hr/migrations/0004_auto_20180725_2127.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.0.7 on 2018-07-25 18:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('small_small_hr', '0003_staffdocument_public'), - ] - - operations = [ - migrations.AlterField( - model_name='staffprofile', - name='birthday', - field=models.DateField( - blank=True, default=None, null=True, verbose_name='Birthday'), - ), - ] diff --git a/small_small_hr/migrations/0005_auto_20181115_2112.py b/small_small_hr/migrations/0005_auto_20181115_2112.py deleted file mode 100644 index 765c5c4..0000000 --- a/small_small_hr/migrations/0005_auto_20181115_2112.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.1.2 on 2018-11-15 18:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('small_small_hr', '0004_auto_20180725_2127'), - ] - - operations = [ - migrations.AlterField( - model_name='staffdocument', - name='public', - field=models.BooleanField( - blank=True, - default=False, - help_text='If public, it will be available to everyone.', - verbose_name='Public'), - ), - migrations.AlterField( - model_name='staffprofile', - name='overtime_allowed', - field=models.BooleanField( - blank=True, default=False, verbose_name='Overtime allowed'), - ), - ] diff --git a/small_small_hr/migrations/0006_auto_20181209_0108.py b/small_small_hr/migrations/0006_auto_20181209_0108.py deleted file mode 100644 index fbdcd16..0000000 --- a/small_small_hr/migrations/0006_auto_20181209_0108.py +++ /dev/null @@ -1,77 +0,0 @@ -# Generated by Django 2.1.3 on 2018-12-08 22:08 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('small_small_hr', '0005_auto_20181115_2112'), - ] - - operations = [ - migrations.CreateModel( - name='FreeDay', - fields=[ - ('id', - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID')), - ('name', models.CharField(max_length=255, - verbose_name='Name')), - ('date', models.DateField(unique=True, verbose_name='Date')), - ], - options={ - 'verbose_name': 'Free Day', - 'verbose_name_plural': 'Free Days', - 'ordering': ['-date'], - }, - ), - migrations.AlterModelOptions( - name='annualleave', - options={ - 'ordering': ['-year', 'leave_type', 'staff'], - 'verbose_name': 'Annual Leave', - 'verbose_name_plural': 'Annual Leave' - }, - ), - migrations.AlterModelOptions( - name='leave', - options={ - 'ordering': ['staff', '-start'], - 'verbose_name': 'Leave', - 'verbose_name_plural': 'Leave' - }, - ), - migrations.AlterModelOptions( - name='overtime', - options={ - 'ordering': ['staff', '-date', 'start'], - 'verbose_name': 'Overtime', - 'verbose_name_plural': 'Overtime' - }, - ), - migrations.AlterModelOptions( - name='staffdocument', - options={ - 'ordering': ['staff', 'name', '-created'], - 'verbose_name': 'Staff Document', - 'verbose_name_plural': 'Staff Documents' - }, - ), - 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)], - db_index=True, - default=2018, - verbose_name='Year'), - ), - ] diff --git a/small_small_hr/migrations/0007_auto_20190625_2111.py b/small_small_hr/migrations/0007_auto_20190625_2111.py deleted file mode 100644 index b4f9f96..0000000 --- a/small_small_hr/migrations/0007_auto_20190625_2111.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 2.1.7 on 2019-06-25 18:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("small_small_hr", "0006_auto_20181209_0108")] - - operations = [ - 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), - ], - db_index=True, - default=2017, - verbose_name="Year", - ), - ) - ] diff --git a/small_small_hr/migrations/0008_auto_20200607_1537.py b/small_small_hr/migrations/0008_auto_20200607_1537.py deleted file mode 100644 index dd9f1bd..0000000 --- a/small_small_hr/migrations/0008_auto_20200607_1537.py +++ /dev/null @@ -1,65 +0,0 @@ -# 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 deleted file mode 100644 index 14b0e95..0000000 --- a/small_small_hr/migrations/0009_auto_20200607_2244.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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, - ), - ] From 8ecda530a312af3745b9bdbb9ce42859d8db4483 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 19 Jun 2020 23:06:00 +0300 Subject: [PATCH 74/83] Use model_mommy recipes to create staff profile objects It seems that django-mptt gets confused when model_mommy sets randomized values for the mptt model fields. We therefore use a model_mommy recipe to control this. Credits: https://github.com/django-mptt/django-mptt/issues/694 --- tests/test_process.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tests/test_process.py b/tests/test_process.py index 0f22b3c..5aa32ae 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -8,6 +8,7 @@ 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 @@ -28,18 +29,28 @@ def setUp(self): ) self.boss.groups.add(hr_group) - @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 = mommy.make( + self.manager = mommy.make( "auth.User", first_name="Jane", last_name="Ndoe", email="jane@example.com" ) - manager_profile = mommy.make("small_small_hr.StaffProfile", user=manager) - - user = mommy.make( + self.user = mommy.make( "auth.User", first_name="Bob", last_name="Ndoe", email="bob@example.com" ) - staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) + + 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 @@ -160,15 +171,10 @@ def test_leave_review_process(self, mock): # pylint: disable=too-many-locals @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 = mommy.make( - "auth.User", first_name="Jane", last_name="Ndoe", email="jane@example.com" - ) - manager_profile = mommy.make("small_small_hr.StaffProfile", user=manager) + manager = self.manager + manager_profile = self.manager_profile - user = mommy.make( - "auth.User", first_name="Bob", last_name="Ndoe", email="bob@example.com" - ) - staffprofile = mommy.make("small_small_hr.StaffProfile", user=user) + staffprofile = self.staffprofile staffprofile.supervisor = manager_profile staffprofile.save() From d9bdfb1230b3bcb3d6f10904742ebe8f742e3d7a Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 19 Jun 2020 23:07:58 +0300 Subject: [PATCH 75/83] Revert "Redo migrations" This reverts commit 83e5d8c853eba8782f0f173e30aa24eb659cdf4f. We may not need to recreate this project's migrations (yet) because the django-mptt issue has been fixed in the previous commit i.e. 8ecda530a312af3745b9bdbb9ce42859d8db4483 --- small_small_hr/migrations/0001_initial.py | 769 +++++++----------- .../migrations/0002_auto_20180722_1808.py | 36 + .../migrations/0003_staffdocument_public.py | 21 + .../migrations/0004_auto_20180725_2127.py | 19 + .../migrations/0005_auto_20181115_2112.py | 28 + .../migrations/0006_auto_20181209_0108.py | 77 ++ .../migrations/0007_auto_20190625_2111.py | 34 + .../migrations/0008_auto_20200607_1537.py | 65 ++ .../migrations/0009_auto_20200607_2244.py | 52 ++ 9 files changed, 627 insertions(+), 474 deletions(-) create mode 100644 small_small_hr/migrations/0002_auto_20180722_1808.py create mode 100644 small_small_hr/migrations/0003_staffdocument_public.py create mode 100644 small_small_hr/migrations/0004_auto_20180725_2127.py create mode 100644 small_small_hr/migrations/0005_auto_20181115_2112.py create mode 100644 small_small_hr/migrations/0006_auto_20181209_0108.py create mode 100644 small_small_hr/migrations/0007_auto_20190625_2111.py create mode 100644 small_small_hr/migrations/0008_auto_20200607_1537.py create mode 100644 small_small_hr/migrations/0009_auto_20200607_2244.py diff --git a/small_small_hr/migrations/0001_initial.py b/small_small_hr/migrations/0001_initial.py index b70f612..01ac00a 100644 --- a/small_small_hr/migrations/0001_initial.py +++ b/small_small_hr/migrations/0001_initial.py @@ -1,15 +1,12 @@ -# Generated by Django 3.0.7 on 2020-06-19 19:28 -# pylint: disable=invalid-name,missing-module-docstring,missing-class-docstring -import django.contrib.postgres.fields.jsonb -import django.db.models.deletion +# Generated by Django 2.0.6 on 2018-06-19 19:22 + from django.conf import settings +import django.contrib.postgres.fields.jsonb from django.db import migrations, models - -import mptt.fields +import django.db.models.deletion import phonenumber_field.modelfields import private_storage.fields import private_storage.storage.files -import sorl.thumbnail.fields class Migration(migrations.Migration): @@ -22,507 +19,331 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name="FreeDay", + name='AnnualLeave', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=255, verbose_name="Name")), - ("date", models.DateField(unique=True, verbose_name="Date")), + ('id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('created', + models.DateTimeField( + auto_now_add=True, verbose_name='Created')), + ('modified', + models.DateTimeField(auto_now=True, verbose_name='Modified')), + ('year', + models.PositiveIntegerField( + choices=[(2017, 2017), (2018, 2018), (2019, 2019), + (2020, 2020), (2021, 2021), (2022, 2022)], + db_index=True, + default=2018, + verbose_name='Year')), + ('leave_type', + models.CharField( + choices=[('1', 'Sick Leave'), ('2', 'Regular Leave')], + db_index=True, + max_length=1, + verbose_name='Type')), + ('allowed_days', + models.PositiveIntegerField( + blank=True, + default=21, + help_text='Number of leave days allowed in a year.', + verbose_name='Allowed Leave days')), + ('carried_over_days', + models.PositiveIntegerField( + blank=True, + default=0, + help_text= + 'Number of leave days carried over into this year.', + verbose_name='Carried Over Leave days')), ], options={ - "verbose_name": "Free Day", - "verbose_name_plural": "Free Days", - "ordering": ["-date"], + 'verbose_name': 'Annual Leave', + 'verbose_name_plural': 'Annual Leave', + 'ordering': ['year', 'leave_type', 'staff'], }, ), migrations.CreateModel( - name="Role", + name='Leave', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created", - models.DateTimeField(auto_now_add=True, verbose_name="Created"), - ), - ( - "modified", - models.DateTimeField(auto_now=True, verbose_name="Modified"), - ), - ("name", models.CharField(max_length=255, verbose_name="Name")), - ( - "description", - models.TextField( - blank=True, default="", verbose_name="Description" - ), - ), + ('id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('created', + models.DateTimeField( + auto_now_add=True, verbose_name='Created')), + ('modified', + models.DateTimeField(auto_now=True, verbose_name='Modified')), + ('start', models.DateTimeField(verbose_name='Start Date')), + ('end', models.DateTimeField(verbose_name='End Date')), + ('reason', + models.TextField( + blank=True, default='', verbose_name='Reason')), + ('status', + models.CharField( + blank=True, + choices=[('1', 'Approved'), ('3', 'Pending'), + ('2', 'Rejected')], + db_index=True, + default='3', + max_length=1, + verbose_name='Status')), + ('comments', + models.TextField( + blank=True, default='', verbose_name='Comments')), + ('leave_type', + models.CharField( + blank=True, + choices=[('1', 'Sick Leave'), ('2', 'Regular Leave')], + db_index=True, + default='2', + max_length=1, + verbose_name='Type')), ], options={ - "verbose_name": "Role", - "verbose_name_plural": "Roles", - "ordering": ["name", "created"], - "abstract": False, + 'verbose_name': 'Leave', + 'verbose_name_plural': 'Leave', + 'ordering': ['staff', 'start'], + 'abstract': False, }, ), migrations.CreateModel( - name="StaffProfile", + name='OverTime', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created", - models.DateTimeField(auto_now_add=True, verbose_name="Created"), - ), - ( - "modified", - models.DateTimeField(auto_now=True, verbose_name="Modified"), - ), - ( - "image", - sorl.thumbnail.fields.ImageField( - blank=True, - help_text="A square image works best", - max_length=255, - upload_to="staff-images/", - verbose_name="Profile Image", - ), - ), - ( - "sex", - models.CharField( - blank=True, - choices=[ - ("0", "Not Known"), - ("1", "Male"), - ("2", "Female"), - ("9", "Not Applicable"), - ], - db_index=True, - default="0", - max_length=1, - verbose_name="Gender", - ), - ), - ( - "phone", - phonenumber_field.modelfields.PhoneNumberField( - blank=True, - default="", - max_length=128, - region=None, - verbose_name="Phone", - ), - ), - ( - "address", - models.TextField(blank=True, default="", verbose_name="Addresss"), - ), - ( - "birthday", - models.DateField( - blank=True, default=None, null=True, verbose_name="Birthday" - ), - ), - ( - "leave_days", - models.PositiveIntegerField( - blank=True, - default=21, - help_text="Number of leave days allowed in a year.", - verbose_name="Leave days", - ), - ), - ( - "sick_days", - models.PositiveIntegerField( - blank=True, - default=10, - help_text="Number of sick days allowed in a year.", - verbose_name="Sick days", - ), - ), - ( - "overtime_allowed", - models.BooleanField( - blank=True, default=False, verbose_name="Overtime allowed" - ), - ), - ( - "start_date", - models.DateField( - blank=True, - default=None, - help_text="The start date of employment", - null=True, - verbose_name="Start Date", - ), - ), - ( - "end_date", - models.DateField( - blank=True, - default=None, - help_text="The end date of employment", - null=True, - verbose_name="End Date", - ), - ), - ( - "data", - django.contrib.postgres.fields.jsonb.JSONField( - blank=True, default=dict, verbose_name="Data" - ), - ), - ("lft", models.PositiveIntegerField(editable=False)), - ("rght", models.PositiveIntegerField(editable=False)), - ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)), - ("level", models.PositiveIntegerField(editable=False)), - ( - "role", - models.ForeignKey( - blank=True, - default=None, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="small_small_hr.Role", - verbose_name="Role", - ), - ), - ( - "supervisor", - mptt.fields.TreeForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - related_name="children", - to="small_small_hr.StaffProfile", - verbose_name="Manager", - ), - ), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - verbose_name="User", - ), - ), + ('id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('created', + models.DateTimeField( + auto_now_add=True, verbose_name='Created')), + ('modified', + models.DateTimeField(auto_now=True, verbose_name='Modified')), + ('reason', + models.TextField( + blank=True, default='', verbose_name='Reason')), + ('status', + models.CharField( + blank=True, + choices=[('1', 'Approved'), ('3', 'Pending'), + ('2', 'Rejected')], + db_index=True, + default='3', + max_length=1, + verbose_name='Status')), + ('comments', + models.TextField( + blank=True, default='', verbose_name='Comments')), + ('date', models.DateField(db_index=True, verbose_name='Date')), + ('start', models.TimeField(verbose_name='Start')), + ('end', models.TimeField(verbose_name='End')), ], options={ - "verbose_name": "Staff Profile", - "verbose_name_plural": "Staff Profiles", - "ordering": [ - "user__first_name", - "user__last_name", - "user__username", - "created", - ], - "abstract": False, + 'verbose_name': 'Overtime', + 'verbose_name_plural': 'Overtime', + 'ordering': ['staff', 'date', 'start'], + 'abstract': False, }, ), migrations.CreateModel( - name="StaffDocument", + name='Role', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created", - models.DateTimeField(auto_now_add=True, verbose_name="Created"), - ), - ( - "modified", - models.DateTimeField(auto_now=True, verbose_name="Modified"), - ), - ("name", models.CharField(max_length=255, verbose_name="Name")), - ( - "description", - models.TextField( - blank=True, default="", verbose_name="Description" - ), - ), - ( - "file", - private_storage.fields.PrivateFileField( - help_text="Upload staff member document", - storage=private_storage.storage.files.PrivateFileSystemStorage(), - upload_to="staff-documents/", - verbose_name="File", - ), - ), - ( - "public", - models.BooleanField( - blank=True, - default=False, - help_text="If public, it will be available to everyone.", - verbose_name="Public", - ), - ), - ( - "staff", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="small_small_hr.StaffProfile", - verbose_name="Staff Member", - ), - ), + ('id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('created', + models.DateTimeField( + auto_now_add=True, verbose_name='Created')), + ('modified', + models.DateTimeField(auto_now=True, verbose_name='Modified')), + ('name', models.CharField(max_length=255, + verbose_name='Name')), + ('description', + models.TextField( + blank=True, default='', verbose_name='Description')), ], options={ - "verbose_name": "Staff Document", - "verbose_name_plural": "Staff Documents", - "ordering": ["staff", "name", "-created"], - "abstract": False, + 'verbose_name': 'Role', + 'verbose_name_plural': 'Roles', + 'ordering': ['name', 'created'], + 'abstract': False, }, ), migrations.CreateModel( - name="OverTime", + name='StaffDocument', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "review_date", - models.DateTimeField( - blank=True, default=None, null=True, verbose_name="Review Date" - ), - ), - ( - "created", - models.DateTimeField(auto_now_add=True, verbose_name="Created"), - ), - ( - "modified", - models.DateTimeField(auto_now=True, verbose_name="Modified"), - ), - ( - "review_reason", - models.TextField(blank=True, default="", verbose_name="Reason"), - ), - ( - "review_status", - models.CharField( - blank=True, - choices=[ - ("1", "Approved"), - ("3", "Pending"), - ("2", "Rejected"), - ], - db_index=True, - default="3", - max_length=1, - verbose_name="Status", - ), - ), - ("date", models.DateField(db_index=True, verbose_name="Date")), - ("start", models.TimeField(verbose_name="Start")), - ("end", models.TimeField(verbose_name="End")), - ( - "staff", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="small_small_hr.StaffProfile", - verbose_name="Staff Member", - ), - ), + ('id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('created', + models.DateTimeField( + auto_now_add=True, verbose_name='Created')), + ('modified', + models.DateTimeField(auto_now=True, verbose_name='Modified')), + ('name', models.CharField(max_length=255, + verbose_name='Name')), + ('description', + models.TextField( + blank=True, default='', verbose_name='Description')), + ('file', + private_storage.fields.PrivateFileField( + help_text='Upload staff member drocument', + storage=private_storage.storage.files. + PrivateFileSystemStorage(), + upload_to='staff-documents/', + verbose_name='File')), ], options={ - "verbose_name": "Overtime", - "verbose_name_plural": "Overtime", - "ordering": ["staff", "-date", "start"], - "abstract": False, + 'verbose_name': 'Staff Document', + 'verbose_name_plural': 'Staff Documents', + 'ordering': ['staff', 'name', 'created'], + 'abstract': False, }, ), migrations.CreateModel( - name="Leave", + name='StaffProfile', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "review_date", - models.DateTimeField( - blank=True, default=None, null=True, verbose_name="Review Date" - ), - ), - ( - "created", - models.DateTimeField(auto_now_add=True, verbose_name="Created"), - ), - ( - "modified", - models.DateTimeField(auto_now=True, verbose_name="Modified"), - ), - ("start", models.DateTimeField(verbose_name="Start Date")), - ("end", models.DateTimeField(verbose_name="End Date")), - ( - "review_reason", - models.TextField(blank=True, default="", verbose_name="Reason"), - ), - ( - "review_status", - models.CharField( - blank=True, - choices=[ - ("1", "Approved"), - ("3", "Pending"), - ("2", "Rejected"), - ], - db_index=True, - default="3", - max_length=1, - verbose_name="Status", - ), - ), - ( - "leave_type", - models.CharField( - blank=True, - choices=[("1", "Sick Leave"), ("2", "Regular Leave")], - db_index=True, - default="2", - max_length=1, - verbose_name="Type", - ), - ), - ( - "staff", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="small_small_hr.StaffProfile", - verbose_name="Staff Member", - ), - ), + ('id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('created', + models.DateTimeField( + auto_now_add=True, verbose_name='Created')), + ('modified', + models.DateTimeField(auto_now=True, verbose_name='Modified')), + ('sex', + models.CharField( + blank=True, + choices=[('0', 'Not Known'), ('1', 'Male'), + ('2', 'Female'), ('9', 'Not Applicable')], + db_index=True, + default='0', + max_length=1, + verbose_name='Gender')), + ('phone', + phonenumber_field.modelfields.PhoneNumberField( + blank=True, + default='', + max_length=128, + verbose_name='Phone')), + ('address', + models.TextField( + blank=True, default='', verbose_name='Addresss')), + ('birthday', + models.DateField( + blank=True, + default=None, + null=True, + verbose_name='Birth day')), + ('leave_days', + models.PositiveIntegerField( + blank=True, + default=21, + help_text='Number of leave days allowed in a year.', + verbose_name='Leave days')), + ('sick_days', + models.PositiveIntegerField( + blank=True, + default=10, + help_text='Number of sick days allowed in a year.', + verbose_name='Sick days')), + ('overtime_allowed', + models.BooleanField( + default=False, verbose_name='Overtime allowed')), + ('start_date', + models.DateField( + blank=True, + default=None, + help_text='The start date of employment', + null=True, + verbose_name='Start Date')), + ('end_date', + models.DateField( + blank=True, + default=None, + help_text='The end date of employment', + null=True, + verbose_name='End Date')), + ('data', + django.contrib.postgres.fields.jsonb.JSONField( + blank=True, default=dict, verbose_name='Data')), + ('role', + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to='small_small_hr.Role', + verbose_name='Role')), + ('user', + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name='User')), ], options={ - "verbose_name": "Leave", - "verbose_name_plural": "Leave", - "ordering": ["staff", "-start"], - "abstract": False, + 'verbose_name': + 'Staff Profile', + 'verbose_name_plural': + 'Staff Profiles', + 'ordering': [ + 'user__first_name', 'user__last_name', 'user__username', + 'created' + ], + 'abstract': + False, }, ), - migrations.CreateModel( - name="AnnualLeave", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created", - models.DateTimeField(auto_now_add=True, verbose_name="Created"), - ), - ( - "modified", - models.DateTimeField(auto_now=True, verbose_name="Modified"), - ), - ( - "year", - 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", - ), - ), - ( - "leave_type", - models.CharField( - choices=[("1", "Sick Leave"), ("2", "Regular Leave")], - db_index=True, - max_length=1, - verbose_name="Type", - ), - ), - ( - "allowed_days", - models.PositiveIntegerField( - blank=True, - default=21, - help_text="Number of leave days allowed in a year.", - verbose_name="Allowed Leave days", - ), - ), - ( - "carried_over_days", - models.PositiveIntegerField( - blank=True, - default=0, - help_text="Number of leave days carried over into this year.", - verbose_name="Carried Over Leave days", - ), - ), - ( - "staff", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="small_small_hr.StaffProfile", - verbose_name="Staff Member", - ), - ), - ], - options={ - "verbose_name": "Annual Leave", - "verbose_name_plural": "Annual Leave", - "ordering": ["-year", "leave_type", "staff"], - "unique_together": {("year", "staff", "leave_type")}, - }, + migrations.AddField( + model_name='staffdocument', + name='staff', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='small_small_hr.StaffProfile', + verbose_name='Staff Member'), + ), + migrations.AddField( + model_name='overtime', + name='staff', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='small_small_hr.StaffProfile', + verbose_name='Staff Member'), + ), + migrations.AddField( + model_name='leave', + name='staff', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='small_small_hr.StaffProfile', + verbose_name='Staff Member'), + ), + migrations.AddField( + model_name='annualleave', + name='staff', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='small_small_hr.StaffProfile', + verbose_name='Staff Member'), + ), + migrations.AlterUniqueTogether( + name='annualleave', + unique_together={('year', 'staff', 'leave_type')}, ), ] diff --git a/small_small_hr/migrations/0002_auto_20180722_1808.py b/small_small_hr/migrations/0002_auto_20180722_1808.py new file mode 100644 index 0000000..3efed98 --- /dev/null +++ b/small_small_hr/migrations/0002_auto_20180722_1808.py @@ -0,0 +1,36 @@ +# Generated by Django 2.0.7 on 2018-07-22 15:08 + +from django.db import migrations +import private_storage.fields +import private_storage.storage.files +import sorl.thumbnail.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('small_small_hr', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='staffprofile', + name='image', + field=sorl.thumbnail.fields.ImageField( + blank=True, + help_text='A square image works best', + max_length=255, + upload_to='staff-images/', + verbose_name='Profile Image'), + ), + migrations.AlterField( + model_name='staffdocument', + name='file', + field=private_storage.fields.PrivateFileField( + help_text='Upload staff member document', + storage=private_storage.storage.files.PrivateFileSystemStorage( + ), + upload_to='staff-documents/', + verbose_name='File'), + ), + ] diff --git a/small_small_hr/migrations/0003_staffdocument_public.py b/small_small_hr/migrations/0003_staffdocument_public.py new file mode 100644 index 0000000..8cc0c90 --- /dev/null +++ b/small_small_hr/migrations/0003_staffdocument_public.py @@ -0,0 +1,21 @@ +# Generated by Django 2.0.7 on 2018-07-25 16:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('small_small_hr', '0002_auto_20180722_1808'), + ] + + operations = [ + migrations.AddField( + model_name='staffdocument', + name='public', + field=models.BooleanField( + default=False, + help_text='If public, it will be available to everyone.', + verbose_name='Public'), + ), + ] diff --git a/small_small_hr/migrations/0004_auto_20180725_2127.py b/small_small_hr/migrations/0004_auto_20180725_2127.py new file mode 100644 index 0000000..f694e35 --- /dev/null +++ b/small_small_hr/migrations/0004_auto_20180725_2127.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.7 on 2018-07-25 18:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('small_small_hr', '0003_staffdocument_public'), + ] + + operations = [ + migrations.AlterField( + model_name='staffprofile', + name='birthday', + field=models.DateField( + blank=True, default=None, null=True, verbose_name='Birthday'), + ), + ] diff --git a/small_small_hr/migrations/0005_auto_20181115_2112.py b/small_small_hr/migrations/0005_auto_20181115_2112.py new file mode 100644 index 0000000..765c5c4 --- /dev/null +++ b/small_small_hr/migrations/0005_auto_20181115_2112.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1.2 on 2018-11-15 18:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('small_small_hr', '0004_auto_20180725_2127'), + ] + + operations = [ + migrations.AlterField( + model_name='staffdocument', + name='public', + field=models.BooleanField( + blank=True, + default=False, + help_text='If public, it will be available to everyone.', + verbose_name='Public'), + ), + migrations.AlterField( + model_name='staffprofile', + name='overtime_allowed', + field=models.BooleanField( + blank=True, default=False, verbose_name='Overtime allowed'), + ), + ] diff --git a/small_small_hr/migrations/0006_auto_20181209_0108.py b/small_small_hr/migrations/0006_auto_20181209_0108.py new file mode 100644 index 0000000..fbdcd16 --- /dev/null +++ b/small_small_hr/migrations/0006_auto_20181209_0108.py @@ -0,0 +1,77 @@ +# Generated by Django 2.1.3 on 2018-12-08 22:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('small_small_hr', '0005_auto_20181115_2112'), + ] + + operations = [ + migrations.CreateModel( + name='FreeDay', + fields=[ + ('id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('name', models.CharField(max_length=255, + verbose_name='Name')), + ('date', models.DateField(unique=True, verbose_name='Date')), + ], + options={ + 'verbose_name': 'Free Day', + 'verbose_name_plural': 'Free Days', + 'ordering': ['-date'], + }, + ), + migrations.AlterModelOptions( + name='annualleave', + options={ + 'ordering': ['-year', 'leave_type', 'staff'], + 'verbose_name': 'Annual Leave', + 'verbose_name_plural': 'Annual Leave' + }, + ), + migrations.AlterModelOptions( + name='leave', + options={ + 'ordering': ['staff', '-start'], + 'verbose_name': 'Leave', + 'verbose_name_plural': 'Leave' + }, + ), + migrations.AlterModelOptions( + name='overtime', + options={ + 'ordering': ['staff', '-date', 'start'], + 'verbose_name': 'Overtime', + 'verbose_name_plural': 'Overtime' + }, + ), + migrations.AlterModelOptions( + name='staffdocument', + options={ + 'ordering': ['staff', 'name', '-created'], + 'verbose_name': 'Staff Document', + 'verbose_name_plural': 'Staff Documents' + }, + ), + 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)], + db_index=True, + default=2018, + verbose_name='Year'), + ), + ] diff --git a/small_small_hr/migrations/0007_auto_20190625_2111.py b/small_small_hr/migrations/0007_auto_20190625_2111.py new file mode 100644 index 0000000..b4f9f96 --- /dev/null +++ b/small_small_hr/migrations/0007_auto_20190625_2111.py @@ -0,0 +1,34 @@ +# Generated by Django 2.1.7 on 2019-06-25 18:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [("small_small_hr", "0006_auto_20181209_0108")] + + operations = [ + 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), + ], + db_index=True, + default=2017, + verbose_name="Year", + ), + ) + ] 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, + ), + ] From ab8938e4902bf0ec6cad76974707cac71edc9416 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Fri, 19 Jun 2020 23:18:10 +0300 Subject: [PATCH 76/83] Add release drafter template --- .github/release-drafter.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/release-drafter.yml 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 From b1e8972ad1e7e06f553318d5f598cc7e4c6186de Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 23 Jun 2020 21:32:31 +0300 Subject: [PATCH 77/83] Remove unused templates --- .../small_small_hr/email/generic_email_body.html | 7 ------- .../small_small_hr/email/generic_email_body.txt | 9 --------- .../small_small_hr/email/generic_email_subject.txt | 1 - 3 files changed, 17 deletions(-) delete mode 100644 small_small_hr/templates/small_small_hr/email/generic_email_body.html delete mode 100644 small_small_hr/templates/small_small_hr/email/generic_email_body.txt delete mode 100644 small_small_hr/templates/small_small_hr/email/generic_email_subject.txt 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 From 3b66084261951037aa838c6692666aacec13467f Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 23 Jun 2020 21:40:55 +0300 Subject: [PATCH 78/83] =?UTF-8?q?=E2=86=91=20bump=20to=20version=200.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- small_small_hr/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/small_small_hr/__init__.py b/small_small_hr/__init__.py index 0ab9e5e..18c3d3e 100644 --- a/small_small_hr/__init__.py +++ b/small_small_hr/__init__.py @@ -1,7 +1,7 @@ """ Main init file for small_small_hr """ -VERSION = (0, 1, 9) +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 From 5254ba154b22104fae1ed453c67d15930af36fcc Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 23 Jun 2020 22:14:31 +0300 Subject: [PATCH 79/83] Ensure tests directory is not included in package --- MANIFEST.in | 2 ++ setup.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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/setup.py b/setup.py index c94b296..0ecc156 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ 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.2", "voluptuous", From 1ac04f040b520f1e3d02363ad5b471efe723a5cf Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 23 Jun 2020 22:18:55 +0300 Subject: [PATCH 80/83] Update packages --- requirements/dev.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 33fd7a0..cc76f5d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -6,7 +6,7 @@ # appdirs==1.4.4 # via black, virtualenv -asgiref==3.2.8 # via django +asgiref==3.2.10 # via django astroid==2.4.2 # via pylint attrs==19.3.0 # via black autopep8==1.5.3 @@ -32,26 +32,26 @@ fastdiff==0.2.0 # via snapshottest filelock==3.0.12 # via tox, virtualenv flake8==3.8.3 freezegun==0.3.15 -identify==1.4.19 # via pre-commit +identify==1.4.20 # via pre-commit importlib-metadata==1.6.1 # via flake8, importlib-resources, pluggy, pre-commit, tox, virtualenv importlib-resources==2.0.1 # via pre-commit, virtualenv -ipdb==0.13.2 +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 @@ -84,10 +84,10 @@ 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.23 # via pre-commit, tox +virtualenv==20.0.25 # via pre-commit, tox voluptuous==0.11.7 wasmer==0.4.1 # via fastdiff -wcwidth==0.2.4 # via prompt-toolkit +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 From ef42bc5dd4a3e2af82a4b15cd47671a9f3702865 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 23 Jun 2020 22:19:13 +0300 Subject: [PATCH 81/83] Fix pylint docstring issue --- setup.py | 4 +--- small_small_hr/__init__.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 0ecc156..1a163a3 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,4 @@ -""" -Setup.py for small_small_hr -""" +"""Setup.py for small_small_hr.""" from os import path from setuptools import find_packages, setup diff --git a/small_small_hr/__init__.py b/small_small_hr/__init__.py index 18c3d3e..1d0da64 100644 --- a/small_small_hr/__init__.py +++ b/small_small_hr/__init__.py @@ -1,6 +1,4 @@ -""" -Main init file for small_small_hr -""" +"""Main init file for small_small_hr.""" VERSION = (0, 2, 0) __version__ = ".".join(str(v) for v in VERSION) # pylint: disable=invalid-name From bba39e5ac6a98abd0407aa916e2e99ff7a7c6f40 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 23 Jun 2020 22:31:28 +0300 Subject: [PATCH 82/83] Update django-model-reviews --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index cc76f5d..1e597e5 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -22,7 +22,7 @@ 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.1 +django-model-reviews==1.0.2 django-mptt==0.11.0 django-phonenumber-field==4.0.0 django-private-storage==2.2.2 From 39dc6ad30abce095a3058d1e9db55f9c386fe8f0 Mon Sep 17 00:00:00 2001 From: Kelvin Jayanoris Date: Tue, 23 Jun 2020 22:32:25 +0300 Subject: [PATCH 83/83] Update setup.py --- setup.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 1a163a3..0300348 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,34 @@ """Setup.py for small_small_hr.""" -from os import path +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" + 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",