Exemple #1
0
def process_comment_payload_from_gh(payload):


    # =================================================== ПОДГОТОВКА ===================================================


    def parse_payload(payload):

        payload_parsed = {}  # словарь issue (название, описание, ссылка)

        # действие и его автор
        payload_parsed['action'] = payload['action']
        payload_parsed['sender_id'] = payload['sender']['id']  # sender - тот, кто совершил действие
        payload_parsed['sender_login'] = payload['sender']['login']

        # заполение полей issue
        payload_parsed['issue_title'] = payload['issue']['title']
        payload_parsed['issue_body'] = payload['issue']['body']
        payload_parsed['issue_author_id'] = payload['issue']['user']['id']
        payload_parsed['issue_author_login'] = payload['issue']['user']['login']

        # идентификаторы (для связи и логов)
        payload_parsed['issue_id'] = payload['issue']['id']
        payload_parsed['repos_id'] = payload['repository']['id']
        payload_parsed['issue_number'] = payload['issue']['number']

        # ссылка на issue (для фразы бота и логов)
        payload_parsed['issue_url'] = payload['issue']['html_url']

        # комментарий
        payload_parsed['comment_body'] = payload['comment']['body']
        payload_parsed['comment_id'] = payload['comment']['id']
        payload_parsed['comment_author_id'] = payload['comment']['user']['id']
        payload_parsed['comment_author_login'] = payload['comment']['user']['login']

        return payload_parsed

    try:
        issue = parse_payload(payload)

    except:

        error_text = 'ERROR: unknown payload type'

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' +
                  'received webhook from GITHUB: issues comments' + '\n' +
                  error_text)

        return HttpResponse(error_text, status=200)

    # авторизация в redmine по токену
    api_key_redmime = read_file('api_keys/api_key_redmime.txt')     # загрузка ключа для redmine api
    api_key_redmime = api_key_redmime.replace('\n', '')             # избавляемся от \n в конце строки

    # загрузка template из файла
    issue_redmine_template = read_file('data/issue_redmine_template.json')
    issue_redmine_template = Template(issue_redmine_template)       # шаблон для каждого issue

    # заголовки авторизации и приложения, при отправке запросов на редмайн
    headers = {'X-Redmine-API-Key': api_key_redmime,
               'Content-Type': 'application/json'}


    # ============================================ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ =============================================


    # issue_body
    # comment_body
    # comment_edit
    # comment_edit_else's
    # добавляем фразу бота, со ссылкой на аккаунт пользователя в гитхабе
    def add_bot_phrase(issue, to):

        # добавляем фразу бота к описанию issue
        if (to == 'issue_body'):
            author_url_gh = '"' + issue['issue_author_login'] + '":' + 'https://github.com/' + issue['issue_author_login']
            issue_url_gh = '"issue":' + issue['issue_url']
            issue_body = '>I am a bot, bleep-bloop.\n' +\
                         '>' + author_url_gh + ' Has opened the ' + issue_url_gh + ' in Github.\n' +\
                         issue['issue_body']

            return issue_body

        # добавляем фразу бота к комментарию
        elif (to == 'comment_body'):

            # добавляем фразу бота
            author_url = '"' + issue['comment_author_login'] + '":' + 'https://github.com/' + issue['comment_author_login']
            comment_url = '"comment":' + issue['issue_url'] + '#issuecomment-' + str(issue['comment_id'])
            comment_body = '>I am a bot, bleep-bloop.\n' +\
                           '>' + author_url + ' Has left a ' + comment_url + ' in Github.\n' +\
                           issue['comment_body']

            return comment_body

        # добавляем фразу бота к комментарию (изменение своего комментария)
        elif (to == 'comment_edit'):

            # добавляем фразу бота
            author_url = '"' + issue['comment_author_login'] + '":' + 'https://github.com/' + issue['comment_author_login']
            comment_url = '"comment":' + issue['issue_url'] + '#issuecomment-' + str(issue['comment_id'])
            comment_body = '>I am a bot, bleep-bloop.\n' +\
                           '>' + author_url + ' Has edited his ' + comment_url + ' in Github.\n' +\
                           issue['comment_body']

            return comment_body

        # добавляем фразу бота к комментарию (изменение чужого комментария)
        elif (to == "comment_edit_else's"):

            # добавляем фразу бота
            sender_url = '"' + issue['sender_login'] + '":' + 'https://github.com/' + issue['sender_login']
            author_url = '"' + issue['comment_author_login'] + '":' + 'https://github.com/' + issue['comment_author_login']
            comment_url = '"comment":' + issue['issue_url'] + '#issuecomment-' + str(issue['comment_id'])
            comment_body = '>I am a bot, bleep-bloop.\n' +\
                           '>' + sender_url + ' Has edited ' + author_url + " 's " + comment_url + ' in Github.\n' +\
                           issue['comment_body']

            return comment_body

        else:

            WRITE_LOG("\nERROR: process_comment_payload_from_gh.add_bot_phrase - unknown parameter 'to': " + to + '.' +
                      "\nPlease, check the code on possible typos." +
                      "\nAlternatively, add logic to process '" + to + "' action correctly.\n")

            return None


    # типичные ошибки на этапе проверки: не связаны проекты, задачи, комментарии, неизвестное действие и т.п.
    def PREPARATION_ERR(error_text):

        # добавляем, чтобы в начале получилось: 'PREPARATION ERROR'
        error_text = 'PREPARATION ' + error_text

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' +
                  'received webhook from GITHUB: issues_comments | ' + 'action: ' + str(issue['action']) + '\n' +
                  error_text)

        return HttpResponse(error_text, status=200)

    # логическая ошибка: неизвестное действие, неправильные label-ы в гитхабе и т.п.
    def LOGICAL_ERR(error_text):

        # добавляем, чтобы в начале получилось: 'LOGICAL ERROR'
        error_text = 'LOGICAL ' + error_text

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n' +
                  'received webhook from GITHUB: issues_comments | ' + 'action: ' + str(issue['action']) + '\n' +
                  error_text)

        return HttpResponse(error_text, status=200)


    # ============================================= КОМАНДЫ ДЛЯ ЗАГРУЗКИ ===============================================


    def post_comment(linked_projects, issue):


        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------


        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_comment_payload_from_gh.post_comment\n" +\
                         "comment " + str(issue['action']) +" in GITHUB, but the project is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        project_id_rm = linked_projects.project_id_rm

        linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id'])

        # дополнительная проверка, что issue связаны
        if (linked_issues.count() == 0):

            error_text = "ERROR: process_comment_payload_from_gh.post_comment\n" +\
                         "comment " + str(issue['action']) + " in GITHUB, but the issue is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_issues = linked_issues[0]


        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------


        # проверяем, если автор issue - бот
        if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])):
            issue_body = del_bot_phrase(issue['issue_body'])

        else:
            issue_body = add_bot_phrase(issue, 'issue_body')

        comment_body = add_bot_phrase(issue, 'comment_body')    # добавляем фразу бота

        # обработка спец. символов
        issue_title = align_special_symbols(issue['issue_title'])
        issue_body = align_special_symbols(issue_body)
        comment_body = align_special_symbols(comment_body)


        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ----------------------------------------


        issue_templated = issue_redmine_template.render(
            project_id=project_id_rm,
            issue_id=linked_issues.issue_id_rm,
            priority_id=linked_issues.priority_id_rm,
            subject=issue_title,
            description=issue_body,
            notes=comment_body)

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        issue_url_rm = url_rm.replace('.json',
                                      '/' + str(linked_issues.issue_id_rm) + '.json')
        request_result = requests.put(issue_url_rm,
                                      data=issue_templated,
                                      headers=headers)


        # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ --------------------------------------------
        # (делаем привязку комментариев после получения веб-хука от редмайна)


        # ДЕБАГГИНГ
        log_comment_gh(request_result, issue, linked_issues, linked_projects.project_id_rm)

        return request_result

    # изменение комментария (постим новый, пишем 'edited comment')
    def edit_comment(linked_projects, issue):


        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------



        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_comment_payload_from_gh.edit_comment\n" +\
                         "comment " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        project_id_rm = linked_projects.project_id_rm

        linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id'])

        # дополнительная проверка, что issue связаны
        if (linked_issues.count() == 0):

            error_text = "ERROR: process_comment_payload_from_gh.edit_comment\n" +\
                         "comment " + str(issue['action']) + " in GITHUB, but the issue is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_issues = linked_issues[0]

        linked_comments = linked_issues.get_comment_by_id_gh(issue['comment_id'])

        # дополнительная проверка, что комментарии связаны
        if (linked_comments.count() == 0):

            error_text = "ERROR: process_comment_payload_from_gh.edit_comment\n" +\
                         "comment " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_comments = linked_comments[0]


        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------


        # проверяем, если автор issue - бот
        if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])):
            issue_body = del_bot_phrase(issue['issue_body'])

        else:
            issue_body = add_bot_phrase(issue, 'issue_body')

        # если изменил свой комментарий
        if (issue['sender_id'] == issue['comment_author_id']):
            comment_body = add_bot_phrase(issue, 'comment_edit')            # добавляем фразу бота

        # если изменил не свой комментарий
        else:
            comment_body = add_bot_phrase(issue, "comment_edit_else's")     # добавляем фразу бота

        # обработка спец. символов
        issue_title = align_special_symbols(issue['issue_title'])
        issue_body = align_special_symbols(issue_body)
        comment_body = align_special_symbols(comment_body)


        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ----------------------------------------


        issue_templated = issue_redmine_template.render(
            project_id=project_id_rm,
            issue_id=linked_issues.issue_id_rm,
            priority_id=linked_issues.priority_id_rm,
            subject=issue_title,
            description=issue_body,
            notes=comment_body)

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        issue_url_rm = url_rm.replace('.json',
                                      '/' + str(linked_issues.issue_id_rm) + '.json')
        request_result = requests.put(issue_url_rm,
                                      data=issue_templated,
                                      headers=headers)


        # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ --------------------------------------------
        # (делаем привязку комментариев после получения веб-хука от редмайна)


        # ДЕБАГГИНГ
        log_comment_gh(request_result, issue, linked_issues, linked_projects.project_id_rm)

        return request_result

    '''
    # изменение комментария (не постим новый, а изменяем старый)
    # СТАРЫЙ КОД: перед использованием - привести в порядок (как edit_comment выше)
    def edit_comment(issue, linked_issues):

        # дополнительная проверка, что issue связаны
        # (на случай, если изменили не связанный issue)
        if (linked_issues.count() == 0):
            WRITE_LOG('\n' + '='*35 + ' ' + str(datetime.datetime.today()) + ' ' + '='*35 + '\n' +
                      'received webhook from GITHUB: issue_comments | ' + 'action: ' + str(issue['action']) + '\n' +
                      "ERROR: posted comment in GITHUB, but the issue is not linked to REDMINE")
            return HttpResponse(status=200)
        linked_issues = linked_issues[0]

        # дополнительная проверка, что комментарии связаны
        linked_comments = linked_issues.get_comment_by_id_gh(issue['comment_id'])
        if (linked_comments.count() == 0):
            WRITE_LOG('\n' + '='*35 + ' ' + str(datetime.datetime.today()) + ' ' + '='*35 + '\n' +
                      'received webhook from GITHUB: issue_comments | ' + 'action: ' + str(issue['action']) + '\n' +
                      "ERROR: edited comment in GITHUB, but it is not linked to REDMINE")
            return HttpResponse(status=200)
        linked_comments = linked_comments[0]


        # ------------------------------------------- ОБРАБАТЫВАЕМ ФРАЗУ БОТА ------------------------------------------


        # проверяем, если автор issue - бот
        if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])):
            issue_body = del_bot_phrase(issue['issue_body'])

        else:
            issue_body = add_bot_phrase(issue)

        comment_body = bot_speech_comment(issue)    # добавляем фразу бота

        # обработка спец. символов
        issue_title = align_special_symbols(issue['issue_title'])
        issue_body = align_special_symbols(issue_body)
        comment_body = align_special_symbols(comment_body)


        # ----------------------------------------- ЗАГРУЖАЕМ ДАННЫЕ В РЕДМАЙН -----------------------------------------


        issue_templated = issue_redmine_template.render(
            project_id=project_id_rm,
            issue_id=linked_issues.issue_id_rm,
            priority_id=priority_ids_rm[0],
            subject=issue_title,
            description=issue_body,
            notes=comment_body)

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        issue_url_rm = url_rm.replace('.json',
                                      '/' + str(linked_issues.issue_id_rm) + '.json')
        request_result = requests.put(issue_url_rm,
                                      data=issue_templated,
                                      headers=headers)


        # ------------------------------------------- ПРИВЯЗКА КОММЕНТАРИЕВ --------------------------------------------
        # (делаем привязку после получения веб-хука от редмайна)


        # ДЕБАГГИНГ
        log_comment_gh(request_result, issue, linked_issues)

        return request_result'''


    # ========================================= ЗАГРУЗКА КОММЕНТАРИЯ В REDMINE =========================================


    linked_projects = Linked_Projects.objects.get_project_by_id_gh(issue['repos_id'])
    if (issue['action'] == 'created'):

        if (chk_if_gh_user_is_our_bot(issue['sender_id'])):

            error_text = prevent_cyclic_comment_gh(issue)
            return HttpResponse(error_text, status=200)

        request_result = post_comment(linked_projects, issue)

    elif (issue['action'] == 'edited'):

        if (chk_if_gh_user_is_our_bot(issue['sender_id'])):

            error_text = prevent_cyclic_comment_gh(issue)
            return HttpResponse(error_text, status=200)

        request_result = edit_comment(linked_projects, issue)

    else:

        error_text = "ERROR: process_comment_payload_from_gh\n" + \
                     "WRONG ACTION"

        return LOGICAL_ERR(error_text)


    return align_request_result(request_result)
def query_data_gh_to_rm(linked_projects):


    # =================================================== ПОДГОТОВКА ===================================================


    # авторизация в redmine по токену
    api_key_redmime = read_file('api_keys/api_key_redmime.txt')         # загрузка ключа для redmine api
    api_key_redmime = api_key_redmime.replace('\n', '')                 # избавляемся от \n в конце строки

    # загрузка template из файла
    issue_redmine_template = read_file('data/issue_redmine_template.json')
    issue_redmine_template = Template(issue_redmine_template)           # шаблон для каждого issue в редмайне

    # заголовки авторизации и приложения, при отправке запросов на редмайн
    headers_rm = {'X-Redmine-API-Key': api_key_redmime,
                  'Content-Type': 'application/json'}


    # загрузка issue template из файла
    issue_github_template = read_file('data/issue_github_template.json')
    issue_github_template = Template(issue_github_template)  # шаблон для каждого issue

    # авторизация в гитхабе по токену
    api_key_github = read_file('api_keys/api_key_github.txt')           # загрузка ключа для github api
    api_key_github = api_key_github.replace('\n', '')                   # избавляемся от \n в конце строки

    # заголовки авторизации и приложения, при отправке запросов на гитхаб
    headers_gh = {'Authorization': 'token ' + api_key_github,
                  'Content-Type': 'application/json'}


    # ============================================ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ =============================================


    # добавляем пробелы, чтобы отделить от лога проектов и задач (как табуляция)
    # лог связи комментариев
    def log_link_comments_start():

        if (not allow_log_project_linking):
            return 0

        WRITE_LOG_GRN('\n    ' + '=' * 33 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 33 + '\n' +
                      "    LINKING COMMENTS IN PROGRESS" + '\n')

    def log_link_comments_finish():

        if (not allow_log_project_linking):
            return 0

        WRITE_LOG_GRN("    LINKING COMMENTS FINISHED" + '\n' +
                      '    ' + '=' * 33 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 33 + '\n')

    # лог связи задач
    def log_link_issues_start():

        if (not allow_log_project_linking):
            return 0

        WRITE_LOG_GRN('\n  ' + '=' * 34 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 34 + '\n' +
                      "  LINKING ISSUES IN PROGRESS" + '\n')

    def log_link_issues_finish():

        if (not allow_log_project_linking):
            return 0

        WRITE_LOG_GRN("  LINKING ISSUES  FINISHED" + '\n' +
                      '  ' + '=' * 34 + ' ' + str(datetime.datetime.today()) + ' ' + '=' * 34 + '\n')


    def log_issue_gh(result, issue, linked_issues):

        if (not allow_log_project_linking):
            return 0
        if (not detailed_log_project_linking):
            return 0

        action_rm = 'POST'

        WRITE_LOG('\n  ' + '-' * 34 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 34 + '\n' +
                  '  linking issue from GITHUB to REDMINE' + '\n' +
                  '  ' + action_rm + ' result in REDMINE: ' + str(result.status_code) + ' ' + str(result.reason) + '\n' +
                  '  GITHUB  | ---------------- issue ----------------' + '\n' +
                  '          | author_login:  '******'issue_author_login']) + '\n' +
                  '          | issue_url:     ' + issue['issue_url'] + '\n' +
                  '          | issue_id:      ' + str(issue['issue_id']) + '\n' +
                  '          |\n' +
                  '  REDMINE | ---------------- issue ----------------' + '\n' +
                  '          | issue_id:      ' + str(linked_issues.issue_id_rm))

    def log_comment_gh(result, comment, linked_issues):

        if (not allow_log_project_linking):
            return 0
        if (not detailed_log_project_linking):
            return 0

        action_rm = 'EDIT'

        WRITE_LOG('\n    ' + '-' * 33 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 33 + '\n' +
                  '    linking comment from GITHUB to REDMINE' + '\n' +
                  '    ' + action_rm + ' result in REDMINE: ' + str(result.status_code) + ' ' + str(result.reason) + '\n' +
                  '    GITHUB  | --------------- comment ---------------' + '\n' +
                  '            | author_login:  '******'comment_author_login']) + '\n' +
                  '            | comment_id:    ' + str(comment['comment_id']) + '\n' +
                  '            |\n' +
                  '    REDMINE | ---------------- issue ----------------' + '\n' +
                  '            | issue_id:      ' + str(linked_issues.issue_id_rm))

    def log_issue_gh_already_linked(linked_issues, issue):

        if (not allow_log_project_linking):
            return 0
        if (not detailed_log_project_linking):
            return 0

        WRITE_LOG('\n  ' + '-' * 34 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 34 + '\n' +
                  '  linking issue from GITHUB to REDMINE'  + '\n' +
                  '  WARNING: issues are already linked!'  + '\n' +
                  '  GITHUB  | ---------------- issue ----------------' + '\n' +
                  '          | author_login:  '******'issue_author_login']) + '\n' +
                  '          | issue_url:     ' + issue['issue_url'] + '\n' +
                  '          | issue_id:      ' + str(issue['issue_id']) + '\n' +
                  '          |\n' +
                  '  REDMINE | ---------------- issue ----------------' + '\n' +
                  '          | issue_id:      ' + str(linked_issues.issue_id_rm))

    def log_comment_gh_already_linked(linked_comments):

        if (not allow_log_project_linking):
            return 0
        if (not detailed_log_project_linking):
            return 0

        WRITE_LOG('\n    ' + '-' * 33 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 33 + '\n' +
                  '    linking comment from GITHUB to REDMINE\n' +
                  '    WARNING: comments are already linked\n' +
                  '    GITHUB  | --------------- comment ---------------' + '\n' +
                  '            | comment_id:    ' + str(linked_comments.comment_id_gh) + '\n' +
                  '            |\n' +
                  '    REDMINE | --------------- comment ---------------' + '\n' +
                  '            | comment_id:    ' + str(linked_comments.comment_id_rm) + '\n')

    #def log_correct_gh_labels(issue, tracker, linked_issues):


    # issue_body
    # comment_body
    # добавляем фразу бота, со ссылкой на аккаунт пользователя в гитхабе
    def add_bot_phrase(issue, to):

        # добавляем фразу бота к описанию issue
        if (to == 'issue_body'):

            # добавляем фразу бота
            author_url_gh = '"' + issue['issue_author_login'] + '":' + 'https://github.com/' + issue['issue_author_login']
            issue_url_gh = '"issue":' + issue['issue_url']
            issue_body = '>I am a bot, bleep-bloop.\n' +\
                         '>' + author_url_gh + ' Has opened the ' + issue_url_gh + ' in Github.\n' +\
                         issue['issue_body']

            return issue_body

        # добавляем фразу бота к комментарию
        elif (to == 'comment_body'):

            # добавляем фразу бота
            author_url = '"' + issue['comment_author_login'] + '":' + 'https://github.com/' + issue['comment_author_login']
            comment_url = '"comment":' + issue['issue_url'] + '#issuecomment-' + str(issue['comment_id'])
            comment_body = '>I am a bot, bleep-bloop.\n' +\
                           '>' + author_url + ' Has left a ' + comment_url + ' in Github.\n' +\
                           issue['comment_body']

            return comment_body

        else:

            WRITE_LOG("\nERROR: 'process_payload_from_gh.add_bot_phrase'\n" +
                      "unknown parameter 'to': " + to + '.\n' +
                      "Please, check your code on possible typos.\n" +
                      "Alternatively, add logic to process '" + to + "' parameter correctly.")

            return None

    # исправление label-ов в гитхабе
    def correct_gh_labels(issue, tracker, linked_issues):

        if (not allow_correct_github_labels):
            return 0

        # добавление label-ов
        priority = match_priority_to_gh(linked_issues.priority_id_rm)
        status = match_status_to_gh(linked_issues.status_id_rm)


        # ---------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ----------------------------------------


        issue_title = align_special_symbols(issue['issue_title'])
        issue_body = align_special_symbols(issue['issue_body'])

        issue_templated = issue_github_template.render(
            title=issue_title,
            body=issue_body,
            priority=priority,
            status=status,
            tracker=tracker)
        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        url_gh = make_gh_repos_url(linked_issues.repos_id_gh)

        # добавление issue_id к ссылке
        issue_url_gh = url_gh + '/' + str(linked_issues.issue_num_gh)
        request_result = requests.patch(issue_url_gh,
                                        data=issue_templated,
                                        headers=headers_gh)

        return request_result


    # парсинг комментария
    def parse_comment(issue, comment):

        comment_parsed = {}  # словарь issue (название, описание, ссылка)

        # заполение полей issue
        comment_parsed['issue_title'] = issue['issue_title']
        comment_parsed['issue_body'] = issue['issue_body']
        comment_parsed['issue_author_id'] = issue['issue_author_id']  # автор issue
        comment_parsed['issue_author_login'] = issue['issue_author_login']

        # идентификаторы (для связи и логов)
        comment_parsed['issue_id'] = issue['issue_id']
        comment_parsed['repos_id'] = issue['repos_id']
        comment_parsed['issue_number'] = issue['issue_number']

        # ссылка на issue (для фразы бота и логов)
        comment_parsed['issue_url'] = issue['issue_url']

        # комментарий
        comment_parsed['comment_body'] = comment['body']
        comment_parsed['comment_id'] = comment['id']
        comment_parsed['comment_author_id'] = comment['user']['id']
        comment_parsed['comment_author_login'] = comment['user']['login']

        return comment_parsed

    # парсинг задачи
    def parse_issue(issue):

        issue_parsed = {}  # словарь issue (название, описание, ссылка)

        # заполение полей issue
        issue_parsed['issue_title'] = issue['title']
        issue_parsed['issue_body'] = issue['body']
        issue_parsed['issue_author_id'] = issue['user']['id']
        issue_parsed['issue_author_login'] = issue['user']['login']

        # идентификаторы (для связи и логов)
        issue_parsed['issue_id'] = issue['id']
        issue_parsed['repos_id'] = linked_projects.repos_id_gh
        issue_parsed['issue_number'] = issue['number']

        # ссылка на issue (для фразы бота и логов)
        issue_parsed['issue_url'] = issue['html_url']

        issue_parsed['labels'] = issue['labels']

        issue_parsed['action'] = 'opened'
        issue_parsed['state'] = issue['state']

        return issue_parsed


    def prevent_cyclic_comment_gh(issue):

        error_text = '    The user, who left the comment: ' + issue['issue_author_login'] + ' | user id: ' + str(issue['issue_author_id']) + ' (our bot)\n' + \
                     '    Aborting action, in order to prevent cyclic: GH -> S -> RM -> S -> GH -> ...'

        if (allow_log_cyclic):
            WRITE_LOG(error_text)

        return error_text


    def chk_if_issues_are_linked(issue_id_gh):

        linked_issues = Linked_Issues.objects.get_issue_by_id_gh(issue_id_gh)
        if (len(linked_issues) == 0):
            return False
        else:
            return True

    def chk_if_comments_are_linked(comment_id_gh):

        linked_comments = Linked_Comments.objects.get_by_comment_id_gh(comment_id_gh)
        if (len(linked_comments) == 0):
            return False
        else:
            return True


    # ============================================= КОМАНДЫ ДЛЯ ЗАГРУЗКИ ===============================================


    def post_comment(linked_projects, linked_issues, comment):


        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------


        # проверяем, если автор комментария - наш бот
        if (chk_if_gh_user_is_our_bot(comment['comment_author_id'])):

            error_text = prevent_cyclic_comment_gh(comment)
            return HttpResponse(error_text, status=200)


        project_id_rm = linked_projects.project_id_rm


        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------


        # проверяем, если автор issue - наш бот
        if (chk_if_gh_user_is_our_bot(comment['issue_author_id'])):
            issue_body = del_bot_phrase(comment['issue_body'])

        else:
            issue_body = add_bot_phrase(comment, 'issue_body')

        comment_body = add_bot_phrase(comment, 'comment_body')    # добавляем фразу бота

        # обработка спец. символов
        issue_title = align_special_symbols(comment['issue_title'])
        issue_body = align_special_symbols(issue_body)
        comment_body = align_special_symbols(comment_body)


        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ----------------------------------------


        issue_templated = issue_redmine_template.render(
            project_id=project_id_rm,
            issue_id=linked_issues.issue_id_rm,
            priority_id=linked_issues.priority_id_rm,
            subject=issue_title,
            description=issue_body,
            notes=comment_body)

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        issue_url_rm = url_rm.replace('.json',
                                      '/' + str(linked_issues.issue_id_rm) + '.json')
        request_result = requests.put(issue_url_rm,
                                      data=issue_templated,
                                      headers=headers_rm)


        # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ --------------------------------------------
        # (делаем привязку комментариев после получения веб-хука от редмайна)


        # ДЕБАГГИНГ
        log_comment_gh(request_result, comment, linked_issues)

        if (request_result.status_code == 200):
            request_result.status_code = 201

        return request_result

    def post_issue(linked_projects, issue):


        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------


        project_id_rm = linked_projects.project_id_rm

        # настройка label-ов
        priority_id_rm = priority_ids_rm[0]

        tracker_id_rm = None
        for label in issue['labels']:

            tracker_rm = match_label_to_rm(label['name'])

            # если label известный
            if (tracker_rm != None):

                if (tracker_rm['type'] == 'Tracker'):

                    if (tracker_id_rm == None):
                        tracker_id_rm = tracker_rm['id_rm']

                    # если пользователь выбрал более одного трекера -> значение по умолчанию
                    else:
                        tracker_id_rm = tracker_ids_rm[0]   # трекер по умолчанию

        # проверяем, был ли установлен трекер
        if (tracker_id_rm == None):
            tracker_id_rm = tracker_ids_rm[0]

        if (issue['state'] == 'closed'):
            status_id_rm = status_ids_rm[5]     # статус "закрытый"
            is_opened = False

        else:
            status_id_rm = status_ids_rm[0]     # статус по умолчанию
            is_opened = True


        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------


        #title = '[From Github] ' + issue['issue_title']
        title = issue['issue_title']
        issue_body = add_bot_phrase(issue, 'issue_body')   # добавляем фразу бота

        # обработка спец. символов
        title = align_special_symbols(title)
        issue_body = align_special_symbols(issue_body)


        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ----------------------------------------


        issue_templated = issue_redmine_template.render(
            project_id=project_id_rm,
            tracker_id=tracker_id_rm,
            status_id=status_id_rm,
            priority_id=priority_id_rm,
            subject=title,
            description=issue_body)

        # кодировка по умолчанию (Latin-1) на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        request_result = requests.post(url_rm,
                                       data=issue_templated,
                                       headers=headers_rm)


        # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ --------------------------------------------


        posted_issue = json.loads(request_result.text)

        # занесение в базу данных информацию о том, что данные issues связаны
        linked_issues = Linked_Issues.objects.create_linked_issues(
            posted_issue['issue']['id'],    # id issue в редмайне
            issue['issue_id'],              # id issue в гитхабе
            issue['repos_id'],              # id репозитория в гитхабе
            issue['issue_number'],          # номер issue  в репозитории гитхаба
            tracker_id_rm,                  # id трекера в редмайне
            status_id_rm,                   # id статуса в редмайне
            priority_id_rm,                 # id приоритета в редмайне
            is_opened)                      # закрыт / открыт

        # добавляем linked_issues в linked_projects
        linked_projects.add_linked_issues(linked_issues)

        # корректируем label-ы в гитхабе
        tracker = match_tracker_to_gh(linked_issues.tracker_id_rm)
        correct_gh_labels(issue, tracker, linked_issues)


        # ДЕБАГГИНГ
        log_issue_gh(request_result, issue, linked_issues)

        result = {}
        result['request_result'] = request_result
        result['linked_issues'] = linked_issues

        return result


    # ====================================== ЗАГРУЗКА ДАННЫХ ИЗ GITHUB В REDMINE =======================================


    # загрузка комментариев у задачи
    def link_comments_in_issue(linked_issues, issue):


        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------


        log_link_comments_start()

        repos_url_gh = linked_projects.url_gh[19:]  # избавляемся от 'https://github.com/'

        # запрос кол-ва всех comment
        issue_comments_url = 'https://api.github.com/repos/' + repos_url_gh + '/issues/' + str(linked_issues.issue_num_gh) + '/comments'
        request_result = requests.get(issue_comments_url)  # запрос всех комментариев

        requested_comments = json.loads(request_result.text)


        # ------------------------------ ЗАПРОС ВСЕХ КОММЕНТАРИЕВ С ЗАДАЧИ В ГИТХАБЕ ------------------------------


        if (request_result.status_code != 200):
            error_text = '    ERROR WHILE LINKING COMMENTS: ' + str(request_result) + '\n    ' + str(request_result.text)
            WRITE_LOG_ERR(error_text)
            return 0

        # цикл перебора всех комментариев в задаче
        for comment in requested_comments:

            comment_parsed = parse_comment(issue, comment)
            if (chk_if_comments_are_linked(comment_parsed['comment_id'])):
                # комментарии уже связаны
                linked_comments = Linked_Comments.objects.get_by_comment_id_gh(comment_parsed['comment_id'])
                log_comment_gh_already_linked(linked_comments[0])

                continue

            # отправляем комментарий в редмайн
            post_result = post_comment(linked_projects, linked_issues, comment_parsed)

            # пропускаем коментарии бота
            if (post_result.status_code == 200):
                continue

            # ошибка при отправке комментария в редмайн
            if (post_result.status_code != 201):

                error_text = '    ERROR WHILE LINKING COMMENTS: ' + str(request_result) + '\n  ' + str(
                    request_result.text)
                WRITE_LOG_ERR(error_text)
                return 0

        log_link_comments_finish()

    # загрузка задач у проекта
    def link_issues_in_project(linked_projects):


        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------


        log_link_issues_start()

        repos_url_gh = linked_projects.url_gh[19:]     # избавляемся от 'https://github.com/'

        # запрос кол-ва всех issue
        url_gh = 'https://api.github.com/search/issues?q=repo:' + repos_url_gh
        request_result = requests.get(url_gh)   # для total_count

        WRITE_LOG('  ISSUES total_count QUERRY RESULT: ' + str(request_result))
        num_issues = json.loads(request_result.text)['total_count']
        WRITE_LOG('  total_count: ' + str(num_issues))


        # ----------------------------------- ЗАПРОС ВСЕХ ЗАДАЧ С ПРОЕКТА В ГИТХАБЕ ------------------------------------


        issues = []
        WRITE_LOG('  QUERRYING ISSUES FROM GITHUB')

        #per_page = 6  # для тестов
        per_page = 100  # кол-во issue за страницу
        page = 1

        # цикл перехода по всем страницам, по 100 issue за страницу
        while True:

            WRITE_LOG('  per_page: ' + str(per_page) + ' page: ' + str(page) +
                      ' | status: ' + str(request_result.status_code) + ' ' + str(request_result.reason))

            ''' https://api.github.com/search/issues?q=repo:AlexanderND/issues_linker_auto_labels_test/issues&per_page=5&page=1 '''
            url = url_gh + '/issues&per_page=' + str(per_page) + '&page=' + str(page)
            request_result = requests.get(url)

            issues_on_page = json.loads(request_result.text)['items']

            # цикл перебора всех задач на странице
            for issue in issues_on_page:

                issue_parsed = parse_issue(issue)

                # сохраняем данные
                issues.append(issue_parsed)

            # переход на след. страницу
            page += 1

            # цикл с постусловием
            if (page >= 1 + num_issues / per_page):
                break


        # --------------------------------- ОТПРАВКА ЗАДАЧ В СВЯЗАННЫЙ ПРОЕКТ В РЕДМАЙНЕ -------------------------------


        # отправляем задачи в редмайн в обратном порядке
        for issue in reversed(issues):

            if (chk_if_issues_are_linked(issue['issue_id'])):
                # задачи уже связаны
                linked_issues = Linked_Issues.objects.get_issue_by_id_gh(issue['issue_id'])
                log_issue_gh_already_linked(linked_issues[0], issue)

                link_comments_in_issue(linked_issues[0], issue)

                continue

            post_result = post_issue(linked_projects, issue)

            # если успешно создали новую задачу в редмайне, осуществляем привязку комментариев
            if (post_result['request_result'].status_code != 201):

                error_text = '  ERROR WHILE LINKING ISSUES: ' + str(request_result) + '\n  ' + str(
                    request_result.text)
                WRITE_LOG_ERR(error_text)
                return 0

            else:
                link_comments_in_issue(post_result['linked_issues'], issue)

        log_link_issues_finish()


    link_issues_in_project(linked_projects)
def process_payload_from_gh(payload):

    # =================================================== ПОДГОТОВКА ===================================================

    def parse_payload(payload):

        payload_parsed = {}  # словарь issue (название, описание, ссылка)

        # действие и его автор
        payload_parsed['action'] = payload['action']
        payload_parsed['sender_id'] = payload['sender'][
            'id']  # sender - тот, кто совершил действие
        payload_parsed['sender_login'] = payload['sender']['login']

        # заполение полей issue
        payload_parsed['issue_title'] = payload['issue']['title']
        payload_parsed['issue_body'] = payload['issue']['body']
        payload_parsed['issue_author_id'] = payload['issue']['user']['id']
        payload_parsed['issue_author_login'] = payload['issue']['user'][
            'login']

        # идентификаторы (для связи и логов)
        payload_parsed['issue_id'] = payload['issue']['id']
        payload_parsed['repos_id'] = payload['repository']['id']
        payload_parsed['issue_number'] = payload['issue']['number']

        # ссылка на issue (для фразы бота и логов)
        payload_parsed['issue_url'] = payload['issue']['html_url']

        payload_parsed['labels'] = payload['issue']['labels']

        #if ((payload_parsed['action'] == 'labeled') | (payload_parsed['action'] == 'unlabeled')):
        #    payload_parsed['label'] = payload['issue']['label']

        return payload_parsed

    try:
        issue = parse_payload(payload)

    except:

        error_text = 'ERROR: unknown payload type'

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                  ' ' + '=' * 35 + '\n' +
                  'received webhook from GITHUB: issues' + '\n' + error_text)

        return HttpResponse(error_text, status=200)

    # авторизация в redmine по токену
    api_key_redmime = read_file(
        'api_keys/api_key_redmime.txt')  # загрузка ключа для redmine api
    api_key_redmime = api_key_redmime.replace(
        '\n', '')  # избавляемся от \n в конце строки

    # загрузка template из файла
    issue_redmine_template = read_file('data/issue_redmine_template.json')
    issue_redmine_template = Template(
        issue_redmine_template)  # шаблон для каждого issue

    # заголовки авторизации и приложения, при отправке запросов на редмайн
    headers_rm = {
        'X-Redmine-API-Key': api_key_redmime,
        'Content-Type': 'application/json'
    }

    # авторизация в гитхабе по токену
    api_key_github = read_file(
        'api_keys/api_key_github.txt')  # загрузка ключа для github api
    api_key_github = api_key_github.replace(
        '\n', '')  # избавляемся от \n в конце строки

    # загрузка issue template из файла
    issue_github_template = read_file('data/issue_github_template.json')
    issue_github_template = Template(
        issue_github_template)  # шаблон для каждого issue

    # заголовки авторизации и приложения, при отправке запросов на гитхаб
    headers_gh = {
        'Authorization': 'token ' + api_key_github,
        'Content-Type': 'application/json'
    }

    # ============================================ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ =============================================

    # issue_body
    # comment_body_action
    # добавляем фразу бота, со ссылкой на аккаунт пользователя в гитхабе
    def add_bot_phrase(issue, to):

        # добавляем фразу бота к описанию issue
        if (to == 'issue_body'):

            # добавляем фразу бота
            author_url_gh = '"' + issue[
                'issue_author_login'] + '":' + 'https://github.com/' + issue[
                    'issue_author_login']
            issue_url_gh = '"issue":' + issue['issue_url']
            issue_body = '>I am a bot, bleep-bloop.\n' +\
                         '>' + author_url_gh + ' Has opened the ' + issue_url_gh + ' in Github.\n' +\
                         issue['issue_body']

            return issue_body

        # добавляем фразу бота (комментарием) к действию в гитхабе (закрыл, изменил и т.д.)
        elif (to == 'comment_body_action'):

            author_url = '"' + issue[
                'sender_login'] + '":' + 'https://github.com/' + issue[
                    'sender_login']
            issue_url = '"issue":' + issue['issue_url']
            comment_body = 'I am a bot, bleep-bloop.\n' +\
                         author_url + ' Has ' + issue['action'] + ' the ' + issue_url + ' in Github.\n'

            return comment_body

        else:

            WRITE_LOG("\nERROR: 'process_payload_from_gh.add_bot_phrase'\n" +
                      "unknown parameter 'to': " + to + '.\n' +
                      "Please, check your code on possible typos.\n" +
                      "Alternatively, add logic to process '" + to +
                      "' parameter correctly.")

            return None

    # обновление linked_issues в базе данных сервера (tracker_id, status_id, priority_id)
    def update_linked_issues(linked_issues, tracker_id, status_id, priority_id,
                             is_opened):

        linked_issues.tracker_id_rm = tracker_id
        linked_issues.status_id_rm = status_id
        linked_issues.priority_id_rm = priority_id

        linked_issues.is_opened = is_opened

        linked_issues.save()

    # TODO: отправлять комментарий бота, что нельзя установить неправильные label-ы + логи?
    # исправление label-ов в гитхабе
    def correct_gh_labels(issue, tracker, linked_issues):

        if (not allow_correct_github_labels):
            return 0

        # добавление label-ов
        priority = match_priority_to_gh(linked_issues.priority_id_rm)
        status = match_status_to_gh(linked_issues.status_id_rm)

        # ---------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ----------------------------------------

        issue_templated = issue_github_template.render(
            title=issue['issue_title'],
            body=issue['issue_body'],
            priority=priority,
            status=status,
            tracker=tracker)
        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        url_gh = make_gh_repos_url(linked_issues.repos_id_gh)

        # добавление issue_id к ссылке
        issue_url_gh = url_gh + '/' + str(linked_issues.issue_num_gh)
        request_result = requests.patch(issue_url_gh,
                                        data=issue_templated,
                                        headers=headers_gh)

        return request_result

    # TODO: отправлять комментарий бота, что нельзя открыть rejected issue + логи?
    # закрыть issue в гитхабе
    def close_gh_issue(linked_issues, url_gh):

        # ---------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ----------------------------------------

        issue_templated = issue_github_template.render(state='closed')
        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        # добавление issue_id к ссылке
        issue_url_gh = url_gh + '/' + str(linked_issues.issue_num_gh)
        request_result = requests.patch(issue_url_gh,
                                        data=issue_templated,
                                        headers=headers_gh)

        return request_result

    # типичные ошибки на этапе проверки: не связаны проекты, задачи, комментарии, неизвестное действие и т.п.
    def PREPARATION_ERR(error_text):

        # добавляем, чтобы в начале получилось: 'PREPARATION ERROR'
        error_text = 'PREPARATION ' + error_text

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                  ' ' + '=' * 35 + '\n' +
                  'received webhook from GITHUB: issues | ' + 'action: ' +
                  str(issue['action']) + '\n' + error_text)

        return HttpResponse(error_text, status=200)

    # логическая ошибка: неизвестное действие, неправильные label-ы в гитхабе и т.п.
    def LOGICAL_ERR(error_text):

        # добавляем, чтобы в начале получилось: 'LOGICAL ERROR'
        error_text = 'LOGICAL ' + error_text

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                  ' ' + '=' * 35 + '\n' +
                  'received webhook from GITHUB: issues | ' + 'action: ' +
                  str(issue['action']) + '\n' + error_text)

        return HttpResponse(error_text, status=200)

    # ============================================= КОМАНДЫ ДЛЯ ЗАГРУЗКИ ===============================================

    def post_issue(linked_projects, issue):

        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------

        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_payload_from_gh.post_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        project_id_rm = linked_projects.project_id_rm

        # настройка label-ов
        tracker_id_rm = None
        status_id_rm = status_ids_rm[0]
        priority_id_rm = priority_ids_rm[0]

        for label in issue['labels']:

            tracker_rm = match_label_to_rm(label['name'])

            # если label известный
            if (tracker_rm != None):

                if (tracker_rm['type'] == 'Tracker'):

                    if (tracker_id_rm == None):
                        tracker_id_rm = tracker_rm['id_rm']

                    # если пользователь выбрал более одного трекера -> значение по умолчанию
                    else:
                        tracker_id_rm = tracker_ids_rm[0]

        # проверяем, был ли установлен трекер
        if (tracker_id_rm == None):
            tracker_id_rm = tracker_ids_rm[0]

        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------

        #title = '[From Github] ' + issue['issue_title']
        title = issue['issue_title']
        issue_body = add_bot_phrase(issue,
                                    'issue_body')  # добавляем фразу бота

        # обработка спец. символов
        title = align_special_symbols(title)
        issue_body = align_special_symbols(issue_body)

        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ----------------------------------------

        issue_templated = issue_redmine_template.render(
            project_id=project_id_rm,
            tracker_id=tracker_id_rm,
            status_id=status_id_rm,
            priority_id=priority_id_rm,
            subject=title,
            description=issue_body)

        # кодировка по умолчанию (Latin-1) на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        request_result = requests.post(url_rm,
                                       data=issue_templated,
                                       headers=headers_rm)

        # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ --------------------------------------------

        posted_issue = json.loads(request_result.text)

        # занесение в базу данных информацию о том, что данные issues связаны
        linked_issues = Linked_Issues.objects.create_linked_issues(
            posted_issue['issue']['id'],  # id issue в редмайне
            issue['issue_id'],  # id issue в гитхабе
            issue['repos_id'],  # id репозитория в гитхабе
            issue['issue_number'],  # номер issue  в репозитории гитхаба
            tracker_id_rm,  # id трекера в редмайне
            status_id_rm,  # id статуса в редмайне
            priority_id_rm,  # id приоритета в редмайне
            True)  # открыт

        # добавляем linked_issues в linked_projects
        linked_projects.add_linked_issues(linked_issues)

        # корректируем label-ы в гитхабе
        tracker = match_tracker_to_gh(linked_issues.tracker_id_rm)
        correct_gh_labels(issue, tracker, linked_issues)

        # ДЕБАГГИНГ
        log_issue_gh(request_result, issue, linked_issues,
                     linked_projects.project_id_rm)

        return request_result

    # is_opened - issue открыто / закрыто
    def edit_issue(linked_projects, issue, is_opened):

        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------

        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_payload_from_gh.edit_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        project_id_rm = linked_projects.project_id_rm
        repos_id_gh = linked_projects.repos_id_gh
        url_gh = make_gh_repos_url(repos_id_gh)

        linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id'])

        # дополнительная проверка, что issue связаны
        if (linked_issues.count() == 0):

            error_text = "ERROR: process_payload_from_gh.edit_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_issues = linked_issues[0]

        # проверка: что issue был отклонён (запрещаем открывать вновь)
        """
        if (linked_issues.status_id_rm == status_ids_rm[4]):

            if (is_opened == True):

                return close_gh_issue(linked_issues, url_gh)
        """

        # настройка label-ов
        tracker_id_rm = None

        for label in issue['labels']:

            tracker_rm = match_label_to_rm(label['name'])

            # если label известный
            if (tracker_rm != None):

                if (tracker_rm['type'] == 'Tracker'):

                    if (tracker_id_rm == None):
                        tracker_id_rm = tracker_rm['id_rm']

                    # если пользователь выбрал более одного трекера -> значение по умолчанию
                    else:
                        tracker_id_rm = tracker_ids_rm[0]

        # проверяем, был ли установлен трекер
        if (tracker_id_rm == None):
            tracker_id_rm = tracker_ids_rm[0]

        # корректируем label-ы в гитхабе
        tracker = match_tracker_to_gh(linked_issues.tracker_id_rm)
        correct_gh_labels(issue, tracker, linked_issues)

        # обновляем информацию в таблице
        update_linked_issues(linked_issues, tracker_id_rm,
                             linked_issues.status_id_rm,
                             linked_issues.priority_id_rm, is_opened)

        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------

        #title = '[From Github] ' + issue['issue_title']
        title = issue['issue_title']

        # проверяем, если автор issue - бот
        if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])):
            issue_body = del_bot_phrase(
                issue['issue_body'])  # удаляем фразу бота

        else:
            issue_body = add_bot_phrase(issue,
                                        'issue_body')  # добавляем фразу бота

        comment_body = add_bot_phrase(
            issue, 'comment_body_action'
        )  # добавляем фразу бота в комментарий к действию

        # обработка спец. символов
        title = align_special_symbols(title)
        issue_body = align_special_symbols(issue_body)
        comment_body = align_special_symbols(comment_body)

        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ----------------------------------------

        if (is_opened):
            status_id = linked_issues.status_id_rm

        else:
            status_id = status_ids_rm[5]  # closed

        issue_templated = issue_redmine_template.render(
            project_id=project_id_rm,
            issue_id=linked_issues.issue_id_rm,
            tracker_id=linked_issues.tracker_id_rm,
            status_id=status_id,
            priority_id=linked_issues.priority_id_rm,
            subject=title,
            description=issue_body,
            notes=comment_body)

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        issue_url_rm = url_rm.replace(
            '.json', '/' + str(linked_issues.issue_id_rm) + '.json')
        request_result = requests.put(issue_url_rm,
                                      data=issue_templated,
                                      headers=headers_rm)

        # ДЕБАГГИНГ
        log_issue_gh(request_result, issue, linked_issues,
                     linked_projects.project_id_rm)

        return request_result

    def delete_issue(linked_projects, issue):

        # ----------------------------------------------- ПОДГОТОВКА -----------------------------------------------

        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_payload_from_gh.delete_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id'])

        # дополнительная проверка, что issue связаны
        if (linked_issues.count() == 0):

            error_text = "ERROR: process_payload_from_gh.delete_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_issues = linked_issues[0]

        # -------------------------------------- УДАЛЕНИЕ ДАННЫХ В РЕДМАЙНЕ ----------------------------------------

        issue_url_rm = url_rm.replace(
            '.json', '/' + str(linked_issues.issue_id_rm) + '.json')

        request_result = requests.delete(issue_url_rm, headers=headers_rm)

        # удаление linked_issues из базы данных
        linked_issues.delete()

        # ДЕБАГГИНГ
        log_issue_gh(request_result, issue, linked_issues,
                     linked_projects.project_id_rm)

        return request_result

    def reject_issue(linked_projects, issue):

        # ----------------------------------------------- ПОДГОТОВКА ----------------------------------------------

        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_payload_from_gh.reject_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        project_id_rm = linked_projects.project_id_rm
        repos_id_gh = linked_projects.repos_id_gh
        url_gh = make_gh_repos_url(repos_id_gh)

        linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id'])

        # дополнительная проверка, что issue связаны
        if (linked_issues.count() == 0):

            error_text = "ERROR: process_payload_from_gh.reject_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_issues = linked_issues[0]

        # обновляем информацию в таблице
        update_linked_issues(
            linked_issues,
            linked_issues.tracker_id_rm,
            status_ids_rm[4],  # 4 - rejected
            linked_issues.priority_id_rm,
            False)

        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------

        #title = '[From Github] ' + issue['issue_title']
        title = issue['issue_title']

        # проверяем, если автор issue - бот
        if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])):
            issue_body = del_bot_phrase(
                issue['issue_body'])  # удаляем фразу бота

        else:
            issue_body = add_bot_phrase(issue,
                                        'issue_body')  # добавляем фразу бота

        comment_body = add_bot_phrase(
            issue, 'comment_body_action'
        )  # добавляем фразу бота в комментарий к действию

        # обработка спец. символов
        title = align_special_symbols(title)
        issue_body = align_special_symbols(issue_body)
        comment_body = align_special_symbols(comment_body)

        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ----------------------------------------

        issue_templated = issue_redmine_template.render(
            project_id=project_id_rm,
            issue_id=linked_issues.issue_id_rm,
            tracker_id=linked_issues.tracker_id_rm,
            status_id=status_ids_rm[4],  # 4 - rejected
            priority_id=linked_issues.priority_id_rm,
            subject=title,
            description=issue_body,
            notes=comment_body)

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        issue_url_rm = url_rm.replace(
            '.json', '/' + str(linked_issues.issue_id_rm) + '.json')
        request_result = requests.put(issue_url_rm,
                                      data=issue_templated,
                                      headers=headers_rm)

        # удаление linked_issues из базы данных
        linked_issues.delete()

        # ДЕБАГГИНГ
        log_issue_gh(request_result, issue, linked_issues,
                     linked_projects.project_id_rm)

        return request_result

    # TODO: бот не совсем корректно реагирует, если изменить трекер и что-либо ещё (частично исправил)
    # TODO: также, бот несколько раз упоминает действие в редмайне (labeled, unlabeld) (так как гитхаб отсылает все изменения столько раз, сколько label-ов было изменено...)
    def label_issue(linked_projects, issue):

        # ----------------------------------------------- ПОДГОТОВКА -----------------------------------------------

        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_payload_from_gh.label_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but the project is not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        project_id_rm = linked_projects.project_id_rm

        linked_issues = linked_projects.get_issue_by_id_gh(issue['issue_id'])

        # дополнительная проверка, что issue связаны
        if (linked_issues.count() == 0):

            error_text = "ERROR: process_payload_from_gh.label_issue\n" +\
                         "issue " + str(issue['action']) + " in GITHUB, but it's not linked to REDMINE"

            return PREPARATION_ERR(error_text)

        linked_issues = linked_issues[0]

        priority_id_rm = None
        status_id_rm = None
        tracker_id_rm = None
        incorrect_labels = False
        labels = issue['labels']
        for label in labels:

            label_gh = match_label_to_rm(label['name'])

            #если label известный
            if (label_gh != None):

                if (label_gh['type'] == 'Priority'):

                    if (priority_id_rm == None):

                        priority_id_rm = label_gh['id_rm']

                        if (priority_id_rm != linked_issues.priority_id_rm):
                            incorrect_labels = True

                    else:
                        incorrect_labels = True

                elif (label_gh['type'] == 'Status'):

                    if (status_id_rm == None):

                        status_id_rm = label_gh['id_rm']

                        if (status_id_rm != linked_issues.status_id_rm):
                            incorrect_labels = True

                    else:
                        incorrect_labels = True

                elif (label_gh['type'] == 'Tracker'):

                    if (tracker_id_rm == None):
                        tracker_id_rm = label_gh['id_rm']

                    # пользователь выбрал новый трекер, но не удалил старый -> выбираем новый
                    else:
                        if (tracker_id_rm == linked_issues.tracker_id_rm):
                            tracker_id_rm = label_gh['id_rm']

        # проверяем, был ли изменён трекер
        if (tracker_id_rm == None):
            tracker_id_rm = linked_issues.tracker_id_rm

        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------

        #title = '[From Github] ' + issue['issue_title']
        title = issue['issue_title']

        # проверяем, если автор issue - бот
        if (chk_if_gh_user_is_our_bot(issue['issue_author_id'])):
            issue_body = del_bot_phrase(
                issue['issue_body'])  # удаляем фразу бота

        else:
            issue_body = add_bot_phrase(issue,
                                        'issue_body')  # добавляем фразу бота

        comment_body = add_bot_phrase(
            issue, 'comment_body_action'
        )  # добавляем фразу бота в комментарий к действию

        # обработка спец. символов
        title = align_special_symbols(title)
        issue_body = align_special_symbols(issue_body)
        comment_body = align_special_symbols(comment_body)

        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В РЕДМАЙН ----------------------------------------

        # корректируем label-ы в гитхабе
        tracker = match_tracker_to_gh(tracker_id_rm)
        request_result = correct_gh_labels(
            issue, tracker, linked_issues)  # корректируем label-ы в гитхабе

        if (request_result.status_code != 200):

            # сообщаем об ошибке
            error_text = "ERROR: process_payload_from_gh.label_issue\n" +\
                         "Encountered some error while trying to correct labels in Github"

            return LOGICAL_ERR(error_text)

        # TODO: похоже, он не успевает изменить linked_issues.tracker_id_rm: вебхуки приходят почти одновременно
        # проверяем, был ли изменён трекер и предотвращаем множественную отправку сообщений в гитхаб
        if ((tracker_id_rm != linked_issues.tracker_id_rm) &
            (issue['action'] == 'labeled')):

            # обновляем информацию в таблице
            update_linked_issues(linked_issues, tracker_id_rm,
                                 linked_issues.status_id_rm,
                                 linked_issues.priority_id_rm, True)

            issue_templated = issue_redmine_template.render(
                project_id=project_id_rm,
                issue_id=linked_issues.issue_id_rm,
                tracker_id=tracker_id_rm,
                status_id=linked_issues.status_id_rm,
                priority_id=linked_issues.priority_id_rm,
                subject=title,
                description=issue_body,
                notes=comment_body)

            # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
            issue_templated = issue_templated.encode('utf-8')

            issue_url_rm = url_rm.replace(
                '.json', '/' + str(linked_issues.issue_id_rm) + '.json')
            request_result = requests.put(issue_url_rm,
                                          data=issue_templated,
                                          headers=headers_rm)

        # проверяем, корректные ли label-ы
        if (incorrect_labels):

            # сообщаем об ошибке
            error_text = "ERROR: process_payload_from_gh.label_issue\n" +\
                         "incorrect labels in GITHUB"

            return LOGICAL_ERR(error_text)

        else:

            # ДЕБАГГИНГ
            log_issue_gh(request_result, issue, linked_issues,
                         linked_projects.project_id_rm)

            return request_result

    # ============================================ ЗАГРУЗКА ISSUE В REDMINE ============================================

    do_delete_issues = False  # запрет удаления issues (вместо удаления ставим rejected)

    linked_projects = Linked_Projects.objects.get_project_by_id_gh(
        issue['repos_id'])
    if (issue['action'] == 'opened'):

        if (chk_if_gh_user_is_our_bot(issue['sender_id'])):

            error_text = prevent_cyclic_issue_gh(issue)
            return HttpResponse(error_text, status=200)

        request_result = post_issue(linked_projects, issue)

    elif (issue['action'] == 'edited'):

        if (chk_if_gh_user_is_our_bot(issue['sender_id'])):

            error_text = prevent_cyclic_issue_gh(issue)
            return HttpResponse(error_text, status=200)

        request_result = edit_issue(linked_projects, issue, True)

    elif (issue['action'] == 'closed'):

        if (chk_if_gh_user_is_our_bot(issue['sender_id'])):

            error_text = prevent_cyclic_issue_gh(issue)
            return HttpResponse(error_text, status=200)

        request_result = edit_issue(linked_projects, issue, False)

    elif (issue['action'] == 'reopened'):

        if (chk_if_gh_user_is_our_bot(issue['sender_id'])):

            error_text = prevent_cyclic_issue_gh(issue)
            return HttpResponse(error_text, status=200)

        request_result = edit_issue(linked_projects, issue, True)

    elif (issue['action'] == 'deleted'):

        if (chk_if_gh_user_is_our_bot(issue['sender_id'])):

            error_text = prevent_cyclic_issue_gh(issue)
            return HttpResponse(error_text, status=200)

        if (do_delete_issues):
            request_result = delete_issue(linked_projects, issue)

        else:
            request_result = reject_issue(linked_projects, issue)

    # Совершенно безразлично, 'labeled' или 'unlabeled'
    elif ((issue['action'] == 'labeled') | (issue['action'] == 'unlabeled')):

        if (chk_if_gh_user_is_our_bot(issue['sender_id'])):

            error_text = prevent_cyclic_issue_gh(issue)
            return HttpResponse(error_text, status=200)

        request_result = label_issue(linked_projects, issue)

    else:

        error_text = "ERROR: process_payload_from_gh\n" +\
                     "UNKNOWN ACTION"

        return LOGICAL_ERR(error_text)

    return align_request_result(request_result)
def relink_projects():

    # =================================================== ПОДГОТОВКА ===================================================

    # авторизация в redmine по токену (локальный сервер)
    api_key_redmime = read_file(
        'api_keys/api_key_redmime_local.txt')  # загрузка ключа для redmine api
    #api_key_redmime = read_file('api_keys/api_key_redmime.txt')        # загрузка ключа для redmine api
    api_key_redmime = api_key_redmime.replace(
        '\n', '')  # избавляемся от \n в конце строки
    '''
    # авторизация в redmine по токену (реальный сервер)
    api_key_redmime = read_file('api_keys/api_key_redmime_local.txt')   # загрузка ключа для redmine api
    api_key_redmime = api_key_redmime.replace('\n', '')                 # избавляемся от \n в конце строки
    '''

    # авторизация в гитхабе по токену
    api_key_github = read_file(
        'api_keys/api_key_github.txt')  # загрузка ключа для github api
    api_key_github = api_key_github.replace(
        '\n', '')  # избавляемся от \n в конце строки

    linked_projects = Linked_Projects.objects.get_all()
    for linked_project in linked_projects:

        # ============================ РЕСИНХРОНИЗАЦИЯ ВСЕХ СВЯЗАННЫХ ПРОЕКТОВ (GH -> GM) ==============================

        repos_id_gh = linked_project.repos_id_gh
        url_gh = linked_project.url_gh

        project_id_rm = linked_project.project_id_rm
        url_rm = linked_project.url_rm

        def log_relink_projects_start():

            if (not allow_log_project_linking):
                return 0

            WRITE_LOG_GRN(
                '\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' +
                '=' * 35 + '\n' + 'RELINKING PROJECTS IN PROGRESS' + '\n' +
                'GITHUB       | ---------------------------------------' +
                '\n' + '             | repos_id:     ' + str(repos_id_gh) +
                '\n' + '             | repos_url:    ' + url_gh + '\n' +
                'REDMINE      | ---------------------------------------' +
                '\n' + '             | project_id:   ' + str(project_id_rm) +
                '\n' + '             | project_url:  ' + url_rm)

        def log_relink_projects_finish():

            if (not allow_log_project_linking):
                return 0

            WRITE_LOG_GRN(
                'RELINKING PROJECTS FINISHED' + '\n' +
                'GITHUB       | ---------------------------------------' +
                '\n' + '             | repos_id:     ' + str(repos_id_gh) +
                '\n' + '             | repos_url:    ' + url_gh + '\n' +
                'REDMINE      | ---------------------------------------' +
                '\n' + '             | project_id:   ' + str(project_id_rm) +
                '\n' + '             | project_url:  ' + url_rm + '\n' + '\n' +
                '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' +
                '=' * 35 + '\n')

        log_relink_projects_start()

        # запрос issues и комментариев к ним из гитхаба и отправка в редмайн
        query_data_gh_to_rm(linked_project)

        log_relink_projects_finish()

    response_text = "re-linking projects successfully!"

    return HttpResponse(response_text,
                        status=200)  # <br> - новая строка в html
def link_projects(payload):

    # =================================================== ПОДГОТОВКА ===================================================

    # достаём id из payload-а
    #project_id_rm = payload['project_id_rm']
    #repos_id_gh = payload['repos_id_gh']

    # достаём ссылки из payload-а
    url_rm = payload['url_rm']
    url_gh = payload['url_gh']

    # авторизация в redmine по токену (локальный сервер)
    api_key_redmime = read_file(
        'api_keys/api_key_redmime.txt')  # загрузка ключа для redmine api
    api_key_redmime = api_key_redmime.replace(
        '\n', '')  # избавляемся от \n в конце строки

    # авторизация в гитхабе по токену
    api_key_github = read_file(
        'api_keys/api_key_github.txt')  # загрузка ключа для github api
    api_key_github = api_key_github.replace(
        '\n', '')  # избавляемся от \n в конце строки

    # заголовки авторизации и приложения, при отправке запросов на редмайн
    headers_rm = {
        'X-Redmine-API-Key': api_key_redmime,
        'Content-Type': 'application/json'
    }

    # заголовки авторизации и приложения, при отправке запросов на гитхаб
    headers_gh = {
        'Authorization': 'token ' + api_key_github,
        'Content-Type': 'application/json'
    }

    def log_linking_error(error_text, request_result):

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                  ' ' + '=' * 35 + '\n' +
                  'ERROR: tried to LINK PROJECTS, but ' + error_text + '\n' +
                  'REDMINE         | ---------------------------------------' +
                  '\n' + '                | project_url:  ' + url_rm + '\n' +
                  'GITHUB          | ---------------------------------------' +
                  '\n' + '                | repos_url:    ' + url_gh + '\n' +
                  'REQUEST_RESULT  | ---------------------------------------' +
                  '\n' + '                | status_code:  ' +
                  str(request_result.status_code) + '\n' +
                  '                | text:         ' + request_result.text)

    def log_linking_error_url(error_text):

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                  ' ' + '=' * 35 + '\n' +
                  'ERROR: tried to LINK PROJECTS, but ' + error_text + '\n' +
                  'REDMINE         | ---------------------------------------' +
                  '\n' + '                | project_url:  ' + url_rm + '\n' +
                  'GITHUB          | ---------------------------------------' +
                  '\n' + '                | repos_url:    ' + url_gh + '\n')

    def linking_error(error_text, request_result):
        log_linking_error(error_text, request_result)
        return HttpResponse(error_text.replace('\n', '<br>'),
                            status=200)  # <br> - новая строка в html

    def linking_error_url(error_text):
        log_linking_error_url(error_text)
        return HttpResponse(error_text.replace('\n', '<br>'),
                            status=200)  # <br> - новая строка в html

    # ========================================= ПОЛУЧЕНИЕ PROJECT_ID РЕДМАЙНА ==========================================

    # связь по ссылке
    if (url_rm != ''):
        # для удобства, выводим как json
        api_url_rm = url_rm + '.json'
        request_result = requests.get(api_url_rm, headers=headers_gh)

        if (request_result.status_code != 200):
            error_text = 'Something went wrong in REDMINE.\n' +\
                         'Please, check if the URL is correct.\n' +\
                         'Aborting action, in order to prevent a perpetual loop.\n' +\
                         'If the URL are correct, then try again sometime later.'
            return linking_error(error_text, request_result)

        project_id_rm = json.loads(request_result.text)['project']['id']

    # TODO: связь по id
    else:
        error_text = "You didn't input REDMINE's URL.\n" +\
                     'The linking by id has not been implemented (yet).\n' +\
                     'Please, input the URL.\n' +\
                     'Aborting action, in order to prevent a perpetual loop.'
        return linking_error_url(error_text)

    # ========================================== ПОЛУЧЕНИЕ REPOS_ID ГИТХАБА ============================================

    # связь по ссылке
    if (url_gh != ''):
        # удаляем 19 первых символов (https://github.com/)
        repos_url_gh = url_gh[19:]  # AlexanderND/test
        api_url_gh = 'https://api.github.com/repos/' + repos_url_gh  # https://api.github.com/repos/AlexanderND/test

        request_result = requests.get(api_url_gh, headers=headers_gh)

        if (request_result.status_code != 200):
            error_text = 'Something went wrong in GITHUB.\n' +\
                         'Please, check if the URL is correct.\n' +\
                         'Aborting action, in order to prevent a perpetual loop.\n' +\
                         'If the URL are correct, then try again sometime later.'
            return linking_error(error_text, request_result)

        repos_id_gh = json.loads(request_result.text)['id']

    # TODO: связь по id
    else:
        error_text = "You didn't input REDMINE's URL.\n" +\
                     'The linking by id has not been implemented (yet).\n' +\
                     'Please, input the URL.\n' +\
                     'Aborting action, in order to prevent a perpetual loop.'
        return linking_error_url(error_text)

    # ============================================= СОХРАНЕНИЕ ID-ШНИКОВ ===============================================

    def log_link_projects_start():

        if (not allow_log_project_linking):
            return 0

        WRITE_LOG_GRN(
            '\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) + ' ' +
            '=' * 35 + '\n' + 'LINKING PROJECTS IN PROGRESS' + '\n' +
            'GITHUB       | ---------------------------------------' + '\n' +
            '             | repos_id:     ' + str(repos_id_gh) + '\n' +
            '             | repos_url:    ' + url_gh + '\n' +
            'REDMINE      | ---------------------------------------' + '\n' +
            '             | project_id:   ' + str(project_id_rm) + '\n' +
            '             | project_url:  ' + url_rm)

    def log_link_projects_finish():

        if (not allow_log_project_linking):
            return 0

        WRITE_LOG_GRN(
            'LINKING PROJECTS FINISHED' + '\n' +
            'GITHUB       | ---------------------------------------' + '\n' +
            '             | repos_id:     ' + str(repos_id_gh) + '\n' +
            '             | repos_url:    ' + url_gh + '\n' +
            'REDMINE      | ---------------------------------------' + '\n' +
            '             | project_id:   ' + str(project_id_rm) + '\n' +
            '             | project_url:  ' + url_rm + '\n' + '=' * 35 + ' ' +
            str(datetime.datetime.today()) + ' ' + '=' * 35 + '\n')

    # проверка, что проекты уже связаны
    def load_linked_projects(project_id_rm, repos_id_gh):

        linked_projects = Linked_Projects.objects.get_linked_projects(
            project_id_rm, repos_id_gh)

        if (len(linked_projects) < 1):

            linked_projects = Linked_Projects.objects.create_linked_projects(
                project_id_rm, repos_id_gh, url_rm, url_gh)
            return linked_projects

        else:
            return linked_projects[0]  # будет только один связанный проект

    log_link_projects_start()

    # занесение в базу данных информацию о том, что данные проекты связаны
    linked_projects = load_linked_projects(project_id_rm, repos_id_gh)

    # ============================== СОЗДАНИЕ НЕОБХОДИМЫХ ДЛЯ РАБОТЫ LABEL-ОВ В ГИТХАБЕ ================================

    # создание label-ов в гитхабе
    github_label_template = read_file('data/github_label_template.json')
    github_label_template = Template(
        github_label_template)  # шаблон создания label-ов

    labels_url_gh = api_url_gh + '/labels'

    # загрузка label-ов, необходимых для связи с гитхабом
    github_labels = read_file('data/github_labels.json')
    github_labels = json.loads(github_labels)

    labels = github_labels['labels']

    response_text = 'Projects posted successfully!\n' +\
                    "(or not, I actually don't know)\n" +\
                    "Labels:\n\n"

    # загрузка label-ов в гитхаб
    def log_label_post(label, post_result):

        if (not allow_log_project_linking):
            return 0
        if (not detailed_log_project_linking):
            return 0

        post_result_text = str(post_result.text)

        log_text = '\n' + '-' * 35 + ' ' + str(datetime.datetime.today()) + ' ' + '-' * 35 + '\n' +\
                   'POSTing new label to GITHUB:' + '\n' +\
                   'LABEL        | ---------------------------------------' + '\n' +\
                   '             | name:         ' + label['name'] + '\n' +\
                   '             | description:  ' + label['description'] + '\n' +\
                   '             | color:        ' + label['color'] + '\n' +\
                   '             | default:      ' + label['default'] + '\n' +\
                   'POST RESULT  | ---------------------------------------' + '\n' +\
                   '             | status:       ' + str(post_result) + '\n' +\
                   '             | text:         ' + post_result_text + '\n'

        if (post_result.status_code == 201):
            WRITE_LOG(log_text)

        # скорее всего, label просто уже существует
        elif (post_result.status_code == 422):
            WRITE_LOG_WAR(log_text)

        else:
            WRITE_LOG_ERR(log_text)

    # TODO: исправить постинг label-ов (не приходит description)
    # TODO: исправить постинг label-ов (не приходит default)
    # постим label-ы в гитхаб
    for label in labels:

        # загружаем label-ы
        label_templated = github_label_template.render(
            name=label['name'],
            description=label['description'],
            color=label['color'],
            default=label['default'])

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        label_templated = label_templated.encode('utf-8')

        request_result = requests.post(labels_url_gh,
                                       data=label_templated,
                                       headers=headers_gh)

        log_text = ''
        log_text += label['name'] + '\n'
        log_text += str(request_result) + '\n'
        log_text += str(request_result.text) + '\n'

        # ДЕБАГГИНГ
        log_label_post(label, request_result)

        response_text += log_text

    # =================================== ЗАГРУЗКА ВСЕХ ISSUE ИЗ ГИТХАБА В РЕДМАЙН =====================================

    # запрос issues и комментариев к ним из гитхаба и отправка в редмайн
    query_data_gh_to_rm(linked_projects)

    log_link_projects_finish()

    # TODO: выдавать то же, что выдавалось бы без переопределения метода create
    return HttpResponse(response_text.replace('\n', '<br>'),
                        status=200)  # <br> - новая строка в html
Exemple #6
0
def process_payload_from_rm(payload):

    # =================================================== ПОДГОТОВКА ===================================================

    def parse_payload(payload):

        payload = payload[
            'payload']  # достаём содержимое payload. payload payload. payload? payload!

        payload_parsed = {}  # словарь issue (название, описание, ссылка)

        # автор issue
        payload_parsed['issue_author_id'] = payload['issue']['author']['id']
        payload_parsed['issue_author_login'] = payload['issue']['author'][
            'login']
        payload_parsed['issue_author_firstname'] = payload['issue']['author'][
            'firstname']
        payload_parsed['issue_author_lastname'] = payload['issue']['author'][
            'lastname']

        payload_parsed['action'] = payload['action']  # совершённое действие

        # при update возможна добавка комментария
        if (payload_parsed['action'] == 'updated'):

            # тело комментария
            payload_parsed['comment_body'] = payload['journal']['notes']

            # id комментария (для связи и логов)
            payload_parsed['comment_id'] = payload['journal']['id']

            # автор комментария
            payload_parsed['comment_author_id'] = payload['journal']['author'][
                'id']
            payload_parsed['comment_author_login'] = payload['journal'][
                'author']['login']
            payload_parsed['comment_author_firstname'] = payload['journal'][
                'author']['firstname']
            payload_parsed['comment_author_lastname'] = payload['journal'][
                'author']['lastname']

        # заполение полей issue
        payload_parsed['issue_title'] = payload['issue']['subject']
        payload_parsed['issue_body'] = payload['issue']['description']
        payload_parsed['tracker_id'] = payload['issue']['tracker']['id']
        payload_parsed['status_id'] = payload['issue']['status']['id']
        payload_parsed['priority_id'] = payload['issue']['priority']['id']

        # идентификаторы (для связи и логов)
        payload_parsed['issue_id'] = payload['issue']['id']
        payload_parsed['project_id'] = payload['issue']['project']['id']

        # ссылка на issue (для фразы бота и логов)
        payload_parsed['issue_url'] = payload['url']

        return payload_parsed

    try:
        issue = parse_payload(payload)

    except:

        error_text = 'ERROR: unknown payload type'

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                  ' ' + '=' * 35 + '\n' +
                  'received webhook from REDMINE: issues' + '\n' + error_text)

        return HttpResponse(error_text, status=200)

    # авторизация в гитхабе по токену
    api_key_github = read_file(
        'api_keys/api_key_github.txt')  # загрузка ключа для github api
    api_key_github = api_key_github.replace(
        '\n', '')  # избавляемся от \n в конце строки

    # загрузка issue template из файла
    issue_github_template = read_file('data/issue_github_template.json')
    issue_github_template = Template(
        issue_github_template)  # шаблон для каждого issue

    # загрузка comment template из файла
    comment_github_template = read_file('data/comment_github_template.json')
    comment_github_template = Template(
        comment_github_template)  # шаблон для каждого issue

    # заголовки авторизации и приложения, при отправке запросов на гитхаб
    headers = {
        'Authorization': 'token ' + api_key_github,
        'Content-Type': 'application/json'
    }

    # ============================================ ВСПОМОГАТЕЛЬНЫЕ КОМАНДЫ =============================================

    # issue_body
    # comment_body
    # comment_body_action
    # issue_label
    # добавляем фразу бота
    def add_bot_phrase(issue, to):

        # добавляем фразу бота к описанию issue
        if (to == 'issue_body'):

            firstname = issue['issue_author_firstname']
            lastname = issue['issue_author_lastname']
            login = issue['issue_author_login']
            # добавляем фразу бота
            issue_body = '>I am a bot, bleep-bloop.\n' +\
                         '>' + firstname + ' ' + lastname + ' (' + login + ') Has opened the issue in Redmine.\n\n' +\
                         issue['issue_body']

            return issue_body

        # добавляем фразу бота к комментарию
        elif (to == 'comment_body'):

            firstname = issue['comment_author_firstname']
            lastname = issue['comment_author_lastname']
            login = issue['comment_author_login']
            # добавляем фразу бота
            comment_body = '>I am a bot, bleep-bloop.\n' + \
                           '>' + firstname + ' ' + lastname + ' (' + login + ') Has commented / edited with comment the issue in Redmine.\n\n' +\
                           issue['comment_body']

            return comment_body

        # добавляем фразу бота (комментарием) к действию в редмайне (закрыл, изменил и т.д.)
        elif (to == 'comment_body_action'):

            firstname = issue['comment_author_firstname']
            lastname = issue['comment_author_lastname']
            login = issue['comment_author_login']
            # добавляем фразу бота
            comment_body = 'I am a bot, bleep-bloop.\n' +\
                           firstname + ' ' + lastname + ' (' + login + ') Has edited the issue in Redmine.\n\n'

            return comment_body

        else:

            WRITE_LOG(
                "\nERROR: process_payload_from_rm.add_bot_phrase - unknown parameter 'to': "
                + to + '.' + "\nPlease, check your code on possible typos." +
                "\nAlternatively, add logic to process '" + to +
                "' action correctly.\n")

            return None

    # обновление linked_issues в базе данных сервера (tracker_id, status_id, priority_id)
    def update_linked_issues(linked_issues, issue):

        linked_issues.tracker_id_rm = issue['tracker_id']
        linked_issues.priority_id_rm = issue['priority_id']

        # если rejected
        if (issue['status_id'] == status_ids_rm[4]):
            linked_issues.is_opened = False
            linked_issues.status_id_rm = issue['status_id']

        # если closed
        elif (issue['status_id'] == status_ids_rm[5]):
            linked_issues.is_opened = False

        # иначе - открываем
        else:
            linked_issues.is_opened = True
            linked_issues.status_id_rm = issue['status_id']

        linked_issues.save()

    # типичные ошибки на этапе проверки: не связаны проекты, задачи, комментарии, неизвестное действие и т.п.
    def PREPARATION_ERR(error_text):

        # добавляем, чтобы в начале получилось: 'PREPARATION ERROR'
        error_text = 'PREPARATION ' + error_text

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                  ' ' + '=' * 35 + '\n' +
                  'received webhook from REDMINE: issues | ' + 'action: ' +
                  str(issue['action']) + '\n' + error_text)

        return HttpResponse(error_text, status=200)

    # логическая ошибка: неизвестное действие, неправильные label-ы в гитхабе и т.п.
    def LOGICAL_ERR(error_text):

        # добавляем, чтобы в начале получилось: 'LOGICAL ERROR'
        error_text = 'LOGICAL ' + error_text

        WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                  ' ' + '=' * 35 + '\n' +
                  'received webhook from REDMINE: issues | ' + 'action: ' +
                  str(issue['action']) + '\n' + error_text)

        return HttpResponse(error_text, status=200)

    # ============================================= КОМАНДЫ ДЛЯ ЗАГРУЗКИ ===============================================

    def post_issue(linked_projects, issue):

        # ----------------------------------------------- ПОДГОТОВКА -----------------------------------------------

        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_payload_from_rm.post_issue\n" +\
                         "issue " + str(issue['action']) + " in REDMINE, but the project is not linked to GITHUB"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        repos_id_gh = linked_projects.repos_id_gh
        url_gh = make_gh_repos_url(repos_id_gh)

        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------

        #title = '[From Redmine] ' + issue['issue_title']
        title = issue['issue_title']
        issue_body = add_bot_phrase(issue,
                                    'issue_body')  # добавляем фразу бота

        # обработка спец. символов
        title = align_special_symbols(title)
        issue_body = align_special_symbols(issue_body)

        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ----------------------------------------

        # добавление label-ов
        tracker = match_tracker_to_gh(issue['tracker_id'])
        status = match_status_to_gh(issue['status_id'])
        priority = match_priority_to_gh(issue['priority_id'])

        issue_templated = issue_github_template.render(title=title,
                                                       body=issue_body,
                                                       priority=priority,
                                                       status=status,
                                                       tracker=tracker)

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        request_result = requests.post(url_gh,
                                       data=issue_templated,
                                       headers=headers)

        # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ --------------------------------------------

        posted_issue = json.loads(request_result.text)

        # занесение в базу данных информацию о том, что данные issues связаны
        linked_issues = Linked_Issues.objects.create_linked_issues(
            issue['issue_id'],  # id issue в редмайне
            posted_issue['id'],  # id issue в гитхабе
            repos_id_gh,  # id репозитория в гитхабе
            posted_issue['number'],  # номер issue  в репозитории гитхаба
            issue['tracker_id'],  # id трекера в редмайне
            issue['status_id'],  # id статуса в редмайне
            issue['priority_id'],  # id приоритета в редмайне
            True)  # открыт

        # добавляем linked_issues в linked_projects
        linked_projects.add_linked_issues(linked_issues)

        # ДЕБАГГИНГ
        log_issue_post_rm(request_result, issue, linked_issues)

        return request_result

    # загрузка комментария. нет необходимости в подготовке, так как запускается из edit_issue
    # (redmine не различает оставление комментария и изменение issue)
    def post_comment(linked_issues, issue, url_gh):

        # ------------------------------------------ ОБРАБОТКА ФРАЗЫ БОТА -----------------------------------------

        # нет комментария
        if (issue['comment_body'] == ''):
            comment_body = add_bot_phrase(
                issue, 'comment_body_action')  # добавляем фразу бота
        else:
            comment_body = add_bot_phrase(
                issue, 'comment_body')  # добавляем фразу бота

        # обработка спец. символов
        comment_body = align_special_symbols(comment_body)

        # --------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ----------------------------------------

        comment_templated = comment_github_template.render(body=comment_body)

        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        comment_templated = comment_templated.encode('utf-8')

        # добавление issue_id к ссылке
        issue_comments_url_gh = url_gh + '/' + str(
            linked_issues.issue_num_gh) + '/comments'
        request_result = requests.post(issue_comments_url_gh,
                                       data=comment_templated,
                                       headers=headers)

        # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ --------------------------------------------

        #занесение в базу данных информации о том, что комментарии связаны
        posted_comment = json.loads(request_result.text)
        linked_comments = linked_issues.add_comment(issue['comment_id'],
                                                    posted_comment['id'])

        # ДЕБАГГИНГ
        log_comment_rm(request_result, issue, linked_issues, linked_comments)

        return request_result

    def edit_issue(linked_projects, issue):

        # ----------------------------------------------- ПОДГОТОВКА -----------------------------------------------

        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_payload_from_rm.edit_issue\n" +\
                         "issue " + str(issue['action']) + " in REDMINE, but the project is not linked to GITHUB"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        repos_id_gh = linked_projects.repos_id_gh
        url_gh = make_gh_repos_url(repos_id_gh)

        linked_issues = linked_projects.get_issue_by_id_rm(issue['issue_id'])

        # дополнительная проверка, что issue связаны
        if (linked_issues.count() == 0):

            error_text = "ERROR: process_payload_from_rm.edit_issue\n" +\
                         "issue " + str(issue['action']) + " in REDMINE, but it's not linked to GITHUB"

            return PREPARATION_ERR(error_text)

        linked_issues = linked_issues[0]

        # добавление label-ов
        state_gh = "opened"  # открыть / закрыть issue
        tracker = match_tracker_to_gh(issue['tracker_id'])
        priority = match_priority_to_gh(issue['priority_id'])

        if (issue['status_id'] != linked_issues.status_id_rm):
            status = match_status_to_gh(issue['status_id'])

            if (status == 'Status: closed'):
                status = match_status_to_gh(
                    linked_issues.status_id_rm
                )  # не меняем статус (нет label-а closed)
                state_gh = 'closed'

            elif (status == 'Status: rejected'):
                state_gh = 'closed'
        else:
            status = match_status_to_gh(linked_issues.status_id_rm)

        post_comment(linked_issues, issue,
                     url_gh)  # ОТПРАВЛЯЕМ КОММЕНТАРИЙ В ГИТХАБ

        # ----------------------------------------- ОБРАБОТКА ФРАЗЫ БОТА -------------------------------------------

        #title = '[From Redmine (edited)] ' + issue['issue_title']
        title = issue['issue_title']

        # проверяем, если автор issue - бот
        if (chk_if_rm_user_is_our_bot(issue['issue_author_id'])):
            issue_body = del_bot_phrase(
                issue['issue_body'])  # удаляем фразу бота

        else:
            issue_body = add_bot_phrase(issue,
                                        'issue_body')  # добавляем фразу бота

        # обработка спец. символов
        title = align_special_symbols(title)
        issue_body = align_special_symbols(issue_body)

        # ---------------------------------------- ЗАГРУЗКА ДАННЫХ В ГИТХАБ ----------------------------------------

        issue_templated = issue_github_template.render(title=title,
                                                       body=issue_body,
                                                       state=state_gh,
                                                       priority=priority,
                                                       status=status,
                                                       tracker=tracker)
        # кодировка Latin-1 на некоторых задачах приводит к ошибке кодировки в питоне
        issue_templated = issue_templated.encode('utf-8')

        # добавление issue_id к ссылке
        issue_url_gh = url_gh + '/' + str(linked_issues.issue_num_gh)
        request_result = requests.patch(issue_url_gh,
                                        data=issue_templated,
                                        headers=headers)

        # ------------------------------------------ СОХРАНЕНИЕ ДАННЫХ --------------------------------------------

        # обновляем информацию в таблице
        update_linked_issues(linked_issues, issue)

        # ДЕБАГГИНГ
        log_issue_edit_rm(request_result, issue, linked_issues)

        return request_result

    # привязка комментария на редмайне к гитхабу (да, это костыль)
    # пришлось привязать к id комментария в фразе бота на редмайне (редмайн не посылает внятный ответ на PUT запрос)
    def link_comment_to_github(linked_projects, issue):

        # ----------------------------------------------- ПОДГОТОВКА -----------------------------------------------

        # дополнительная проверка, что проекты связаны
        if (linked_projects.count() == 0):

            error_text = "ERROR: process_payload_from_rm.link_comment_to_github\n" +\
                         "tried to link comment from REDMINE to GITHUB, but the project is not linked to GITHUB"

            return PREPARATION_ERR(error_text)

        linked_projects = linked_projects[0]

        linked_issues = linked_projects.get_issue_by_id_rm(issue['issue_id'])

        # дополнительная проверка, что issue связаны
        if (linked_issues.count() == 0):

            error_text = "ERROR: process_payload_from_rm.link_comment_to_github\n" +\
                         "tried to link comment from REDMINE to GITHUB, but the issue is not linked to GITHUB"

            return PREPARATION_ERR(error_text)

        linked_issues = linked_issues[0]

        # определяем действие (определяем, нужно ли привязывать комментарий к гитхабу -
        # комментарий от бота может оказаться сообщением о действии пользователя на гитхабе
        action = issue['comment_body'].split(' ')[6]
        if (action == 'left'):

            # достаём id комментария в гитхабе
            comment_id_gh_str = issue['comment_body'].split(
                '#issuecomment-')[1]
            comment_id_gh_str = comment_id_gh_str.split(' ')[0]
            comment_id_gh = int(comment_id_gh_str)

            # занесение в базу данных информацию о том, что комментарии связаны
            linked_comments = linked_issues.add_comment(
                issue['comment_id'], comment_id_gh)

            responce_text = log_link_comment_crutch(issue, linked_comments)
            return HttpResponse(responce_text, status=201)

        else:

            error_text = prevent_cyclic_comment_rm(issue)
            return HttpResponse(error_text, status=200)

    # ============================================ ЗАГРУЗКА ISSUE В GITHUB =============================================

    linked_projects = Linked_Projects.objects.get_project_by_id_rm(
        issue['project_id'])

    if (issue['action'] == 'opened'):

        if (chk_if_rm_user_is_our_bot(issue['issue_author_id'])):

            error_text = prevent_cyclic_issue_rm(issue)
            return HttpResponse(error_text, status=200)

        if (allow_issues_post_rm_to_gh):
            request_result = post_issue(linked_projects, issue)

        else:

            error_text = "WARNING: process_payload_from_rm\n" + \
                         "PROHIBITED ACTION"

            WRITE_LOG('\n' + '=' * 35 + ' ' + str(datetime.datetime.today()) +
                      ' ' + '=' * 35 + '\n' +
                      'received webhook from REDMINE: issues | ' + 'action: ' +
                      str(issue['action']) + '\n' + error_text)

            return HttpResponse(error_text, status=200)

    elif (issue['action'] == 'updated'):

        if (chk_if_rm_user_is_our_bot(issue['comment_author_id'])):

            # попытка связать комментарий на редмайне с гитхабом
            return link_comment_to_github(linked_projects, issue)

        # изменение issue + добавление комментария
        request_result = edit_issue(linked_projects, issue)

    else:

        error_text = "ERROR: process_payload_from_rm\n" + \
                     "WRONG ACTION"

        return LOGICAL_ERR(error_text)

    return align_request_result(request_result)