Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion employees/common/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from utils.decorators import notCallable


MAX_DECIMAL_VALUE_VALIDATOR_MESSAGE = ugettext_lazy('Ensure this value is less than or equal to ')
MAX_DECIMAL_VALUE_VALIDATOR_MESSAGE = ugettext_lazy('Minutes cannot be greater than 59.')
MAX_HOURS_VALUE_VALIDATOR_MESSAGE = ugettext_lazy('This value cannot be greater than 24:00.')
MIN_HOURS_VALUE_VALIDATOR_MESSAGE = ugettext_lazy('This value must be greater than 0.')


@notCallable
Expand Down
2 changes: 1 addition & 1 deletion employees/common/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


class MaxDecimalValueValidator(BaseValidator):
message = MAX_DECIMAL_VALUE_VALIDATOR_MESSAGE + '%(limit_value)s.'
message = MAX_DECIMAL_VALUE_VALIDATOR_MESSAGE
code = 'max_decimal_value'

def compare(self, a, b):
Expand Down
21 changes: 21 additions & 0 deletions employees/migrations/0003_auto_20190322_1321.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 2.1.1 on 2019-03-22 13:21

from decimal import Decimal
import django.core.validators
from django.db import migrations, models
import employees.common.validators


class Migration(migrations.Migration):

dependencies = [
('employees', '0002_auto_20190313_1636'),
]

operations = [
migrations.AlterField(
model_name='report',
name='work_hours',
field=models.DecimalField(decimal_places=2, max_digits=4, validators=[django.core.validators.MinValueValidator(Decimal('0.01'), message='This value must be greater than 0.'), django.core.validators.MaxValueValidator(Decimal('24.00'), message='This value cannot be greater than 24:00.'), employees.common.validators.MaxDecimalValueValidator(Decimal('0.59'))]),
),
]
20 changes: 17 additions & 3 deletions employees/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.db import models

from employees.common.constants import ReportModelConstants
from employees.common.strings import MAX_HOURS_VALUE_VALIDATOR_MESSAGE
from employees.common.strings import MIN_HOURS_VALUE_VALIDATOR_MESSAGE
from employees.common.validators import MaxDecimalValueValidator
from managers.models import Project
from users.models import CustomUser
Expand Down Expand Up @@ -31,11 +33,23 @@ class Report(models.Model):
max_digits=ReportModelConstants.MAX_DIGITS.value,
decimal_places=ReportModelConstants.DECIMAL_PLACES.value,
validators=[
MinValueValidator(ReportModelConstants.MIN_WORK_HOURS.value),
MaxValueValidator(ReportModelConstants.MAX_WORK_HOURS.value),
MaxDecimalValueValidator(ReportModelConstants.MAX_WORK_HOURS_DECIMAL_VALUE.value),
MinValueValidator(
ReportModelConstants.MIN_WORK_HOURS.value,
message=MIN_HOURS_VALUE_VALIDATOR_MESSAGE,
),
MaxValueValidator(
ReportModelConstants.MAX_WORK_HOURS.value,
message=MAX_HOURS_VALUE_VALIDATOR_MESSAGE,
),
MaxDecimalValueValidator(
ReportModelConstants.MAX_WORK_HOURS_DECIMAL_VALUE.value,
),
]
)
editable = models.BooleanField(
default=True,
)

@property
def work_hours_str(self):
return self.work_hours.to_eng_string().replace('.', ':')
31 changes: 31 additions & 0 deletions employees/serializers.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
from rest_framework import serializers

from employees.common.constants import ReportModelConstants
from employees.common.strings import MAX_HOURS_VALUE_VALIDATOR_MESSAGE
from employees.common.strings import MIN_HOURS_VALUE_VALIDATOR_MESSAGE
from employees.common.validators import MaxDecimalValueValidator
from employees.models import Report
from managers.models import Project


class HoursField(serializers.DecimalField):
def __init__(self, **kwargs):
super().__init__(
max_value=ReportModelConstants.MAX_WORK_HOURS.value,
min_value=ReportModelConstants.MIN_WORK_HOURS.value,
max_digits=ReportModelConstants.MAX_DIGITS.value,
decimal_places=ReportModelConstants.DECIMAL_PLACES.value,
validators=[MaxDecimalValueValidator(ReportModelConstants.MAX_WORK_HOURS_DECIMAL_VALUE.value)],
**kwargs)
self.validators[1].message = MAX_HOURS_VALUE_VALIDATOR_MESSAGE
self.validators[2].message = MIN_HOURS_VALUE_VALIDATOR_MESSAGE

def to_internal_value(self, data):
if isinstance(data, str) and ':' in data:
converted = data.replace(':', '.')
return super().to_internal_value(converted)
return super().to_internal_value(data)


class ReportSerializer(serializers.HyperlinkedModelSerializer):

project = serializers.SlugRelatedField(
queryset=Project.objects.all(),
slug_field='name',
)
author = serializers.StringRelatedField()

description = serializers.CharField(
style={'base_template': 'textarea.html'},
max_length=ReportModelConstants.MAX_DESCRIPTION_LENGTH.value
)

work_hours = HoursField()

class Meta:
model = Report
fields = (
Expand All @@ -27,3 +52,9 @@ class Meta:
'description',
'work_hours',
)

def to_representation(self, instance):
data = super().to_representation(instance)
if isinstance(self.instance, Report):
data['work_hours'] = self.instance.work_hours_str
return data
2 changes: 1 addition & 1 deletion employees/templates/employees/report_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ <h1>{{ UI_text.PAGE_TITLE.value }}</h1>
{{ report.project }}
</td>
<td class="work-hours-column">
{{ report.work_hours }}
{{ report.work_hours_str }}
</td>
<td class="description-column">
{{ report.description }}
Expand Down
5 changes: 5 additions & 0 deletions employees/tests/test_unit_report_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,8 @@ def test_report_model_work_hours_field_should_not_accept_value_exceeding_set_min
# PARAM
def test_report_model_work_hours_field_should_not_accept_decimal_value_exceeding_set_maximum(self):
self.field_should_not_accept_input('work_hours', ReportModelConstants.MAX_WORK_HOURS_DECIMAL_VALUE.value + Decimal('0.01'))

# PARAM
def test_report_model_work_hours_str_property_should_return_work_hours_field_value_as_string_with_colon_instead_of_dot(self):
report = self.initiate_model('work_hours', Decimal('8.00'))
self.assertEqual(report.work_hours_str, '8:00')
49 changes: 46 additions & 3 deletions employees/tests/test_unit_report_serializer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import datetime
from decimal import Decimal

from django.test import TestCase
from rest_framework.reverse import reverse
from rest_framework.test import APIRequestFactory

from employees.common.constants import ReportModelConstants
from employees.common.strings import MAX_DECIMAL_VALUE_VALIDATOR_MESSAGE
from employees.common.strings import MAX_HOURS_VALUE_VALIDATOR_MESSAGE
from employees.common.strings import MIN_HOURS_VALUE_VALIDATOR_MESSAGE
from employees.models import Report
from employees.serializers import HoursField
from employees.serializers import ReportSerializer
from managers.models import Project
from users.models import CustomUser
Expand Down Expand Up @@ -88,16 +97,50 @@ def test_report_serializer_work_hours_field_should_not_accept_value_exceeding_se
self.field_should_not_accept_input(field='work_hours', value=generate_decimal_with_decimal_places(decimal_places=ReportModelConstants.DECIMAL_PLACES.value + 1))

def test_report_serializer_work_hours_field_should_not_accept_value_exceeding_set_maximum(self):
self.field_should_not_accept_input(field='work_hours', value=ReportModelConstants.MAX_WORK_HOURS.value + Decimal('0.01'))
self.field_should_not_accept_input(
field='work_hours',
value=ReportModelConstants.MAX_WORK_HOURS.value + Decimal('0.01'),
error_message=MAX_HOURS_VALUE_VALIDATOR_MESSAGE,
)

def test_report_serializer_work_hours_field_should_not_accept_value_exceeding_set_minimum(self):
self.field_should_not_accept_input(field='work_hours', value=ReportModelConstants.MIN_WORK_HOURS.value - Decimal('0.01'))
self.field_should_not_accept_input(
field='work_hours',
value=ReportModelConstants.MIN_WORK_HOURS.value - Decimal('0.01'),
error_message=MIN_HOURS_VALUE_VALIDATOR_MESSAGE,
)

def test_report_serializer_work_hours_field_should_not_accept_decimal_value_exceeding_set_maximum(self):
self.field_should_not_accept_input(field='work_hours', value=ReportModelConstants.MAX_WORK_HOURS_DECIMAL_VALUE.value + Decimal('0.01'))
self.field_should_not_accept_input(
field='work_hours',
value=ReportModelConstants.MAX_WORK_HOURS_DECIMAL_VALUE.value + Decimal('0.01'),
error_message=MAX_DECIMAL_VALUE_VALIDATOR_MESSAGE,
)

def test_report_serializer_work_hours_should_not_accept_non_numeric_value(self):
self.field_should_not_accept_input(field='work_hours', value=self.SAMPLE_STRING_FOR_TYPE_VALIDATION_TESTS)

def test_report_serializer_work_hours_field_should_not_be_empty(self):
self.field_should_not_accept_null(field='work_hours')

def test_report_serializer_to_representation_method_should_replace_work_hours_with_string_containing_colon_instead_of_dot(self):
report = Report(
date=datetime.datetime.now().date(),
description='Some description',
author=self.required_input['author'],
project=self.required_input['project'],
work_hours=Decimal('8.00'),
)
report.full_clean()
report.save()
request = APIRequestFactory().get(path=reverse('custom-report-detail', args=(report.pk,)))
serializer = ReportSerializer(report, context={'request': request})
data = serializer.to_representation(report)
self.assertEqual(data['work_hours'], '8:00')


class HoursFieldTests(TestCase):
def test_to_internal_value_should_change_string_with_colon_representing_hour_to_numeric_value_with_dot_separator(self):
hours_field = HoursField()
data = hours_field.to_internal_value('8:00')
self.assertEqual(data, Decimal('8.00'))
11 changes: 7 additions & 4 deletions utils/base_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,11 @@ def default_serializer(self):
"""
Private method containing base code for running serializer validation tests.
"""
def _field_input_acceptance_test(self, field, value, is_valid):
def _field_input_acceptance_test(self, field, value, is_valid, error_message=None):
serializer = self.initiate_serializer(field, value)
self.assertEqual(serializer.is_valid(), is_valid)
if error_message is not None:
self.assertEqual(str(serializer.errors[field][0]), error_message)

"""
Test that putting specified value in specified field should result in successful serializer validation.
Expand All @@ -189,17 +191,18 @@ def field_should_accept_input(self, field, value):
self._field_input_acceptance_test(
field=field,
value=value,
is_valid=True
is_valid=True,
)

"""
Test that putting value in specified field should not result in successful serializer validation.
"""
def field_should_not_accept_input(self, field, value):
def field_should_not_accept_input(self, field, value, error_message=None):
self._field_input_acceptance_test(
field=field,
value=value,
is_valid=False
is_valid=False,
error_message=error_message,
)

"""
Expand Down