oleh_korkh

apps/core/utils/toggl_api_integration.py

Dec 30th, 2020 (edited)
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 28.68 KB | None | 0 0
  1. import base64
  2. import re
  3. import time
  4. from datetime import datetime, timedelta
  5. from json import JSONDecodeError
  6.  
  7. import requests
  8. from django.conf import settings
  9.  
  10. from apps.core.utils.connections_errors_handlers import catch_timeout_error, \
  11.     catch_server_errors, raise_for_status_server_error
  12. from apps.core.utils.filtered_time_entities import filtered_time_entities
  13. from apps.core.utils.timekeeper_api_integration import \
  14.     get_time_point_from_timekeeper
  15.  
  16.  
  17. def get_data_from_toggl_api(access_key, user, timezone):
  18.     """
  19.    This method helps us to get data from Toggl API and render it into
  20.    required format
  21.    """
  22.  
  23.     headers, payload = toggl_authorization(access_key)
  24.  
  25.     # Get start date from TimeKeeper server
  26.     tk_error_message, start_date = get_time_point_from_timekeeper(
  27.         user)
  28.  
  29.     # Set end date to current time
  30.     end_date = datetime.now().astimezone(timezone) \
  31.         .isoformat().replace(':', '%3A') \
  32.         .replace('+', '%2B')
  33.  
  34.     status_toggl = 0
  35.     toggl_error_message = None
  36.  
  37.     if start_date is not None:
  38.         if isinstance(start_date, int):
  39.             start_date = datetime.fromtimestamp(start_date) \
  40.                 .astimezone(timezone).isoformat()
  41.  
  42.         start = start_date.replace(':', '%3A').replace('+', '%2B')
  43.         url = '{}?start_date={}&end_date={}'.format(settings.TOGGL_API_URL,
  44.                                                     start,
  45.                                                     end_date)
  46.     else:
  47.         url = '{}?end_date={}'.format(settings.TOGGL_API_URL, end_date)
  48.  
  49.     # Retrieve all information about time entities from Toggl API related
  50.     # to current user in JSON format
  51.     request_url = url.split('time_entries')
  52.     try:
  53.         settings.LOGGER.info('Send "GET" request to Toggle API')
  54.         request_get = requests.request("GET", url,
  55.                                        headers=headers, data=payload,
  56.                                        verify=False)
  57.  
  58.         status_toggl = raise_for_status_server_error(request_get)
  59.  
  60.         response = request_get.json()
  61.         settings.LOGGER.info('Get data from Toggle API. Status code {}'.format(
  62.             request_get.status_code))
  63.  
  64.         settings.LOGGER.debug(
  65.             'Request get data from "{}" / "time_entries{}" endpoint /'
  66.             ' status code "{}". Response: {}'.format(request_url[0],
  67.                                                      request_url[1],
  68.                                                      request_get.status_code,
  69.                                                      response))
  70.  
  71.     except JSONDecodeError as json_error:
  72.         response = []
  73.         settings.LOGGER.debug(
  74.             'Request get data from "{}" / "time_entries{}" endpoint.'
  75.             ' Toggl token incorrectly specified. JSONDecodeError: '
  76.             '{}'.format(request_url[0], request_url[1], json_error))
  77.         settings.LOGGER.error(
  78.             'No data received from Toggle API. Due to Toggl token '
  79.             'incorrectly specified. '
  80.             'JSONDecodeError: {}'.format(json_error))
  81.     except requests.exceptions.Timeout:
  82.         response = []
  83.         toggl_error_message = catch_timeout_error('time_entries',
  84.                                                   request_url, "Toggl")
  85.     except requests.exceptions.HTTPError:
  86.         response = []
  87.         toggl_error_message = catch_server_errors(
  88.             'time_entries',
  89.             request_url,
  90.             status_toggl, "Toggl")
  91.  
  92.     # time_entities variables to store modified data from JSON
  93.     time_entities = []
  94.     time_entities_without_id = []
  95.     time_entities_no_id_at_the_end = []
  96.  
  97.     for item in response:
  98.         # Parameters for storing information about structure of description, and
  99.         # correctness and availability id task id an the end of description
  100.         wrong_sequence = False
  101.         no_task_id_at_the_end = False
  102.         task_id_as_digit_at_end = False
  103.         different_task_ids_provided = False
  104.  
  105.         # Retrieve date and time from time entity in special data format
  106.         updated_at = datetime.strptime(
  107.             item["at"],
  108.             "%Y-%m-%dT%H:%M:%S%z").astimezone(timezone).strftime(
  109.             "%Y-%m-%d %H:%M")
  110.  
  111.         started_at = datetime.strptime(
  112.             item["start"],
  113.             "%Y-%m-%dT%H:%M:%S%z").astimezone(timezone).strftime(
  114.             "%H:%M")
  115.         started_at_day = datetime.strptime(
  116.             item["start"],
  117.             "%Y-%m-%dT%H:%M:%S%z").astimezone(timezone).strftime(
  118.             "%Y-%m-%d %H:%M")
  119.         # Verification if time entity has stop parameter
  120.         if "stop" in item:
  121.             stopped_at = datetime.strptime(
  122.                 item["stop"],
  123.                 "%Y-%m-%dT%H:%M:%S%z").astimezone(timezone).strftime(
  124.                 "%H:%M")
  125.         else:
  126.             stopped_at = ''
  127.  
  128.         # Verification if time entity has positive duration parameter
  129.         item_duration = item['duration']
  130.         if item_duration > 0:
  131.  
  132.             duration, duration_minutes = _rounding_seconds_into_minutes(
  133.                 item_duration)
  134.  
  135.             # Verification if time entity long more than one day
  136.             days = timedelta(seconds=item_duration).days
  137.             if days != 0:
  138.                 # convert days into hours
  139.                 hours = int(duration.split(':')[0]) + days * 24
  140.                 minutes = duration.split(':')[1]
  141.                 # regenerate duration representation
  142.                 duration = "{}:{}".format(hours, minutes)
  143.  
  144.         else:
  145.             duration = ''
  146.             duration_minutes = 0
  147.  
  148.         # Verification if description is provided
  149.         try:
  150.             # Parse needed information from description field by using regex
  151.             # and search method
  152.             item_description = (item['description']).replace('“', '"').replace(
  153.                 '”', '"').strip()
  154.  
  155.             keywords = ["ACT=", "COMMENT=", "URL=", "ID="]
  156.  
  157.             wrong_sequence = _is_wrong_sequence_param(item_description,
  158.                                                       keywords,
  159.                                                       wrong_sequence)
  160.             # Search keywords in description by using regex
  161.             keywords_in_description = re.search(
  162.                 '^[a-zA-z0-9](.*?)(ACT|COMMENT|URL|ID)=',
  163.                 item_description, re.M | re.I)
  164.  
  165.             # This part of program will execute if keyword found in description
  166.             if keywords_in_description:
  167.                 if re.search('^(([a-zA-z]*[0-9]*)[-]([0-9]*)) ',
  168.                              item_description,
  169.                              re.M | re.I):
  170.                     description = keywords_in_description.group(0)
  171.                 else:
  172.                     description = keywords_in_description.group(0)
  173.                     if re.search('^([A-Z]{3,})', description):
  174.                         description = ''
  175.                     wrong_sequence = True
  176.  
  177.                 for key in keywords:
  178.                     occurrence_count = description.count(key)
  179.                     replace_keyword = "{}".format(key)
  180.                     if occurrence_count >= 2:
  181.  
  182.                         repeat_replace(description, replace_keyword, '',
  183.                                        (occurrence_count - 1))
  184.                     else:
  185.                         description = description.replace(replace_keyword, '')
  186.  
  187.                 task_id_from_search = search_field(item_description, 'ID=')
  188.  
  189.                 task_id_as_digit_at_end = _is_id_digit_at_the_end(
  190.                     task_id_as_digit_at_end, task_id_from_search)
  191.  
  192.                 no_task_id_at_the_end = is_id_at_the_end(
  193.                     task_id_from_search)
  194.  
  195.                 # Search task id regex will find ID keyword
  196.                 # at the end of string
  197.                 search_task_id_regex = re.search(
  198.                     '(^[a-zA-z]*[0-9]*)(?<!^)[-]([0-9]*)$',
  199.                     task_id_from_search, re.M | re.I)
  200.                 if task_id_from_search != '' and search_task_id_regex:
  201.                     task_id_at_the_end = search_task_id_regex.group(0)
  202.                 else:
  203.                     task_id_at_the_end = ''
  204.  
  205.                 activity = search_field(item_description, 'ACT=')
  206.                 url = search_field(item_description, 'URL=')
  207.                 comment = search_field(item_description, 'COMMENT=')
  208.             else:
  209.                 description = item_description
  210.                 activity, url, comment, task_id_at_the_end = '', '', '', ''
  211.                 no_task_id_at_the_end = True
  212.                 settings.LOGGER.warning(
  213.                     'Unrecognizable task id when no keywords are provided. '
  214.                     'Description "{}"'.format(
  215.                         item_description))
  216.  
  217.             # By using this regex we parse task id from the following types of
  218.             # strings: BAI-4 To do smth ACT="Documentation"...
  219.             # - BAI-4 - ID of task
  220.             # - To do smth - description of the task
  221.             # - ACT="Documentation" - option of the task
  222.             if re.search('^(([a-zA-z]*[0-9]*)[-]([0-9]*)) ', description,
  223.                          re.M | re.I):
  224.                 task_id = re.search(
  225.                     '^(([a-zA-z]*[0-9]*)[-]([0-9]*)) ',
  226.                     description, re.M | re.I).group(0).strip()
  227.                 # Match special symbols in fist part of description after
  228.                 # task id. This verification used for define if description
  229.                 # added to time entity. Regex match all symbols except equal
  230.                 # symbol
  231.                 if not re.search(
  232.                         '^(([a-zA-z]*[0-9]*)[-]([0-9]*)) [^=]* ',
  233.                         description,
  234.                         re.M | re.I):
  235.                     wrong_sequence = True
  236.             else:
  237.                 task_id = ''
  238.                 settings.LOGGER.warning(
  239.                     'Unrecognizable task id. Description "{}"'.format(
  240.                         item_description))
  241.  
  242.             if task_id_at_the_end != task_id and not no_task_id_at_the_end \
  243.                     and task_id != '':
  244.                 different_task_ids_provided = True
  245.                 wrong_sequence = False
  246.             else:
  247.                 different_task_ids_provided = False
  248.  
  249.         except KeyError:
  250.             description, task_id, activity, url, comment, task_id_at_the_end = '', '', '', '', '', ''
  251.             wrong_sequence = True
  252.         try:
  253.             stop = item['stop']
  254.         except KeyError:
  255.             stop = ''
  256.         try:
  257.             tags = item['tags']
  258.         except KeyError:
  259.             tags = []
  260.  
  261.         try:
  262.             tid = item['tid']
  263.         except KeyError:
  264.             tid = 0
  265.  
  266.         try:
  267.             pid = item['pid']
  268.         except KeyError:
  269.             pid = 0
  270.  
  271.         # Creating dictionary with retrieved information
  272.  
  273.         dictionary = {
  274.             'id': item['id'],
  275.             'started_at': started_at,
  276.             'stopped_at': stopped_at,
  277.             'updated_at': updated_at,
  278.             'duration': duration,
  279.             'duration_minutes': duration_minutes,
  280.             'description': description,
  281.             'activity': activity,
  282.             'url': url,
  283.             'comment': comment,
  284.             'task_id': task_id,
  285.             'start': item['start'],
  286.             'stop': stop,
  287.             'duration_seconds': item_duration,
  288.             'tags': tags,
  289.             'wid': item['wid'],
  290.             'pid': pid,
  291.             'tid': tid,
  292.             'billable': item['billable'],
  293.             'at': item['at'],
  294.             'started_at_day': started_at_day,
  295.             'task_id_at_the_end': task_id_at_the_end,
  296.             'wrong_sequence': wrong_sequence,
  297.             'no_task_id_at_the_end': no_task_id_at_the_end,
  298.             'task_id_as_digit_at_end': task_id_as_digit_at_end,
  299.             'different_task_ids_provided': different_task_ids_provided
  300.         }
  301.  
  302.         if task_id == '':
  303.             time_entities_without_id.append(dictionary)
  304.  
  305.         if task_id_at_the_end == '':
  306.             time_entities_no_id_at_the_end.append(dictionary)
  307.  
  308.         # Appending time entities with new information
  309.         time_entities.append(dictionary)
  310.  
  311.     time_entities_filtered = filtered_time_entities(time_entities,
  312.                                                     'started_at_day')
  313.     defining_gaps_and_overlaps(time_entities, timezone, user)
  314.     defining_gap_for_last_time_entry(time_entities_filtered, user)
  315.     for dates, values in time_entities_filtered.items():
  316.         for time_entity in values:
  317.             if 'overlapped' not in time_entity:
  318.                 time_entity['overlapped'] = False
  319.             if 'has_gap' not in time_entity:
  320.                 time_entity['has_gap'] = False
  321.             if 'gapped' not in time_entity:
  322.                 time_entity['gapped'] = False
  323.             if 'gap_eod' not in time_entity:
  324.                 time_entity['gap_eod'] = False
  325.             if 'has_overlap' not in time_entity:
  326.                 time_entity['has_overlap'] = False
  327.     return toggl_error_message, tk_error_message, \
  328.            time_entities, time_entities_filtered, \
  329.            time_entities_without_id, time_entities_no_id_at_the_end
  330.  
  331.  
  332. def is_id_at_the_end(task_id_from_search):
  333.     """
  334.    This function uses for verification if no task id at the
  335.    end
  336.  
  337.    :param task_id_from_search: task id searched by using regex
  338.    end contains only digits
  339.  
  340.    :type task_id_from_search: str
  341.  
  342.    :return task_id_from_search == '': True if task id is not present in
  343.     description at the end
  344.    """
  345.  
  346.     return task_id_from_search == ''
  347.  
  348.  
  349. def _is_id_digit_at_the_end(task_id_as_digit_at_end,
  350.                             task_id_from_search):
  351.     """
  352.    This function uses for searching and verification if task id at the
  353.    end contains only digits
  354.  
  355.    :param task_id_from_search: task id searched by using regex
  356.    :param task_id_as_digit_at_end: True or False related if task id at the
  357.    end contains only digits
  358.  
  359.    :type task_id_from_search: str
  360.    :type task_id_as_digit_at_end: bool
  361.  
  362.    :return task_id_as_digit_at_end: True if task id contains only digits
  363.    """
  364.  
  365.     task_id_as_digit = re.search(
  366.         '^[0-9]*',
  367.         task_id_from_search, re.M | re.I)
  368.     if task_id_as_digit:
  369.         if task_id_as_digit.group(0) != '':
  370.             task_id_as_digit_at_end = True
  371.         else:
  372.             task_id_as_digit_at_end = False
  373.     return task_id_as_digit_at_end
  374.  
  375.  
  376. def _is_wrong_sequence_param(item_description, keywords, wrong_sequence):
  377.     """
  378.    This function uses for defining structure of provided description. If
  379.    description sequence is correct this function return False else return True
  380.  
  381.    :param item_description: time entry description
  382.    :param keywords: ACT, COMMENT, URL, ID keywords
  383.    :param wrong_sequence: True or False related if the position of keywords
  384.            is correct or incorrect
  385.  
  386.    :type item_description: str
  387.    :type keywords: list
  388.    :type wrong_sequence: bool
  389.  
  390.    :return wrong_sequence
  391.    """
  392.  
  393.     for key in range(len(keywords) - 1):
  394.         try:
  395.             position_first_element = item_description.find(
  396.                 keywords[key])
  397.             position_next_element = item_description.find(
  398.                 keywords[key + 1])
  399.             if keywords[key] != 'COMMENT=' or keywords[key] != 'ACT=' or \
  400.                     keywords[key] != 'URL=':
  401.                 if position_first_element == -1 and keywords[key] != 'COMMENT=' \
  402.                         and keywords[key] != 'ACT=' and \
  403.                         keywords[key] != 'URL=':
  404.                     wrong_sequence = True
  405.                     break
  406.                 elif position_next_element < position_first_element and position_next_element != -1:
  407.                     wrong_sequence = True
  408.                     break
  409.  
  410.         except IndexError:
  411.             wrong_sequence = True
  412.  
  413.     return wrong_sequence
  414.  
  415.  
  416. def _rounding_seconds_into_minutes(item_duration_seconds):
  417.     """
  418.    This method helps to convert and round duration.
  419.  
  420.    Rounding of seconds:
  421.        - if duration of time entry is 0:00:11 or 0:00:34 or 0:00:47 =>
  422.        round to 0:01:00 (one minute)
  423.        - if duration of time entry is 0:01:29 => round to 0:01:00
  424.        - if duration of time entry is 0:01:31 => round to 0:02:00
  425.  
  426.  
  427.    :param item_duration_seconds: get item_duration in seconds
  428.    :return: rounded duration and duration_minutes
  429.  
  430.    :type item_duration_seconds: int
  431.    :type duration: str
  432.    :type duration_minutes: float
  433.    """
  434.  
  435.     # Convert duration into list
  436.     convert_duration_into_list_of_str = "{}".format(str(
  437.         timedelta(seconds=item_duration_seconds))).split(':')
  438.  
  439.     # Get separate hours, minutes, seconds
  440.     hours = int(convert_duration_into_list_of_str[0])
  441.     minutes = int(convert_duration_into_list_of_str[1])
  442.     seconds = int(convert_duration_into_list_of_str[2])
  443.  
  444.     # Convert duration into minutes
  445.     duration_minutes = round(item_duration_seconds / 60, 0)
  446.  
  447.     # Verification for rounding seconds into minutes
  448.     if hours == 0 and minutes == 0 and seconds > 0:
  449.         duration = "00:01"
  450.         duration_minutes = 1
  451.     elif minutes >= 0 and seconds >= 30:
  452.         duration = "{}:{}".format(
  453.             time.strftime('%H', time.gmtime(item_duration_seconds)),
  454.             str(minutes + 1).zfill(2))
  455.     else:
  456.         duration = time.strftime(
  457.             '%H:%M',
  458.             time.gmtime(item_duration_seconds))
  459.     return duration, duration_minutes
  460.  
  461.  
  462. def toggl_authorization(access_key):
  463.     payload = {}
  464.     user_password = '{}:{}'.format(access_key, settings.TOGGL_USER_PASSWORD)
  465.     user_password_encoded = base64.b64encode(user_password.encode("utf-8"))
  466.     headers = {
  467.         'Authorization': 'Basic {}'.format(
  468.             user_password_encoded.decode("utf-8"))
  469.     }
  470.     return headers, payload
  471.  
  472.  
  473. def defining_gaps_and_overlaps(time_entities, timezone, user):
  474.     """
  475.    In this loop we define the time between ending and beginning of time
  476.    entity. We take end time of current time entity and subtract from ending of
  477.    next time entity. Results we store in time entities dictionary.
  478.    Also we store the result representation, gap start and gap stop
  479.    information which we will use in our template
  480.    """
  481.     hour_from = user.working_hour_from.isoformat()
  482.     hour_till = user.working_hour_till.isoformat()
  483.  
  484.     for idx in range(len(time_entities) - 1):
  485.         try:
  486.  
  487.             current_stop = datetime.strptime(
  488.                 time_entities[idx]['stop'],
  489.                 "%Y-%m-%dT%H:%M:%S%z").astimezone(timezone).replace(
  490.                 tzinfo=None).isoformat()
  491.             future_stop = datetime.strptime(
  492.                 time_entities[idx + 1]['stop'],
  493.                 "%Y-%m-%dT%H:%M:%S%z").astimezone(timezone).replace(
  494.                 tzinfo=None).isoformat()
  495.             future_start = datetime.strptime(
  496.                 time_entities[idx + 1]['start'],
  497.                 "%Y-%m-%dT%H:%M:%S%z").astimezone(timezone).replace(
  498.                 tzinfo=None).isoformat()
  499.             current_stop_slit = current_stop.split('T')
  500.             future_stop_split = future_stop.split('T')
  501.             future_start_split = future_start.split('T')
  502.             if current_stop_slit[0] == future_stop_split[0]:
  503.  
  504.                 if time_entities[idx]['stopped_at'] != '':
  505.                     stopped_at_task = time.mktime(
  506.                         datetime.strptime(time_entities[idx]['stopped_at'],
  507.                                           '%H:%M').timetuple())
  508.                     started_at_task = time.mktime(
  509.                         datetime.strptime(
  510.                             time_entities[idx + 1]['started_at'],
  511.                             '%H:%M').timetuple())
  512.                     result = stopped_at_task - started_at_task
  513.  
  514.                     time_entities[idx]['result'] = result
  515.                     if hour_from <= current_stop_slit[1] <= hour_till and \
  516.                             hour_from <= future_start_split[1] <= hour_till:
  517.  
  518.                         time_entities[idx]['gap_start'] = \
  519.                             time_entities[idx]['stopped_at']
  520.                         time_entities[idx]['gap_stop'] = \
  521.                             time_entities[idx + 1]['started_at']
  522.  
  523.                     else:
  524.                         if result < 0:
  525.                             time_entities[idx]['result'] = 0
  526.  
  527.                     if current_stop < future_stop:
  528.                         time_entities[idx][
  529.                             'result_representation'] = time.strftime(
  530.                             '%H:%M', time.gmtime(abs(result)))
  531.                         time_entities[idx]['overlap_start'] = \
  532.                             time_entities[idx + 1]['started_at']
  533.                         time_entities[idx]['overlap_stop'] = \
  534.                             time_entities[idx]['stopped_at']
  535.  
  536.                         set_overlap_or_gap_param_for_time_entry(
  537.                             idx,
  538.                             time_entities)
  539.                     else:
  540.                         time_entities[idx]['result_representation'] = \
  541.                             time_entities[idx + 1]['duration']
  542.  
  543.                         time_entities[idx]['overlap_start'] = \
  544.                             time_entities[idx + 1]['started_at']
  545.  
  546.                         time_entities[idx]['overlap_stop'] = \
  547.                             time_entities[idx + 1]['stopped_at']
  548.  
  549.                         set_overlap_or_gap_param_for_time_entry(
  550.                             idx,
  551.                             time_entities)
  552.  
  553.         except ValueError:
  554.             time_entities[idx]['result'] = ''
  555.  
  556.  
  557. def defining_gap_for_last_time_entry(time_entities_filtered, user):
  558.     """
  559.    In this loop, we define the time between the ending of time entity and
  560.    hour when working day ends. We loop by filtered time entities in reverse
  561.    order and define stop time point. If we have returned only one
  562.    time entity we took time point from that time entity
  563.    """
  564.  
  565.     hour_from = user.working_hour_from.isoformat()
  566.     hour_till = user.working_hour_till.isoformat()
  567.     hour_till_timestamp = time.mktime(
  568.         datetime.strptime(hour_till,
  569.                           '%H:%M:%S').timetuple())
  570.     for date, time_entities in time_entities_filtered.items():
  571.         if len(time_entities) > 1:
  572.             for idx in reversed(range(len(time_entities) - 1)):
  573.                 try:
  574.                     if hour_from <= time_entities[idx][
  575.                         'stopped_at'] <= hour_till \
  576.                             and hour_from <= time_entities[idx][
  577.                         'started_at'] <= hour_till:
  578.  
  579.                         if time_entities[idx]['stop'].split('T')[1] < \
  580.                                 time_entities[idx + 1]['stop'].split('T')[1]:
  581.                             _gap_eod_calculation(hour_till_timestamp,
  582.                                                  time_entities[idx + 1],
  583.                                                  hour_till)
  584.                             break
  585.                         elif time_entities[idx]['stop'].split('T')[1] > \
  586.                                 time_entities[idx + 1]['stop'].split('T')[1]:
  587.                             _gap_eod_calculation(hour_till_timestamp,
  588.                                                  time_entities[idx], hour_till)
  589.                             break
  590.                 except IndexError:
  591.                     pass
  592.         else:
  593.             if hour_from <= time_entities[0]['stopped_at'] <= hour_till:
  594.                 _gap_eod_calculation(hour_till_timestamp,
  595.                                      time_entities[0], hour_till)
  596.  
  597.  
  598. def _gap_eod_calculation(hour_till_timestamp, element, hour_till):
  599.     """
  600.    This function calculates the gap between the last time entity and the end
  601.    of the day which defined in the settings page by the user.
  602.    We verify if the result more than the gap defined in the settings file,
  603.    if yes we set for gap_eod param True, parse the result in param gap_eod_result
  604.    """
  605.  
  606.     stopped_at_task = time.mktime(
  607.         datetime.strptime(
  608.             element['stopped_at'],
  609.             '%H:%M').timetuple())
  610.     if element['stopped_at'] <= hour_till:
  611.         result = abs(stopped_at_task - hour_till_timestamp)
  612.         if result > settings.GAP:
  613.             element['gap_eod'] = True
  614.  
  615.             hours, minutes = divmod(result / 60, 60)
  616.             if hours != 0:
  617.                 element['gap_eod_result'] = \
  618.                     "%dh %02dm" % (hours, minutes)
  619.             else:
  620.                 element['gap_eod_result'] = \
  621.                     "%02dm" % minutes
  622.  
  623.  
  624. def set_overlap_or_gap_param_for_time_entry(idx, time_entities):
  625.     """
  626.    This function sets param into time entry dict if overlap or gap present
  627.  
  628.    :param idx: index
  629.    :param time_entities: list of all time entities
  630.    :type idx: int
  631.    :type time_entities: list
  632.    """
  633.  
  634.     if time_entities[idx]['result'] > 0 and \
  635.             time_entities[idx]['result'] > settings.OVERLAP:
  636.  
  637.         time_entities[idx]['overlapped'] = True
  638.         current_start = time_entities[idx]['started_at_day'].split(' ')[0]
  639.         future_start = time_entities[idx + 1]['started_at_day'].split(' ')[0]
  640.         if current_start == future_start:
  641.             time_entities[idx + 1]['has_overlap'] = True
  642.             if time_entities[idx]['stopped_at'] < time_entities[idx + 1][
  643.                 'stopped_at']:
  644.                 result = int(time_entities[idx]['result'])
  645.                 time_entities[idx + 1]['overlap_time'] = '{}-{}'.format(
  646.                     time_entities[idx + 1]['started_at'],
  647.                     time_entities[idx]['stopped_at'])
  648.             else:
  649.                 started_at = time.mktime(
  650.                     datetime.strptime(time_entities[idx + 1]['started_at'],
  651.                                       '%H:%M').timetuple())
  652.                 stopped_at = time.mktime(
  653.                     datetime.strptime(time_entities[idx + 1]['stopped_at'],
  654.                                       '%H:%M').timetuple())
  655.                 result = int(stopped_at - started_at)
  656.                 time_entities[idx + 1]['overlap_time'] = '{}-{}'.format(
  657.                     time_entities[idx + 1]['started_at'],
  658.                     time_entities[idx + 1]['stopped_at'])
  659.             hours, minutes = divmod(result / 60, 60)
  660.             if hours != 0:
  661.                 time_entities[idx + 1]['overlap_result'] = \
  662.                     "%d hour %02d minutes" % (hours, minutes)
  663.                 if minutes <= 1 < hours:
  664.                     time_entities[idx + 1]['overlap_result'] = \
  665.                         "%d hours %02d minute" % (hours, minutes)
  666.                     if minutes == 0:
  667.                         time_entities[idx + 1]['overlap_result'] = \
  668.                             "%d hours" % (hours)
  669.                 if minutes <= 1 and hours == 1:
  670.                     time_entities[idx + 1]['overlap_result'] = \
  671.                         "%d hour %02d minute" % (hours, minutes)
  672.                     if minutes == 0:
  673.                         time_entities[idx + 1]['overlap_result'] = \
  674.                             "%d hour" % (hours)
  675.                 if hours > 1 and minutes > 1:
  676.                     time_entities[idx + 1]['overlap_result'] = \
  677.                         "%d hours %02d minutes" % (hours, minutes)
  678.             else:
  679.                 time_entities[idx + 1]['overlap_result'] = \
  680.                     "%02d minutes" % minutes
  681.                 if minutes < 1:
  682.                     time_entities[idx + 1]['overlap_result'] = \
  683.                         "%02d minute" % minutes
  684.     elif time_entities[idx]['result'] < 0 and abs(
  685.             time_entities[idx]['result']) > settings.GAP:
  686.         time_entities[idx]['gapped'] = True
  687.         time_entities[idx + 1]['has_gap'] = True
  688.         hours, minutes = divmod(abs(int(
  689.             time_entities[idx]['result'])) / 60, 60)
  690.         if hours != 0:
  691.             time_entities[idx + 1]['gap_result'] = \
  692.                 "%dh %02dm" % (hours, minutes)
  693.         else:
  694.             time_entities[idx + 1]['gap_result'] = \
  695.                 "%02dm" % minutes
  696.  
  697.  
  698. def search_field(field, keywords):
  699.     """
  700.    This method helps to parse field with special keyword by using regex.
  701.    We split string to several groups and take information in double quote.
  702.    """
  703.  
  704.     # Find key word in description by using regex. Example which will be found
  705.     # ID="TEST-123".
  706.     search_item = re.search(
  707.         r'{}((?<![\\])["])((?:.(?!(?<![\\])\1))*.?)\1'.format(
  708.             keywords), field, re.M | re.I)
  709.     if search_item:
  710.         item = search_item.group(2)
  711.     else:
  712.         item = ''
  713.     return item
  714.  
  715.  
  716. def repeat_replace(s, old, new, occurrence):
  717.     string = s.rsplit(old, occurrence)
  718.     return new.join(string)
Add Comment
Please, Sign In to add comment