# -*- coding: utf-8
# $Id$
# Copyright 2001, 2002 by IVA Team and contributors
#
# This file is part of IVA.
#
# IVA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# IVA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with IVA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Contains class CourseManager, which acts as a factory object for Courses and also the container for the courses."""
__version__ = '$Revision$'[11:-2]
import re, string, time
import strptime
import OFS, Globals, AccessControl
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2
from Globals import Persistent, PersistentMapping
import types
from Products.ZCatalog.ZCatalog import ZCatalog
# from TraversableWrapper import TraversableWrapper as TW
from TraversableWrapper import Traversable
from Course import Course
from common import quote_html_hack, make_action
from common import perm_view, perm_edit, perm_manage, perm_add_lo, perm_access,\
roles_admin, roles_staff, roles_user, translate
from input_checks import is_valid_title
from Thread import Thread
from AccessControl import ClassSecurityInfo
from Cruft import Cruft
from common import course_level_roles
from common import roles_teacher, roles_tutor, roles_student
import time
from zope.interface import implements
from interfaces import ICourseManager, IStatistics, IUserManager
from zope.component import adapter, getUtility
from zope.app.container.interfaces import IObjectAddedEvent
import transaction
class IDManager(Thread):
security = ClassSecurityInfo()
def __init__(self):
self.__id_counter = 0L
# Overrides generate_id from Thread!
security.declarePrivate('generate_id')
def generate_id(self):
"""Return a probably random integer."""
self.__id_counter += 1L
return str(self.__id_counter)
# CourseManager exists in FLE/courses and contains all the courses
# that the FLE installation holds.
class CourseManager(
BTreeFolder2,
Traversable,
Cruft,
Persistent,
AccessControl.Role.RoleManager,
OFS.SimpleItem.Item,
IDManager,
):
"""FLE Coursemanager."""
meta_type = 'CourseManager'
implements(ICourseManager)
security = ClassSecurityInfo()
security.declareObjectPublic()
# No additional comments.
def __init__(self, id):
"""Construct Course manager object."""
self.id = id
BTreeFolder2.__init__(self, id)
self.title = ''
self.cCategories = PersistentMapping()
self.cCategories['groups'] = {}
IDManager.__init__(self)
catalog = ZCatalog('catalog_notes', 'ZCatalog for notes')
# indexes
catalog.addIndex('get_subject', 'TextIndex')
catalog.addIndex('get_body', 'TextIndex')
catalog.addIndex('get_author', 'FieldIndex')
catalog.addIndex('get_tt_id', 'FieldIndex')
# metadata
catalog.addColumn('get_subject')
catalog.addColumn('get_author')
catalog.addColumn('absolute_url')
self._setObject('catalog_notes', catalog)
self._p_changed = 1
# ZCatalog for indexing courses.
catalog = ZCatalog('courses_zcatalog', 'ZCatalog for courses')
# indexes
catalog.addIndex('get_courseID','FieldIndex')
catalog.addIndex('getCourseCategory','FieldIndex')
catalog.addIndex('getStatus','FieldIndex')
catalog.addIndex('getRegStatus','FieldIndex')
catalog.addIndex('get_all_users_id','KeywordIndex')
catalog.addIndex('get_end_date','FieldIndex') # XXX: Date index here maybe
catalog.addIndex('get_start_date','FieldIndex') # XXX: haven't tested it yet
catalog.addIndex('get_n_artefacts','FieldIndex') # XXX: after adding artefact
catalog.addIndex('get_n_notes','FieldIndex') # XXX: or note we need to reindex course
catalog.addIndex('get_n_users','FieldIndex')
catalog.addIndex('get_name','FieldIndex')
catalog.addIndex('get_teachers','FieldIndex')
catalog.addIndex('isLocked', 'FieldIndex')
catalog.addIndex('lastActive','FieldIndex') # XXX: should be fetch from somewhere else...
catalog.addIndex('lastModify','FieldIndex') # XXX
catalog.addIndex('meta_type','FieldIndex')
catalog.addIndex('getUniqId', 'FieldIndex')
# metadata
catalog.addColumn('getCourseCategory')
catalog.addColumn('getLogoURL')
catalog.addColumn('getStatus')
catalog.addColumn('getSubgroupList')
catalog.addColumn('getRegStatus')
catalog.addColumn('get_all_users_id')
catalog.addColumn('get_courseID')
catalog.addColumn('get_end_date')
catalog.addColumn('get_id')
catalog.addColumn('get_n_artefacts')
catalog.addColumn('get_n_notes')
catalog.addColumn('get_n_users')
catalog.addColumn('get_name')
catalog.addColumn('get_start_date')
catalog.addColumn('get_teachers')
catalog.addColumn('isLocked')
catalog.addColumn('lastActive')
catalog.addColumn('lastModify')
catalog.addColumn('meta_type')
catalog.addColumn('getLogoThumbnailURL')
self._setObject('courses_zcatalog', catalog)
self._p_changed = 1
def _setupZWikipermissions(self):
try:
self.manage_permission('Zwiki: Add pages', ('Teacher','Owner','User'), 0)
self.manage_permission('Zwiki: Add wikis', ('Teacher','Owner','User'), 0)
self.manage_permission('Zwiki: Change page types', ('Teacher','Owner','User'), 0)
self.manage_permission('Zwiki: Delete pages', ('Teacher','Owner','User'), 0)
self.manage_permission('Zwiki: Edit pages', ('Teacher','Owner','User'), 0)
self.manage_permission('Zwiki: Rename pages', ('Teacher','Owner','User'), 0)
self.manage_permission('Zwiki: Reparent pages', ('Teacher','Owner','User'), 0)
except ValueError:
# we don't have zwiki around
pass
def hasLogo(self):
return 0
security.declareProtected(perm_add_lo, 'add_course_impl')
def add_course_impl(self, teacher, uniq_id=None):
"""Implementation for add_course."""
if uniq_id is None:
suffix = '_'+str(int(time.time()))
uniq_id = self.generateId(prefix='course',suffix=suffix,rand_ceiling=99999999999)
obj = Course(
self,
'', # name
('',),
'', # desc
'', # organisation
'', # methods
'', # starting_date
'', # ending_date
uniq_id, # uniq_id
)
cid = obj.id
self.acl_users.groups.manage_addGroup(cid, '', '')
self.acl_users.groups.manage_addGroup(cid+'_teachers', '', '')
self._setObject(cid, obj)
nc = getattr(self, cid)
if teacher.strip():
nc.add_teachers((teacher,))
return cid
security.declareProtected(perm_manage, 'add_course_form_handler')
def add_course_form_handler(
self,
REQUEST,
course_id, my_name, desc, organisation, methods,
start_date, end_date,
creating_new_course='',
do_groupfolder='1',
cancel='', # submit buttons
add='', #
tekst='',
logo_upload=None,
staatus=0,
regStatus=0,
cCat = 0,
credits='',
courseID=''
):
"""Check user input data. this is for creating courses only for now."""
# return str(self.REQUEST.AUTHENTICATED_USER.getUserName())
if cancel:
# cancel button press
if creating_new_course:
REQUEST.RESPONSE.redirect(self.absolute_url())
else:
REQUEST.RESPONSE.redirect('course_info.html?course_id=%s' % course_id)
return
elif not add: raise 'FLE Error', 'Unknown button'
if not creating_new_course:
raise 'IVA Error', 'Calling add_course_form_handler without creating_new_course attribute'
# Ok, add button pressed
action=apply(
make_action,
['manage_course_info.html'] +
[(x, eval(x)) for x in
('my_name', 'desc', 'organisation', 'methods',
'start_date', 'end_date')])
if course_id:
action += '&course_id=' + course_id
else:
action += '&creating_new_course=1'
my_name=my_name.strip()
if not is_valid_title(my_name):
return self.restrictedTraverse('message_dialog_error.html')(
self, REQUEST,
title='Invalid name',
message='Give valid name',
action=action)
if creating_new_course and my_name in [x.get_name
for x in self.get_courses()]:
return self.restrictedTraverse('message_dialog_error.html')(
self, REQUEST,
title='Invalid name',
message="Name '%s' taken" % my_name,
action=action)
if not creating_new_course \
and my_name != self.get_child(course_id).get_name() \
and my_name in [x.get_name() for x in self.get_courses()]:
return self.restrictedTraverse('message_dialog_error.html')(
self, REQUEST,
title='Invalid name',
message="Name '%s' taken" % my_name,
action=action)
from common import convert_date # convert dates to time.time()-format
errors = []
if not start_date:
starting_date = 0
else:
try:
time_tuple = strptime.strptime(start_date,
translate(self,'short_date_format',default="%Y-%m-%d"))
starting_date = convert_date(str(time_tuple[2]), # day
str(time_tuple[1]), # month
str(time_tuple[0])) # year
except: errors.append(translate(self,'Starting date:'),)
if not end_date:
ending_date = 0
else:
try:
time_tuple = strptime.strptime(end_date,
translate(self,'short_date_format',default="%Y-%m-%d"))
ending_date = convert_date(str(time_tuple[2]), # day
str(time_tuple[1]), # month
str(time_tuple[0])) # year
except: errors.append(translate(self,'Ending date:'),)
organisation = organisation.strip()
if organisation and not is_valid_title(organisation):
errors.append(translate(self,'Organization'))
# desc and methods are not checked because render_description() and
# render_methods() methods in Course filter out unwanted HTML tags.
if len(errors) > 0:
return self.restrictedTraverse('message_dialog_error.html')(
self, REQUEST,
title='Invalid input',
message=translate(self,'Invalid fields') + ": '" + \
"' , '".join(errors) + "'",
action=action)
# edit an existing course
if creating_new_course:
# Create new course, then proceed with updating it with
# supplied data
teacher = ''
course_id = self.add_course_impl(teacher)
logo=None
try: #Opera puhul ajab puuduva pildi juures kägu
if len(logo_upload.filename)>0:
logo=logo_upload.read()
else:
logo=None
except:
pass
print "course_id", course_id
course_obj = self.get_child(course_id)
course_obj.update(
my_name,
desc,
organisation,
methods,
starting_date,
ending_date,
tekst,
logo,
staatus,
regStatus,
cCat,
credits,
courseID
)
if creating_new_course:
if do_groupfolder:
course_obj.add_folder('CourseFolder')
else:
if do_groupfolder and not course_obj.has_group_folder():
course_obj.add_folder('CourseFolder')
# elif not do_groupfolder and course_obj.has_group_folder():
# course_obj._delObject('gf')
if creating_new_course:
self.change_course(REQUEST, course_id)
REQUEST.RESPONSE.redirect(self.fle_url()+"/courses/"+course_id+"/manage_participants.html?pnimi=")
else:
REQUEST.RESPONSE.redirect(course_obj.absolute_url()+'/course_info.html')
#security.declareProtected(perm_edit, 'get_courses')
security.declarePublic('get_courses')
# No additional comments.
def get_courses(self, REQUEST=None, **kw):
"""Return a list of Course objects in this manager."""
doSort = 0
doFilter = 0
try:
doSort = int(REQUEST.get('sortc', 0))
except AttributeError:
pass
try:
if int(REQUEST.filter):
doFilter = int(REQUEST.filter)
except AttributeError:
pass
if doSort == 1:
return self.courses_zcatalog(meta_type='Course', sort_on='getCourseCategory')
elif doSort == 99 and doFilter:
return self.courses_zcatalog(meta_type='Course', getCourseCategory=doFilter, sort_on='getCourseCategory')
elif doSort == 10:
return self.courses_zcatalog(meta_type='Course', sort_on='getStatus')
elif doSort == 20:
return self.courses_zcatalog(meta_type='Course', sort_on='get_n_users')
elif doSort == 30:
return self.courses_zcatalog(meta_type='Course', sort_on='lastModify')
elif doSort == 40:
return self.courses_zcatalog(meta_type='Course', sort_on='lastActive')
elif doSort == 50:
return self.courses_zcatalog(meta_type='Course', sort_on='get_n_notes')
elif doSort == 60:
return self.courses_zcatalog(meta_type='Course', sort_on='get_n_artefacts')
elif doFilter:
return self.courses_zcatalog(meta_type='Course', getCourseCategory=doFilter, sort_on='getCourseCategory')
else:
return self.courses_zcatalog(meta_type='Course', sort_on='get_name', **kw)
security.declareProtected(perm_edit, 'get_course_by_uniqid')
def get_course_by_uniqid(self, uniqid):
""" get course by uniq id """
result = self.courses_zcatalog(meta_type='Course', getUniqId=uniqid)
if result:
return result[0]
return result
security.declareProtected(perm_manage, 'courses_form_handler')
def courses_form_handler(
self,
REQUEST,
delete='', # form submit button
course_export='',
course_ids=None,
):
"""Form handler for courses index page."""
import types
if int(self.kas_opetaja(REQUEST))!=1:
if not self.fle_root().fle_users.is_power_user(REQUEST.AUTHENTICATED_USER):
return "Access denied"
if not course_ids:
return self.restrictedTraverse('message_dialog_error.html')(
self, REQUEST,
title='No course selected',
message='Please select a course from the course list.',
action= self.absolute_url() )
if delete:
if type(course_ids) is types.StringType:
course_ids=(course_ids,)
return self.restrictedTraverse('message_dialog2.html')(
self, REQUEST,
title = 'Confirmation',
message = translate(self, 'Are you sure you want to delete the following courses:').encode('utf-8') + ' ' + \
', '.join([getattr(self, ci).get_name() for ci in course_ids]),
handler = 'delete_courses_form_handler',
extra_value_name = 'course_ids',
extra_values = course_ids,
option1_value = 'Cancel',
option1_name = 'cancel',
option2_value = 'Ok',
option2_name = 'delete')
elif course_export:
if type(course_ids) is types.StringType:
course_ids=(course_ids,)
message = self.do_courses_export(course_ids)
message = translate(self,'Exported course(s) are save to following location:')+message
return self.restrictedTraverse("manage_courses.html")(message=message)
else:
# This code should never be reached.
raise 'FLE Error', 'Unknown button'
security.declarePrivate('do_courses_export')
def do_courses_export(self,course_ids):
message = ""
for c_id in course_ids:
course = self.get_child(c_id)
message += '
' + course.iva_ims_export(teacherExporting=0)
transaction.commit()
return message
# FIXME: input_checks
security.declareProtected(perm_manage, 'delete_courses_form_handler')
def delete_courses_form_handler(
self,
REQUEST,
course_ids,
delete='',
cancel='',
):
"""Form handler that is called from message_dialog2."""
if delete:
for course_id in course_ids:
#for wt in [user.get_webtop() for user in
# self.get_child(course_id).get_all_users()]:
# wt.recursive_delete_group_folder_proxy(course_id)
[self.get_child(course_id).remove_person(user.get_uname()) for user in self.get_child(course_id).get_all_users()]
self._delObject(course_id)
elif cancel:
pass
else:
# This code should never be reached.
raise 'FLE Error', 'Unknown button'
REQUEST.RESPONSE.redirect(self.absolute_url())
security.declarePublic('has_courses')
def has_courses(self):
"""Return boolean describing whether there is an existing course on
the system."""
return not not self.get_children('Course')
def get_course_id_from_req(self, req):
"""Extract a course id from REQUEST. This is needed by
the CourseManager.course_selection dtml method."""
path = req.PATH_TRANSLATED
splitter = '/'
if splitter not in path:
splitter = '\\'
i = string.find(path, "courses")
ri = string.rfind(path, "courses")
if (i == -1) or (ri == -1):
return None
if (ri != i):
# There is more than one 'courses' word in path.
# We'll have to use the reverse one.
i = ri
plst = filter(lambda x:x, string.split(path[i:], splitter))
try:
return plst[1]
except IndexError:
return None
def get_formatted_current_date(self, REQUEST):
"""Return date formatted depending on user's language."""
return time.strftime(translate(self,'short_date_format',default="%Y-%m-%d"), time.localtime())
def get_printable_starting_date(self, REQUEST):
""" return current time, no starting time for coursemanager """
return self.get_formatted_current_date(REQUEST)
def get_printable_ending_date(self, REQUEST):
""" return current time, no ending time fot coursemanaget.
this is here for course adding page """
return self.get_formatted_current_date(REQUEST)
# security.declareProtected(perm_view, 'user_course_export')
security.declarePublic('user_course_export')
def user_course_export(self,REQUEST):
""" user exports its course for viewing offline """
from ExportIMS import Kirjutus
from ExportIMS import kirjutaYldM
import tempfile, os
uname = REQUEST.AUTHENTICATED_USER.getUserName()
knr=self.fle_root().jooksva_kursuse_nr(self, REQUEST)
# knr=self.fle_root().jooksva_kursuse_nr(self, REQUEST)
knr = self.get_course_id_from_req(REQUEST)
kasutaja = self.fle_users.get_user_info(uname)
kasutaja = ((kasutaja),)
failinimi = tempfile.mktemp()
kursus = self.get_child(knr)
k = Kirjutus(self.fle_root(), kursus.get_id())
k.looKursuseFail()
k.kirjutaWebtop(kursus.gf, tiitel='RAAMATURIIUL')
k.kirjutavCal(kursus.syndmused, 'kursus','GROUP')
k.kirjutaKasutajaWebtop(kasutaja)
k.kirjutaKasutajaSyndmus(kasutaja)
# k.kirjutaWiki(getattr(kursus,'ivawiki'))
for sisegr in kursus.subgroups.objectValues('Subgroup'):
sisegr_kasutajad = kursus.kasutajad_sisegrupis(sisegr.get_name())
if uname not in sisegr_kasutajad:
continue
k.kirjutaWebtop(sisegr,'SISEGRUPP')
try:
k.kirjutavCal(sisegr.syndmused,sisegr.get_name(),'GROUP')
except:
pass
k.kirjutaKasutajaWiki(kasutaja)
k.exportPajad(failinimi, str(kursus.get_name()+"/"))
k.pakiKursusFaili(failinimi,str(kursus.get_name()))
file = open(failinimi, "rb")
export_data=file.read()
file.close()
os.remove(failinimi)
REQUEST.RESPONSE.setHeader('content-type','application/zip')
REQUEST.RESPONSE.setHeader('Content-disposition','attachment; filename=iva_arhiiv.zip')
return export_data
# XXX: Wikis are imported through that method.
# XXX: and security is gone!
security.declareProtected(perm_manage, 'import_form_handler')
def import_form_handler(
self,
REQUEST,
file,
course_import='',
testsonly='',
wikionly='',
cancel='',
wiki_koht='',
dry_run='',
):
"""Form handler for course importing."""
uname = REQUEST.AUTHENTICATED_USER.getUserName()
from StringIO import StringIO
out = StringIO()
if testsonly:
raise 'IVA Error:', 'Wrong place.'
if wikionly:
raise 'IVA Error:', 'Wrong place.'
if course_import:
from ImportIMS import Importer
from ImportExport import Exporter
import tempfile, os
filename = tempfile.mktemp()
f = open(filename,"w+b")
f.write(file.read())
f.close()
import_data=None
imported = Importer(uname,self.fle_root(), out=out)
imported.do_import_answers = 1
imported.loadZip(filename)
element = None
try:
element = imported.loadFile('imsmanifest.xml')
except KeyError:
pass
if element is None:
return self.restrictedTraverse('defaultRender.html')(page='Invalid ZIP-file', userLocation='Management')
imported.processFile(self,element)
if dry_run:
print >> out, "Dry run selected, transation aborted."
transaction.abort()
kala = "