Skip to content

Commit

Permalink
Merge pull request #1061 from Menda/139-new-wflvl2-fields
Browse files Browse the repository at this point in the history
 Add new fields to WorkflowLevel2
  • Loading branch information
jefmoura committed Mar 23, 2018
2 parents 563c976 + 39223bd commit b9b6db9
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 45 deletions.
1 change: 1 addition & 0 deletions requirements/pkg.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ django-oauth-toolkit==1.0.0
django-simple-history==1.9.0
elasticsearch==5.4.0
factory_boy==2.9.2
voluptuous==0.11.1
4 changes: 2 additions & 2 deletions tola/management/commands/loadinitialdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.conf import settings
from django.contrib.sites.models import Site
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.db import transaction, IntegrityError, connection
Expand Down Expand Up @@ -2961,7 +2961,7 @@ def handle(self, *args, **options):
self._create_collected_data()
self._create_workflowlevel1_sectors()
self._create_workflowteams()
except IntegrityError:
except (IntegrityError, ValidationError):
msg = ("Error: the data could not be populated in the "
"database. Check that the affected database tables are "
"empty.")
Expand Down
4 changes: 2 additions & 2 deletions tola/management/commands/tests/test_loadinitialdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys

from django.contrib.auth.models import Group, User
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.management import call_command
from django.db import IntegrityError, connection
from django.test import TestCase, override_settings, tag
Expand Down Expand Up @@ -109,7 +109,7 @@ def test_load_demo_data_two_times_crashes_but_db_keeps_consistent(self):

User.objects.all().delete()

with self.assertRaises(IntegrityError):
with self.assertRaises(ValidationError):
call_command('loadinitialdata', *args, **opts)

self.assertRaises(User.DoesNotExist, User.objects.get,
Expand Down
58 changes: 58 additions & 0 deletions workflow/migrations/0021_auto_20180323_0215.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2018-03-23 09:15
from __future__ import unicode_literals

import django.contrib.postgres.fields.hstore
from django.contrib.postgres.operations import HStoreExtension
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('workflow', '0020_product'),
]

operations = [
HStoreExtension(), # https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/fields/#hstorefield
migrations.AddField(
model_name='historicalworkflowlevel2',
name='address',
field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, help_text='Address object with the structure: street (string), house_number (string), postal_code: (string), city (string), country (string)', null=True),
),
migrations.AddField(
model_name='historicalworkflowlevel2',
name='notes',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='historicalworkflowlevel2',
name='site_instructions',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='historicalworkflowlevel2',
name='type',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AddField(
model_name='workflowlevel2',
name='address',
field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, help_text='Address object with the structure: street (string), house_number (string), postal_code: (string), city (string), country (string)', null=True),
),
migrations.AddField(
model_name='workflowlevel2',
name='notes',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='workflowlevel2',
name='site_instructions',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='workflowlevel2',
name='type',
field=models.CharField(blank=True, max_length=50, null=True),
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class Migration(migrations.Migration):

dependencies = [
('workflow', '0020_product'),
('workflow', '0021_auto_20180323_0215'),
]

operations = [
Expand Down
98 changes: 61 additions & 37 deletions workflow/models.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
from __future__ import unicode_literals
from decimal import Decimal
import uuid

from django.db import models
from django.conf import settings
from django.contrib.postgres import fields
from django.contrib.auth.models import User, Group
from django.contrib.postgres.fields import HStoreField, JSONField
from django.contrib.sites.models import Site
from decimal import Decimal
import uuid

from django.conf import settings
from simple_history.models import HistoricalRecords
from django.contrib.postgres.fields import JSONField

from search.utils import ElasticsearchIndexer

from django.core.exceptions import ValidationError
from django.db.models import Q
try:
from django.utils import timezone
except ImportError:
from datetime import datetime as timezone
from django.db.models import Q
from simple_history.models import HistoricalRecords
from voluptuous import Schema, All, Any, Length

from search.utils import ElasticsearchIndexer

ROLE_ORGANIZATION_ADMIN = 'OrgAdmin'
ROLE_PROGRAM_ADMIN = 'ProgramAdmin'
Expand Down Expand Up @@ -1109,44 +1109,50 @@ def get_queryset(self):


class WorkflowLevel2(models.Model):
level2_uuid = models.CharField(max_length=255, verbose_name='WorkflowLevel2 UUID', default=uuid.uuid4, unique=True, blank=True, help_text="Unique ID")
workflowlevel1 = models.ForeignKey(WorkflowLevel1, verbose_name="Program", related_name="workflowlevel2", help_text="Primary Workflow")
parent_workflowlevel2 = models.IntegerField("Parent", default=0, blank=True)
milestone = models.ForeignKey("Milestone", null=True, blank=True, on_delete=models.SET_NULL, help_text="Association with a Workflow Level 1 Milestone")
name = models.CharField("Name",max_length=255)
sector = models.ForeignKey("Sector", verbose_name="Sector", blank=True, null=True, related_name="workflow2_sector", on_delete=models.SET_NULL, help_text="Primary Sector or type of work")
sub_sector = models.ManyToManyField("Sector", verbose_name="Sub-Sector", blank=True, related_name="workflowlevel2_sub_sector", help_text="Secondary sector or type of work")
description = models.TextField("Description", blank=True, null=True, help_text="Description of the overall effort")
site = models.ManyToManyField(SiteProfile, blank=True, help_text="Geographic sites or locations")
short_name = models.CharField("Code", max_length=20, blank=True, null=True , help_text="Shortened name autogenerated")
office = models.ForeignKey(Office, verbose_name="Office", null=True, blank=True, on_delete=models.SET_NULL, help_text="Primary office for effort")
staff_responsible = models.ForeignKey(TolaUser, on_delete=models.SET_NULL, blank=True, null=True, help_text="Responsible party")
stakeholder = models.ManyToManyField(Stakeholder, verbose_name="Stakeholders", blank=True, help_text="Other parties involved in effort")
effect_or_impact = models.TextField("What is the anticipated Outcome or Goal?", blank=True, null=True, help_text="Descriptive, what is the anticipated outcome of the effort")
expected_start_date = models.DateTimeField("Expected starting date", blank=True, null=True)
expected_end_date = models.DateTimeField("Expected ending date", blank=True, null=True)
total_estimated_budget = models.DecimalField("Total Project Budget", decimal_places=2, max_digits=12, help_text="Total budget to date calculated from Budget Module", default=Decimal("0.00"), blank=True)
local_currency = models.ForeignKey(Currency, null=True, blank=True, related_name="local_project", on_delete=models.SET_NULL, help_text="Primary Currency")
donor_currency = models.ForeignKey(Currency, null=True, blank=True, related_name="donor_project", on_delete=models.SET_NULL, help_text="Secondary Currency")
approval = models.ManyToManyField(ApprovalWorkflow, blank=True, help_text="Multiple approval level and users")
justification_background = models.TextField("General Background and Problem Statement", blank=True, null=True, help_text="Descriptive, why are we starting this effort")
risks_assumptions = models.TextField("Risks and Assumptions", blank=True, null=True, help_text="Descriptive, what are the risks associated")
description_of_government_involvement = models.TextField(blank=True, null=True, help_text="Descriptive, what government entities might be involved")
description_of_community_involvement = models.TextField(blank=True, null=True, help_text="Descriptive, what community orgs are groups are involved")
actual_start_date = models.DateTimeField(blank=True, null=True)
actual_end_date = models.DateTimeField(blank=True, null=True)
actual_duration = models.CharField(max_length=255, blank=True, null=True)
actual_cost = models.DecimalField("Actual Cost", decimal_places=2, max_digits=20, default=Decimal("0.00"), blank=True, help_text="Cost to date calculated from Budget Module")
total_cost = models.DecimalField("Estimated Budget for Organization", decimal_places=2, max_digits=12, help_text="In USD", default=Decimal("0.00"), blank=True)
address = HStoreField(blank=True, null=True, help_text="Address object with the structure: street (string), house_number (string), postal_code: (string), city (string), country (string)")
capacity_built = models.TextField("Describe how sustainability was ensured for this project?", max_length=755, blank=True, null=True, help_text="Descriptive, did this help increases internal or external capacity")
quality_assured = models.TextField("How was quality assured for this project", max_length=755, blank=True, null=True, help_text="Descriptive, how was the overall quality assured for this effort")
description = models.TextField("Description", blank=True, null=True, help_text="Description of the overall effort")
description_of_community_involvement = models.TextField(blank=True, null=True, help_text="Descriptive, what community orgs are groups are involved")
description_of_government_involvement = models.TextField(blank=True, null=True, help_text="Descriptive, what government entities might be involved")
expected_end_date = models.DateTimeField("Expected ending date", blank=True, null=True)
expected_start_date = models.DateTimeField("Expected starting date", blank=True, null=True)
issues_and_challenges = models.TextField("List any issues or challenges faced (include reasons for delays)", blank=True, null=True, help_text="Descriptive, what are some of the issues and challenges")
justification_background = models.TextField("General Background and Problem Statement", blank=True, null=True, help_text="Descriptive, why are we starting this effort")
lessons_learned = models.TextField("Lessons learned", blank=True, null=True, help_text="Descriptive, when completed what lessons were learned")
level2_uuid = models.CharField(max_length=255, verbose_name='WorkflowLevel2 UUID', default=uuid.uuid4, unique=True, blank=True, help_text="Unique ID")
name = models.CharField("Name", max_length=255)
notes = models.TextField(blank=True, null=True)
parent_workflowlevel2 = models.IntegerField("Parent", default=0, blank=True)
quality_assured = models.TextField("How was quality assured for this project", max_length=755, blank=True, null=True, help_text="Descriptive, how was the overall quality assured for this effort")
risks_assumptions = models.TextField("Risks and Assumptions", blank=True, null=True, help_text="Descriptive, what are the risks associated")
short_name = models.CharField("Code", max_length=20, blank=True, null=True, help_text="Shortened name autogenerated")
site_instructions = models.TextField(blank=True, null=True)
total_cost = models.DecimalField("Estimated Budget for Organization", decimal_places=2, max_digits=12, help_text="In USD", default=Decimal("0.00"), blank=True)
total_estimated_budget = models.DecimalField("Total Project Budget", decimal_places=2, max_digits=12, help_text="Total budget to date calculated from Budget Module", default=Decimal("0.00"), blank=True)
type = models.CharField(max_length=50, blank=True, null=True)

approval = models.ManyToManyField(ApprovalWorkflow, blank=True, help_text="Multiple approval level and users")
donor_currency = models.ForeignKey(Currency, null=True, blank=True, related_name="donor_project", on_delete=models.SET_NULL, help_text="Secondary Currency")
effect_or_impact = models.TextField("What is the anticipated Outcome or Goal?", blank=True, null=True, help_text="Descriptive, what is the anticipated outcome of the effort")
indicators = models.ManyToManyField("indicators.Indicator", blank=True)
local_currency = models.ForeignKey(Currency, null=True, blank=True, related_name="local_project", on_delete=models.SET_NULL, help_text="Primary Currency")
milestone = models.ForeignKey("Milestone", null=True, blank=True, on_delete=models.SET_NULL, help_text="Association with a Workflow Level 1 Milestone")
office = models.ForeignKey(Office, verbose_name="Office", null=True, blank=True, on_delete=models.SET_NULL, help_text="Primary office for effort")
# products = OneToMany(Product) # reverse related relationship
sector = models.ForeignKey("Sector", verbose_name="Sector", blank=True, null=True, related_name="workflow2_sector", on_delete=models.SET_NULL, help_text="Primary Sector or type of work")
site = models.ManyToManyField(SiteProfile, blank=True, help_text="Geographic sites or locations")
staff_responsible = models.ForeignKey(TolaUser, on_delete=models.SET_NULL, blank=True, null=True, help_text="Responsible party")
stakeholder = models.ManyToManyField(Stakeholder, verbose_name="Stakeholders", blank=True, help_text="Other parties involved in effort")
sub_sector = models.ManyToManyField("Sector", verbose_name="Sub-Sector", blank=True, related_name="workflowlevel2_sub_sector", help_text="Secondary sector or type of work")
workflowlevel1 = models.ForeignKey(WorkflowLevel1, verbose_name="Program", related_name="workflowlevel2", help_text="Primary Workflow")

create_date = models.DateTimeField("Date Created", null=True, blank=True)
edit_date = models.DateTimeField("Last Edit Date", null=True, blank=True)
created_by = models.ForeignKey('auth.User', related_name='workflowlevel2', null=True, blank=True, on_delete=models.SET_NULL)
edit_date = models.DateTimeField("Last Edit Date", null=True, blank=True)

history = HistoricalRecords()
objects = WorkflowLevel2Manager()
Expand Down Expand Up @@ -1188,6 +1194,24 @@ class Meta:
("can_approve", "Can approve initiation"),
)

def _validate_address(self, address):
schema = Schema({
'street': All(Any(str, unicode), Length(max=100)),
'house_number': All(Any(str, unicode), Length(max=20)),
'postal_code': All(Any(str, unicode), Length(max=20)),
'city': All(Any(str, unicode), Length(max=85)),
'country': All(Any(str, unicode), Length(max=50)),
})
schema(address)

def clean_fields(self, exclude=None):
super(WorkflowLevel2, self).clean_fields(exclude=exclude)
if self.address:
try:
self._validate_address(self.address)
except Exception as error:
raise ValidationError(error)

def save(self, *args, **kwargs):
if self.create_date is None:
self.create_date = timezone.now()
Expand Down
11 changes: 8 additions & 3 deletions workflow/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
from tola import DEMO_BRANCH, track_sync as tsync
from tola.management.commands.loadinitialdata import DEFAULT_WORKFLOW_LEVEL_1S
from workflow.models import (Organization, TolaUser, WorkflowLevel1,
WorkflowTeam, ROLE_ORGANIZATION_ADMIN,
ROLE_PROGRAM_ADMIN, ROLE_PROGRAM_TEAM,
ROLE_VIEW_ONLY)
WorkflowTeam, WorkflowLevel2,
ROLE_ORGANIZATION_ADMIN, ROLE_PROGRAM_ADMIN,
ROLE_PROGRAM_TEAM, ROLE_VIEW_ONLY)

logger = logging.getLogger(__name__)


@receiver(signals.pre_save, sender=WorkflowLevel2)
def pre_save_handler(sender, instance, *args, **kwargs):
instance.full_clean()


def get_addon_by_id(addon_id, addons):
for addon in addons:
if addon.id == addon_id:
Expand Down
22 changes: 22 additions & 0 deletions workflow/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings, tag

import factories
Expand Down Expand Up @@ -89,3 +90,24 @@ class WorkflowLevel2Test(TestCase):
def test_print_instance(self):
wflvl2 = factories.WorkflowLevel2.build()
self.assertEqual(unicode(wflvl2), u'Help Syrians')

def test_save_address_fail(self):
wflvl2 = factories.WorkflowLevel2()
wflvl2.address = {
'street': None,
}
self.assertRaises(ValidationError, wflvl2.save)

wflvl2.address = {
'house_number': 'a'*21,
}
self.assertRaises(ValidationError, wflvl2.save)

def test_save_address(self):
factories.WorkflowLevel2(address={
'street': 'Oderberger Straße',
'house_number': '16A',
'postal_code': '10435',
'city': 'Berlin',
'country': 'Germany',
})

0 comments on commit b9b6db9

Please sign in to comment.