projectal.entities.task API documentation

# projectal.entities.task

View Source

      1import datetime
      2
      3import projectal
      4from projectal.entity import Entity
      5from projectal.enums import DateLimit
      6from projectal.linkers import *
      7
      8
      9class Task(Entity, ResourceLinker, SkillLinker, FileLinker, StageLinker,
     10           StaffLinker, RebateLinker, NoteLinker, TagLinker):
     11    """
     12    Implementation of the [Task](https://projectal.com/docs/latest/#tag/Task) API.
     13    """
     14    _path = 'task'
     15    _name = 'task'
     16    _links = [ResourceLinker, SkillLinker, FileLinker, StageLinker, StaffLinker, RebateLinker,
     17              NoteLinker, TagLinker]
     18
     19    @classmethod
     20    def create(cls, holder, entities):
     21        """Create a Task
     22
     23        `holder`: An instance or the `uuId` of the owner
     24
     25        `entities`: `dict` containing the fields of the entity to be created,
     26        or a list of such `dict`s to create in bulk.
     27        """
     28        holder_id = holder['uuId'] if isinstance(holder, dict) else holder
     29        params = "?holder=" + holder_id
     30        out = super().create(entities, params)
     31
     32        # Tasks should always refer to their parent and project. We don't get this information
     33        # from the creation api method, but we can insert them ourselves because we know what
     34        # they are.
     35        def add_fields(obj):
     36            obj.set_readonly('projectRef', holder_id)
     37            obj.set_readonly('parent', obj.get('parent', holder_id))
     38
     39        if isinstance(out, dict):
     40            add_fields(out)
     41        if isinstance(out, list):
     42            for obj in out:
     43                add_fields(obj)
     44        return out
     45
     46    def update_order(self, order_at_uuId, order_as=True):
     47        url = "/api/task/update?order-at={}&order-as={}".format(
     48            order_at_uuId, 'true' if order_as else 'false')
     49        return api.put(url, [{'uuId': self['uuId']}])
     50
     51    def link_predecessor_task(self, predecessor_task):
     52        return self.__plan(self, predecessor_task, 'add')
     53
     54    def relink_predecessor_task(self, predecessor_task):
     55        return self.__plan(self, predecessor_task, 'update')
     56
     57    def unlink_predecessor_task(self, predecessor_task):
     58        return self.__plan(self, predecessor_task, 'delete')
     59
     60    @classmethod
     61    def __plan(cls, from_task, to_task, operation):
     62        url = '/api/task/plan/task/{}'.format(operation)
     63        payload = {
     64            'uuId': from_task['uuId'],
     65            'taskList': [to_task]
     66        }
     67        api.post(url, payload=payload)
     68        return True
     69
     70    def parents(self):
     71        """
     72        Return an ordered list of [name, uuId] pairs of this task's parents, up to
     73        (but not including) the root of the project.
     74        """
     75        payload = {
     76            "name": "Task Parents",
     77            "type": "msql",
     78            "start": 0,
     79            "limit": -1,
     80            "holder": "{}".format(self['uuId']),
     81            "select": [
     82                [
     83                    "TASK(one).PARENT_ALL_TASK.name"
     84                ],
     85                [
     86                    "TASK(one).PARENT_ALL_TASK.uuId"
     87                ]
     88            ],
     89        }
     90        list = api.query(payload)
     91        # Results come back in reverse order. Flip them around
     92        list.reverse()
     93        return list
     94
     95    def project_uuId(self):
     96        """Return the `uuId` of the Project that holds this Task."""
     97        payload = {
     98            "name": "Project that holds this task",
     99            "type": "msql", "start": 0, "limit": 1,
    100            "holder": "{}".format(self['uuId']),
    101            "select": [
    102                ["TASK.PROJECT.uuId"]
    103            ],
    104        }
    105        projects = api.query(payload)
    106        for t in projects:
    107            return t[0]
    108        return None
    109
    110    @classmethod
    111    def add_task_template(cls, project, template):
    112        """Insert TaskTemplate `template` into Project `project`"""
    113        url = '/api/task/task_template/add?override=false&group=false'
    114        payload = {
    115            'uuId': project['uuId'],
    116            'templateList': [template]
    117        }
    118        api.post(url, payload)
    119
    120    def reset_duration(self, calendars=None):
    121        """Set this task's duration based on its start and end dates while
    122        taking into account the calendar for weekends and scheduled time off.
    123
    124        calendars is expected to be the list of calendar objects for the
    125        location of the project that holds this task. You may provide this
    126        list yourself for efficiency (recommended) - if not provided, it
    127        will be fetched for you by issuing requests to the server.
    128        """
    129        if not calendars:
    130            if 'projectRef' not in self:
    131                task = projectal.Task.get(self)
    132                project_ref = task['projectRef']
    133            else:
    134                project_ref = self['projectRef']
    135            project = projectal.Project.get(project_ref, links=['LOCATION'])
    136            for location in project.get('locationList', []):
    137                calendars = location.calendar()
    138                break
    139
    140        start = self.get('startTime')
    141        end = self.get('closeTime')
    142        if not start or start == DateLimit.Min:
    143            return 0
    144        if not end or end == DateLimit.Max:
    145            return 0
    146
    147        # Build a list of weekday names that are non-working
    148        base_non_working = set()
    149        location_non_working = set()
    150        for calendar in calendars:
    151            if calendar['name'] == "base_calendar":
    152                for item in calendar['calendarList']:
    153                    if not item['isWorking']:
    154                        base_non_working.add(item['type'])
    155
    156            if calendar['name'] == "location":
    157                for item in calendar['calendarList']:
    158                    if not item['isWorking']:
    159                        # TODO: handle multi-day exceptions and times
    160                        location_non_working.add(item['startDate'])
    161
    162        start = datetime.datetime.fromtimestamp(start / 1000)
    163        end = datetime.datetime.fromtimestamp(end / 1000)
    164        hours = 0
    165        current = start
    166        while current <= end:
    167            if current.strftime('%A') in base_non_working:
    168                current += datetime.timedelta(days=1)
    169                continue
    170            if current.strftime('%Y-%m-%d') in location_non_working:
    171                current += datetime.timedelta(days=1)
    172                continue
    173            hours += 8
    174            current += datetime.timedelta(days=1)
    175
    176        self['duration'] = hours * 60

class Task(projectal.entity.Entity, projectal.linkers.ResourceLinker, projectal.linkers.SkillLinker, projectal.linkers.FileLinker, projectal.linkers.StageLinker, projectal.linkers.StaffLinker, projectal.linkers.RebateLinker, projectal.linkers.NoteLinker, projectal.linkers.TagLinker): View Source

     10class Task(Entity, ResourceLinker, SkillLinker, FileLinker, StageLinker,
     11           StaffLinker, RebateLinker, NoteLinker, TagLinker):
     12    """
     13    Implementation of the [Task](https://projectal.com/docs/latest/#tag/Task) API.
     14    """
     15    _path = 'task'
     16    _name = 'task'
     17    _links = [ResourceLinker, SkillLinker, FileLinker, StageLinker, StaffLinker, RebateLinker,
     18              NoteLinker, TagLinker]
     19
     20    @classmethod
     21    def create(cls, holder, entities):
     22        """Create a Task
     23
     24        `holder`: An instance or the `uuId` of the owner
     25
     26        `entities`: `dict` containing the fields of the entity to be created,
     27        or a list of such `dict`s to create in bulk.
     28        """
     29        holder_id = holder['uuId'] if isinstance(holder, dict) else holder
     30        params = "?holder=" + holder_id
     31        out = super().create(entities, params)
     32
     33        # Tasks should always refer to their parent and project. We don't get this information
     34        # from the creation api method, but we can insert them ourselves because we know what
     35        # they are.
     36        def add_fields(obj):
     37            obj.set_readonly('projectRef', holder_id)
     38            obj.set_readonly('parent', obj.get('parent', holder_id))
     39
     40        if isinstance(out, dict):
     41            add_fields(out)
     42        if isinstance(out, list):
     43            for obj in out:
     44                add_fields(obj)
     45        return out
     46
     47    def update_order(self, order_at_uuId, order_as=True):
     48        url = "/api/task/update?order-at={}&order-as={}".format(
     49            order_at_uuId, 'true' if order_as else 'false')
     50        return api.put(url, [{'uuId': self['uuId']}])
     51
     52    def link_predecessor_task(self, predecessor_task):
     53        return self.__plan(self, predecessor_task, 'add')
     54
     55    def relink_predecessor_task(self, predecessor_task):
     56        return self.__plan(self, predecessor_task, 'update')
     57
     58    def unlink_predecessor_task(self, predecessor_task):
     59        return self.__plan(self, predecessor_task, 'delete')
     60
     61    @classmethod
     62    def __plan(cls, from_task, to_task, operation):
     63        url = '/api/task/plan/task/{}'.format(operation)
     64        payload = {
     65            'uuId': from_task['uuId'],
     66            'taskList': [to_task]
     67        }
     68        api.post(url, payload=payload)
     69        return True
     70
     71    def parents(self):
     72        """
     73        Return an ordered list of [name, uuId] pairs of this task's parents, up to
     74        (but not including) the root of the project.
     75        """
     76        payload = {
     77            "name": "Task Parents",
     78            "type": "msql",
     79            "start": 0,
     80            "limit": -1,
     81            "holder": "{}".format(self['uuId']),
     82            "select": [
     83                [
     84                    "TASK(one).PARENT_ALL_TASK.name"
     85                ],
     86                [
     87                    "TASK(one).PARENT_ALL_TASK.uuId"
     88                ]
     89            ],
     90        }
     91        list = api.query(payload)
     92        # Results come back in reverse order. Flip them around
     93        list.reverse()
     94        return list
     95
     96    def project_uuId(self):
     97        """Return the `uuId` of the Project that holds this Task."""
     98        payload = {
     99            "name": "Project that holds this task",
    100            "type": "msql", "start": 0, "limit": 1,
    101            "holder": "{}".format(self['uuId']),
    102            "select": [
    103                ["TASK.PROJECT.uuId"]
    104            ],
    105        }
    106        projects = api.query(payload)
    107        for t in projects:
    108            return t[0]
    109        return None
    110
    111    @classmethod
    112    def add_task_template(cls, project, template):
    113        """Insert TaskTemplate `template` into Project `project`"""
    114        url = '/api/task/task_template/add?override=false&group=false'
    115        payload = {
    116            'uuId': project['uuId'],
    117            'templateList': [template]
    118        }
    119        api.post(url, payload)
    120
    121    def reset_duration(self, calendars=None):
    122        """Set this task's duration based on its start and end dates while
    123        taking into account the calendar for weekends and scheduled time off.
    124
    125        calendars is expected to be the list of calendar objects for the
    126        location of the project that holds this task. You may provide this
    127        list yourself for efficiency (recommended) - if not provided, it
    128        will be fetched for you by issuing requests to the server.
    129        """
    130        if not calendars:
    131            if 'projectRef' not in self:
    132                task = projectal.Task.get(self)
    133                project_ref = task['projectRef']
    134            else:
    135                project_ref = self['projectRef']
    136            project = projectal.Project.get(project_ref, links=['LOCATION'])
    137            for location in project.get('locationList', []):
    138                calendars = location.calendar()
    139                break
    140
    141        start = self.get('startTime')
    142        end = self.get('closeTime')
    143        if not start or start == DateLimit.Min:
    144            return 0
    145        if not end or end == DateLimit.Max:
    146            return 0
    147
    148        # Build a list of weekday names that are non-working
    149        base_non_working = set()
    150        location_non_working = set()
    151        for calendar in calendars:
    152            if calendar['name'] == "base_calendar":
    153                for item in calendar['calendarList']:
    154                    if not item['isWorking']:
    155                        base_non_working.add(item['type'])
    156
    157            if calendar['name'] == "location":
    158                for item in calendar['calendarList']:
    159                    if not item['isWorking']:
    160                        # TODO: handle multi-day exceptions and times
    161                        location_non_working.add(item['startDate'])
    162
    163        start = datetime.datetime.fromtimestamp(start / 1000)
    164        end = datetime.datetime.fromtimestamp(end / 1000)
    165        hours = 0
    166        current = start
    167        while current <= end:
    168            if current.strftime('%A') in base_non_working:
    169                current += datetime.timedelta(days=1)
    170                continue
    171            if current.strftime('%Y-%m-%d') in location_non_working:
    172                current += datetime.timedelta(days=1)
    173                continue
    174            hours += 8
    175            current += datetime.timedelta(days=1)
    176
    177        self['duration'] = hours * 60

Implementation of the Task API.

@classmethod
def create(cls, holder, entities): View Source

    20    @classmethod
    21    def create(cls, holder, entities):
    22        """Create a Task
    23
    24        `holder`: An instance or the `uuId` of the owner
    25
    26        `entities`: `dict` containing the fields of the entity to be created,
    27        or a list of such `dict`s to create in bulk.
    28        """
    29        holder_id = holder['uuId'] if isinstance(holder, dict) else holder
    30        params = "?holder=" + holder_id
    31        out = super().create(entities, params)
    32
    33        # Tasks should always refer to their parent and project. We don't get this information
    34        # from the creation api method, but we can insert them ourselves because we know what
    35        # they are.
    36        def add_fields(obj):
    37            obj.set_readonly('projectRef', holder_id)
    38            obj.set_readonly('parent', obj.get('parent', holder_id))
    39
    40        if isinstance(out, dict):
    41            add_fields(out)
    42        if isinstance(out, list):
    43            for obj in out:
    44                add_fields(obj)
    45        return out

Create a Task

`holder`: An instance or the `uuId` of the owner

`entities`: `dict` containing the fields of the entity to be created, or a list of such `dict`s to create in bulk.

def update_order(self, order_at_uuId, order_as=True): View Source

    47    def update_order(self, order_at_uuId, order_as=True):
    48        url = "/api/task/update?order-at={}&order-as={}".format(
    49            order_at_uuId, 'true' if order_as else 'false')
    50        return api.put(url, [{'uuId': self['uuId']}])

def link_predecessor_task(self, predecessor_task): View Source

    52    def link_predecessor_task(self, predecessor_task):
    53        return self.__plan(self, predecessor_task, 'add')

def relink_predecessor_task(self, predecessor_task): View Source

    55    def relink_predecessor_task(self, predecessor_task):
    56        return self.__plan(self, predecessor_task, 'update')

def unlink_predecessor_task(self, predecessor_task): View Source

    58    def unlink_predecessor_task(self, predecessor_task):
    59        return self.__plan(self, predecessor_task, 'delete')

def parents(self): View Source

    71    def parents(self):
    72        """
    73        Return an ordered list of [name, uuId] pairs of this task's parents, up to
    74        (but not including) the root of the project.
    75        """
    76        payload = {
    77            "name": "Task Parents",
    78            "type": "msql",
    79            "start": 0,
    80            "limit": -1,
    81            "holder": "{}".format(self['uuId']),
    82            "select": [
    83                [
    84                    "TASK(one).PARENT_ALL_TASK.name"
    85                ],
    86                [
    87                    "TASK(one).PARENT_ALL_TASK.uuId"
    88                ]
    89            ],
    90        }
    91        list = api.query(payload)
    92        # Results come back in reverse order. Flip them around
    93        list.reverse()
    94        return list

Return an ordered list of [name, uuId] pairs of this task's parents, up to (but not including) the root of the project.

def project_uuId(self): View Source

     96    def project_uuId(self):
     97        """Return the `uuId` of the Project that holds this Task."""
     98        payload = {
     99            "name": "Project that holds this task",
    100            "type": "msql", "start": 0, "limit": 1,
    101            "holder": "{}".format(self['uuId']),
    102            "select": [
    103                ["TASK.PROJECT.uuId"]
    104            ],
    105        }
    106        projects = api.query(payload)
    107        for t in projects:
    108            return t[0]
    109        return None

Return the `uuId` of the Project that holds this Task.

@classmethod
def add_task_template(cls, project, template): View Source

    111    @classmethod
    112    def add_task_template(cls, project, template):
    113        """Insert TaskTemplate `template` into Project `project`"""
    114        url = '/api/task/task_template/add?override=false&group=false'
    115        payload = {
    116            'uuId': project['uuId'],
    117            'templateList': [template]
    118        }
    119        api.post(url, payload)

Insert TaskTemplate `template` into Project `project`

def reset_duration(self, calendars=None): View Source

    121    def reset_duration(self, calendars=None):
    122        """Set this task's duration based on its start and end dates while
    123        taking into account the calendar for weekends and scheduled time off.
    124
    125        calendars is expected to be the list of calendar objects for the
    126        location of the project that holds this task. You may provide this
    127        list yourself for efficiency (recommended) - if not provided, it
    128        will be fetched for you by issuing requests to the server.
    129        """
    130        if not calendars:
    131            if 'projectRef' not in self:
    132                task = projectal.Task.get(self)
    133                project_ref = task['projectRef']
    134            else:
    135                project_ref = self['projectRef']
    136            project = projectal.Project.get(project_ref, links=['LOCATION'])
    137            for location in project.get('locationList', []):
    138                calendars = location.calendar()
    139                break
    140
    141        start = self.get('startTime')
    142        end = self.get('closeTime')
    143        if not start or start == DateLimit.Min:
    144            return 0
    145        if not end or end == DateLimit.Max:
    146            return 0
    147
    148        # Build a list of weekday names that are non-working
    149        base_non_working = set()
    150        location_non_working = set()
    151        for calendar in calendars:
    152            if calendar['name'] == "base_calendar":
    153                for item in calendar['calendarList']:
    154                    if not item['isWorking']:
    155                        base_non_working.add(item['type'])
    156
    157            if calendar['name'] == "location":
    158                for item in calendar['calendarList']:
    159                    if not item['isWorking']:
    160                        # TODO: handle multi-day exceptions and times
    161                        location_non_working.add(item['startDate'])
    162
    163        start = datetime.datetime.fromtimestamp(start / 1000)
    164        end = datetime.datetime.fromtimestamp(end / 1000)
    165        hours = 0
    166        current = start
    167        while current <= end:
    168            if current.strftime('%A') in base_non_working:
    169                current += datetime.timedelta(days=1)
    170                continue
    171            if current.strftime('%Y-%m-%d') in location_non_working:
    172                current += datetime.timedelta(days=1)
    173                continue
    174            hours += 8
    175            current += datetime.timedelta(days=1)
    176
    177        self['duration'] = hours * 60

Set this task's duration based on its start and end dates while taking into account the calendar for weekends and scheduled time off.

calendars is expected to be the list of calendar objects for the location of the project that holds this task. You may provide this list yourself for efficiency (recommended) - if not provided, it will be fetched for you by issuing requests to the server.

##### Inherited Members

projectal.entity.Entity
  get
  update
  delete
  history
  save
  clone
  list
  match
  match_startswith
  match_endswith
  match_one
  match_startswith_one
  match_endswith_one
  search
  query
  profile_get
  profile_set
  changes
  set_readonly
  get_link_definitions
  entity_name
projectal.linkers.ResourceLinker
  link_resource
  relink_resource
  unlink_resource
projectal.linkers.SkillLinker
  link_skill
  relink_skill
  unlink_skill
projectal.linkers.FileLinker
  link_file
  unlink_file
projectal.linkers.StageLinker
  link_stage
  unlink_stage
projectal.linkers.StaffLinker
  link_staff
  relink_staff
  unlink_staff
projectal.linkers.RebateLinker
  link_rebate
  unlink_rebate
projectal.linkers.TagLinker
  link_tag
  unlink_tag
builtins.dict
  setdefault
  pop
  popitem
  keys
  items
  values
  fromkeys
  clear
  copy