edulc/ed_reg.py

306 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# вызов с параметром -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()