forked from kmsdc/edulc
306 lines
14 KiB
Python
306 lines
14 KiB
Python
# вызов с параметром -c --config Имя_файла_конфигурации
|
||
#
|
||
#
|
||
import json
|
||
from redminelib import Redmine
|
||
from configparser import ConfigParser
|
||
import logging
|
||
import sys
|
||
import argparse
|
||
from lpe import make_doc
|
||
|
||
|
||
|
||
global redmine # объект для подключения к redmine
|
||
global config # объект для доутспа к настройкам
|
||
global conf_cf # config['CustomFilelds']
|
||
|
||
global courses_list # список префиксов курсов
|
||
EXIT_ERROR = 1
|
||
|
||
|
||
|
||
|
||
def createParser ():
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument ('-c', '--config', nargs=1, required=True)
|
||
|
||
return parser
|
||
|
||
|
||
|
||
# возвращает объект для подключени к редмайн.
|
||
# предполагается, что в глобальной переменной config уже находится объект для доступа к настройкам
|
||
# в этой функции бесполезно проверять exception, так как реально обращнеие к редмайн
|
||
# производится только при операциях
|
||
def set_redmine_connection(key_name):
|
||
try:
|
||
conn = config['Connection']
|
||
HOST = conn ['HOST']
|
||
KEY = conn[key_name]
|
||
REDMINE_VERSION = conn['REDMINE_VERSION']
|
||
|
||
except KeyError:
|
||
logger.exception ("Неверные или отсутствуют параметры подключения к Redmine в файле конфигурации")
|
||
exit(EXIT_ERROR)
|
||
else:
|
||
return(Redmine(HOST, key = KEY, version = REDMINE_VERSION, requests={'verify': True}))
|
||
|
||
|
||
|
||
|
||
# на входе list - список словарей. Ищем в нем словарь, где поле key == key_value и возаращам первый найденный словарь
|
||
def get_dict_from_list_by_key (list, key, key_value):
|
||
try:
|
||
if len(list) != 0:
|
||
for el in list:
|
||
if el[key] == key_value:
|
||
return(el)
|
||
raise Exception ('get_dict_from_list_by_keys: Не найден словарь по ключу или пустой список словарей.')
|
||
except:
|
||
logger.exception ('get_dict_from_list_by_keys: Не найден словарь. \n Список на входе = {list} \n Ключ = {key} \n key_value = {key_value}'.
|
||
format(list=list, key=key, key_value=key_value))
|
||
exit (EXIT_ERROR)
|
||
|
||
|
||
def get_student_contact_ids_from_issue(issue):
|
||
contact_id_list = []
|
||
students_contacts = []
|
||
# читаем номер кастом поля Список студентов
|
||
key_value=conf_cf.getint('Students_contacts_cf_id')
|
||
students_custom_field = issue.custom_fields.get(key_value)
|
||
logger.debug ('get_student_contact_ids_from_issues: students_custom_field = {list}'.
|
||
format( list=list(students_custom_field) ) )
|
||
|
||
# В этом словаре список контактов - это поле с ключом 'value'. Собираем из этого поля значения в список
|
||
for v in students_custom_field['value']:
|
||
try:
|
||
contact_id_list.append(int(v))
|
||
except ValueError: # в этом поле бывает всякая фигня, например ['16', '17', '18', '[""]', '6']
|
||
pass
|
||
|
||
# а теперь по списку номеров контактов строим список контактов и возвращаем его
|
||
#for c_id in contact_id_list:
|
||
# students_contacts.append(redmine.contact.get(c_id)).values()
|
||
|
||
return(contact_id_list)
|
||
|
||
# получение всех задач в проекте Продажи Альт
|
||
# c трекером Проведение курса и статусом Курс согласован
|
||
#
|
||
def get_course_conduct_issues ():
|
||
issues = redmine.issue.filter(
|
||
project_id = config ['Projects']['Main_Sales_Project'],
|
||
tracker_id = config.getint ('Trackers','Conduct_Course_Tracker_id'),
|
||
status_id = config.getint('Statuses','Course_Confirmed_id')
|
||
).values()
|
||
#. Так можно получить issues в виде dictionary. а не объекта ResourseSet
|
||
return(issues)
|
||
|
||
|
||
def get_related_issues_ids (issue):
|
||
related_issues = []
|
||
rlist = issue['children']
|
||
for ri in rlist:
|
||
related_issues.append(str(ri['issue_to_id']))
|
||
|
||
logger.debug ("get_related_issues_ids: related issues = {rl}".format(rl=related_issues) )
|
||
return(related_issues)
|
||
|
||
# На входе - задача (main_issue) Проведение курса. На выходе - список всех студентов из всех задач Обучение,
|
||
# свзяанных с задачей main_issue
|
||
def get_enrolled_contact_ids (child_issues_ids):
|
||
enrolled_contacts = []
|
||
|
||
if child_issues_ids == []:
|
||
return (enrolled_contacts)
|
||
|
||
|
||
children_issues = redmine.issue.filter (
|
||
issue_id = ','.join([str(x) for x in child_issues_ids]),
|
||
project_id=config ['Projects']['Main_Courses_Project'],
|
||
status_id = '*'
|
||
).values('id','custom_fields' )
|
||
|
||
key_value= conf_cf.getint('Enrolled_Contact_cf_id')
|
||
for ri in children_issues:
|
||
enrolled_contacts.append (int(get_dict_from_list_by_key
|
||
(ri['custom_fields'], key='id', key_value=key_value)['value']
|
||
))
|
||
|
||
return (enrolled_contacts)
|
||
|
||
def add_enroll_issue (scid, issue):
|
||
|
||
|
||
# Поле Тип курса
|
||
cf_course_type = issue.custom_fields.get(conf_cf.getint("Type_of_course"))['value']
|
||
|
||
# Поле Курс обучения. Из него извлекаем Код курса для последующуего определения id проекта
|
||
course_name = issue.custom_fields.get(conf_cf.getint("Course"))['value']
|
||
course_code = course_name.split('.')[0]
|
||
|
||
project_dict = get_dict_from_list_by_key (
|
||
Courses_List,
|
||
key='kurs',
|
||
key_value = course_code )
|
||
|
||
cf_group_value = issue.custom_fields.get (conf_cf.getint("Conduct_Course_Issue_Group_id") ) ['value']
|
||
|
||
logger.debug ('add_enroll_issue: cf_group_value = {cgv}'.format (cgv=cf_group_value) )
|
||
logger.debug ('add_enroll_issue: cf_course_type = {cct}'.format(cct=cf_course_type) )
|
||
logger.debug ("add_enroll_issue: Название курса = {cn}".format(cn=course_name) )
|
||
logger.debug ("add_enroll_issue: project_dict['project_id'] = {prid}".format( prid=project_dict['project_id']) )
|
||
|
||
custom_fields = [ \
|
||
{'id':int (conf_cf["Enroll_issue_Group_id"]), 'value':cf_group_value}, \
|
||
{'id':int (conf_cf["Enrolled_Contact_cf_id"]), 'value':scid}, \
|
||
{'id':int (conf_cf["Type_of_course"]), 'value':cf_course_type}
|
||
]
|
||
logger.debug ("add_enroll_issue: issue = {iss}".format(iss=list(issue)) )
|
||
|
||
new_issue = redmine.issue.create(
|
||
project_id = project_dict['project_id'],
|
||
subject = course_name,
|
||
tracker_id = int (config ['Trackers']['Enroll_course_Tracker_id']),
|
||
description = config ['Messages']['Contact_course_descr'],
|
||
status_id = int(config ['Statuses']['Registered_for_course']),
|
||
assigned_to_id = issue.assigned_to ['id'],
|
||
start_date = issue.start_date, #(string or date object) – (optional). Issue start date.
|
||
due_date = issue.due_date, # (string or date object) – (optional). Issue end date.
|
||
#watcher_user_ids (list) – (optional). User ids watching this issue.
|
||
parent_issue_id = issue.id, # (int) – (optional). Parent issue id.
|
||
custom_fields = custom_fields
|
||
#uploads (list) – (optional). Uploads as [{'': ''}, ...], accepted keys are:
|
||
#path (required). Absolute file path or file-like object that should be uploaded.
|
||
#filename (optional). Name of the file after upload.
|
||
#description (optional). Description of the file.
|
||
#content_type (optional). Content type of the file.
|
||
)
|
||
|
||
return new_issue
|
||
|
||
def enroll_students():
|
||
project_id = config ['Projects']['Main_Sales_Project']
|
||
tracker_id = config.getint('Trackers','Conduct_Course_Tracker_id')
|
||
status_id = config.getint ('Statuses','Course_Confirmed_id')
|
||
|
||
# Берем все задачи с трекером Проведение курса в статусе Курс согласован
|
||
for issue in redmine.issue.filter(
|
||
project_id = project_id ,
|
||
tracker_id = tracker_id,
|
||
status_id = status_id
|
||
):
|
||
|
||
children_issues_ids = []
|
||
# Для каждой такой задачи : ...
|
||
|
||
# .... берем ее дочерние задачи (то есть задачи с трекером Обучение)
|
||
for child_issue in redmine.issue.get(int(issue.id), include=['children']).children:
|
||
children_issues_ids.append(child_issue.id) # и собираем номера дочерних задач
|
||
|
||
# .... Получаем номера контактов, которые указаны в issue (с трекером Проведение курса)
|
||
students_contact_ids = get_student_contact_ids_from_issue(issue)
|
||
|
||
# .... Получаем список студентов, которые уже записаны на курс по данной issue
|
||
enrolled_contact_ids = get_enrolled_contact_ids (children_issues_ids)
|
||
#print ("students_contact_ids = ", students_contact_ids, "\n",
|
||
# "children_issues_ids = ", children_issues_ids, "\n",
|
||
# "enrolled_contact_ids = ", enrolled_contact_ids)
|
||
|
||
# если контакт есть в задаче Провдение курса , но его нет в дочерних задачах, то создаем новую дочернюю задачу
|
||
for scid in students_contact_ids:
|
||
if scid not in (enrolled_contact_ids):
|
||
ei = add_enroll_issue(scid, issue)
|
||
# print(list(ei))
|
||
|
||
|
||
|
||
def provide_documents():
|
||
Template_name = config.get('Print', 'Svid_name')
|
||
for issue in redmine.issue.filter(
|
||
project_id = config ['Projects']['Main_Sales_Project'],
|
||
tracker_id = config.getint('Trackers','Conduct_Course_Tracker_id'),
|
||
status_id = '*'
|
||
):
|
||
|
||
cf_generate = issue.custom_fields.get(conf_cf.getint('Generate_cert_cf_id'))
|
||
if dict(cf_generate)['value'] != '' : # значит генерируем сертфикаты для дочерних задач
|
||
#print (list(issue))
|
||
tutor_id = issue.custom_fields.get(conf_cf.getint('Tutor_cf_id'))['value']
|
||
#print('tutor_id = ', tutor_id)
|
||
tutor = redmine.contact.get(tutor_id)
|
||
tutor_fio = tutor.last_name + " " + tutor.first_name + " " + tutor.middle_name
|
||
|
||
for child_issue in redmine.issue.get(int(issue.id), include=['children']).children:
|
||
chi = redmine.issue.get(child_issue.id, inclide = ['attachments'])
|
||
Doc_already_present = False
|
||
for attach in chi.attachments:
|
||
if Template_name in attach.description:
|
||
Doc_already_present = True
|
||
break
|
||
|
||
if not Doc_already_present and chi.status['id'] == config.getint ('Statuses','Course_completed'):
|
||
logger.debug ("генерируем сертификат ..... chi.status['id'] = {chi}".format(chi=chi.status['id'] ) )
|
||
|
||
student_id = chi.custom_fields.get(conf_cf.getint('Enrolled_Contact_cf_id'))['value']
|
||
student = redmine.contact.get(student_id)
|
||
st_fio = student.last_name + " " + student.first_name + " " + student.middle_name
|
||
# make_doc(st_fio, tutor_fio)
|
||
|
||
order = {'student_fio':st_fio, 'tutor_fio':tutor_fio, \
|
||
'issuedate':chi.due_date.strftime("%d.%m.%Y"), 'kursname':chi.subject
|
||
}
|
||
|
||
result = make_doc(order, Template_name, child_issue.id)
|
||
redmine.issue.update(chi.id, uploads = result)
|
||
|
||
# В главной задаче Проведение курса выключить рубильник "Готовить сертификаты"
|
||
custom_fields = [{'id':conf_cf.getint('Generate_cert_cf_id'), 'value':''}]
|
||
redmine.issue.update(issue.id, custom_fields = custom_fields)
|
||
|
||
|
||
def main():
|
||
|
||
# создание задач Обучение на контакты из задачи Проведение курса
|
||
logger.info ("Starting - enroll_students")
|
||
enroll_students()
|
||
logger.info ("Finished - enroll_students")
|
||
|
||
# cоздание свидетельств о прохождении курса для тех студентов, которые его прошли
|
||
# но только если в главной задаче Проведение курса включен рубильник Готовить сертификаты
|
||
provide_documents()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
# разбираем параметры комндной строки.
|
||
parser = createParser()
|
||
namespace = parser.parse_args(sys.argv[1:])
|
||
|
||
config = ConfigParser()
|
||
if config.read(namespace.config) == []:
|
||
raise Exception ('Не найден файл конфигурации: {f}'.format(f=namespace.config))
|
||
|
||
conf_cf = config ['CustomFields']
|
||
|
||
Courses_List = json.loads(config.get ('Projects','Courses_List'))
|
||
|
||
logger = logging.getLogger(__name__)
|
||
logger.setLevel(config.get('Logging','LogLevel'))
|
||
# create the logging file handler
|
||
# fh = logging.FileHandler(config.get('Logging','LogFileName'))
|
||
|
||
fh = logging.StreamHandler(sys.stderr)
|
||
|
||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||
fh.setFormatter(formatter)
|
||
|
||
# add handler to logger object
|
||
logger.addHandler(fh)
|
||
|
||
logger.info ("Program started")
|
||
|
||
redmine = set_redmine_connection(key_name='API_KEY')
|
||
|
||
main()
|