# -*- 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 Course, which represents one course, which has one or more CourseContexts, which in turn contain knowledge building conversations.""" __version__ = '$Revision$'[11:-2] import time, strptime import string, types import OFS, Globals from Globals import Persistent, Acquisition import AccessControl from AccessControl import ClassSecurityInfo, getSecurityManager import copy import re import Kodutoo import Blog try: from Products.ZWiki.ZWikiPage import ZWikiPage USE_ZWIKI = 1 except: USE_ZWIKI = 0 from Jamming import Jamming from TraversableWrapper import Traversable from common import intersect_bool, make_action, get_roles, get_local_roles, translate from input_checks import strip_all, is_valid_title from CourseContext import CourseContext #from ThinkingTypeSetManager import ThinkingTypeSetManager as TTSM from Thread import Thread from Cruft import Cruft from TempObjectManager import TempObjectManager from input_checks import render, normal_entry_tags_and_link from common import perm_view, perm_edit, perm_manage, perm_add_lo from Products.ZCatalog.CatalogPathAwareness import CatalogAware import Subgroups import Kalender import WordMap from QuizManager import QuizManager from zope.interface import implements from interfaces import ICourse, IStatistics, IUserManager, IFLE from zope.component import adapter, getUtility, queryUtility from zope.app.container.interfaces import IObjectAddedEvent from OFS.interfaces import IObjectWillBeRemovedEvent from Shared.DC.ZRDB.DA import DatabaseError import logging logger = logging.getLogger('IVA') # Each Course object contains (usually) one or more CourseContexts, which # represent different aspects of the course. # A Course contains information and services on one course implementation. class Course( Persistent, Traversable, Cruft, OFS.Folder.Folder, AccessControl.Role.RoleManager, OFS.SimpleItem.Item, Thread, CatalogAware, ): """Course, contained within CourseManager, represents one course.""" meta_type = 'Course' implements(ICourse) security = ClassSecurityInfo() security.declareObjectPublic() # Parameters: # #- parent: should be a CourseManager # #- name: name of the Course # #- tts: list of ThinkingTypeSets (copies are made in manage_afterAdd) # #- teachers: list of users (of class UserInfo?) that are granted #Teacher privileges # #- etc: other textual information def __init__( self, parent, # Whatever you do, dont bind this to self. name, teachers, description='', organisation='', methods='', starting_date='', ending_date='', uniq_id='' ): """Constructor of the course.""" # Overriding all_meta_types is not not beautiful...but, hey!, it works! #self.all_meta_types = ( # {'name': 'ThinkingTypeSet', # 'action': 'get_id'},) # Cache active members. self.active_memb_cache = [] # Remove all HTML tags from parameters name = strip_all(name) description = strip_all(description) methods = strip_all(methods) organisation = strip_all(organisation) Thread.__init__(self, parent) # Takes care of id and title. if teachers != ('',): self.add_teachers(teachers) self.__name = name # name of the course self.__organisation = organisation self.__description = description self.__methods = methods self.__starting_date = starting_date self.__ending_date = ending_date self.__credits = '' self.__courseID = '' self.__quote = '' self.course_category = 0 self.course_locked = 0 self.default_catalog = 'courses_zcatalog' # course should have uniq id since self.id tends to change self.setUniqId(uniq_id) # This is for group folder path listings - show path up to course. self.toplevel = 1 security.declareProtected(perm_edit, 'add_course_form_handler') def add_course_form_handler(self, REQUEST, course_id, my_name, desc, organisation, methods, start_date, end_date, cancel='', # submit buttons add='', # tekst='', logo_upload=None, staatus=0, regStatus=0, cCat = 0, credits='', courseID='', delete_logo='', lock_course=0, ): """ modify course info, override CourseManager method. """ if cancel: return REQUEST.RESPONSE.redirect('course_info.html?course_id=%s' % course_id) elif not add: raise 'IVA Error', 'Inknown button' if course_id != self.get_id() or not course_id: return action=apply( make_action, ['manage_course_info.html'] + [(x, eval(x)) for x in ('my_name', 'desc', 'organisation', 'methods', 'start_date', 'end_date')]) action += '&course_id=' + course_id 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 my_name != self.get_name(): if 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:',default="%Y-%m-%d"),) 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:',default="%Y-%m-%d"),) organisation = organisation.strip() if organisation and not is_valid_title(organisation): errors.append(translate(self,'Organization')) 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) logo = None try: if len(logo_upload.filename)>0: logo=logo_upload.read() else: logo = None except AttributeError: pass self.update( my_name, desc, organisation, methods, starting_date, ending_date, tekst, logo, staatus, regStatus, cCat, credits, courseID, delete_logo, lock_course, ) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/course_info.html') def is_courseContext(self): """ is course context? """ return def kasTeadmuspajasKontekste(self): "jah/ei" return len(self.objectValues('CourseContext'))>0 def kasMeediapajasProjekte(self): "jah/ei" return len(self.jamming.objectValues('JamSession'))>0 def kysiOotajad(self, REQUEST=None): "Kursusele registreerunud, keda pole veel kinnitatud" return getattr(self, 'ootajad', []) def isLocked(self): """ is course locked """ try: return self.course_locked except AttributeError: return False security.declareProtected(perm_edit,'manage_pendingUsers_handler') def manage_pendingUsers_handler(self, REQUEST, addtocourse='',deletefromlist='',pender=''): """Add or remove user from pending list""" #from Kirjakast import Kirjakast temp = getattr(self.fle_root(), 'fle_users') if not pender: return self.restrictedTraverse('message_dialog_error.html')(self, REQUEST, title="Error", message='Please select some users first', action='manage_pendingUsers.html') if type(pender)==types.StringType: pender=(pender,) penders=self.kysiOotajad() new_penders = [] if deletefromlist: for x in pender: puser = getattr(temp, x) for y in penders: if y == x: continue new_penders.append(y) if addtocourse: for x in pender: puser = getattr(temp, x) self.add_student(puser.get_uname()) mess_title = translate(self,'Added to course') mess_body = translate(self,'You have been added to course ').encode('utf-8')+' '+self.get_name() puser.kirjad.saadaKiri(REQUEST,puser.get_uname(),puser.get_uname(),mess_title,mess_body,999999) for x in penders: if x not in pender: new_penders.append(x) self.reindex_object() self.ootajad=new_penders return REQUEST.RESPONSE.redirect('management_index.html') def _ootajastKursuseLiikmeks(self, REQUEST, kasutaja): """Lisamine""" print "_ootajastKursuseLiikmeks" self.add_student(kasutaja) return 1 def lisaOotaja(self, REQUEST, ootaja): """ Kasutaja lisab end ootama """ print "lisan ootaja...", ootaja, self.getRegStatus() if self.getRegStatus()=='2': #automaatregistreerumine self._ootajastKursuseLiikmeks(REQUEST, ootaja) else: m=self.kysiOotajad(REQUEST) if ootaja not in m: m.append(ootaja) elif ootaja in m: m.remove(ootaja) self.ootajad=m self.reindex_object() return 0 def kasOotaja(self, nimi): "Kas jooksev kasutaja on kursusele ootaja" m=self.kysiOotajad() kas=0 for x in m: if nimi==x: kas=1 return kas security.declareProtected(perm_view, 'get_printable_name') # No additional comments. def get_printable_name(self): """Return name of the course.""" return self.__name security.declareProtected(perm_view, 'get_bg_colour_name') def get_bg_colour_name(self): """...""" return 'gr' # security.declareProtected(perm_view, 'get_name') security.declarePublic('get_name') # No additional comments. def get_name(self): """Get course name.""" #return unicode(self.__name, 'utf-8') return self.__name security.declareProtected(perm_view, 'get_organisation') # No additional comments. def get_organisation(self): """Get organisation name.""" return self.__organisation security.declareProtected(perm_view, 'get_description') # No additional comments. def get_description(self): """Get description.""" return self.__description security.declareProtected(perm_view,'render_description') def render_description(self): """Render description.""" return render( self.get_description(), legal_tags=normal_entry_tags_and_link) security.declareProtected(perm_view, 'get_methods') # No additional comments. def get_methods(self): """Get info on methods.""" return self.__methods security.declareProtected(perm_view,'render_methods') def render_methods(self): """Render methods.""" return render( self.get_methods(), legal_tags=normal_entry_tags_and_link) def get_courseID(self): """ get course id """ try: return self.__courseID except AttributeError: return "" def set_courseID(self,courseID): """ set course id(the other one) """ self.__courseID = courseID def get_credits(self): """ get credits info """ try: return self.__credits except AttributeError: return "" def set_credits(self,credits): """ set credits """ self.__credits = credits # security.declareProtected(perm_view, 'get_teachers') # No additional comments. security.declarePublic('get_teachers') def get_teachers(self): """Get teachers.""" principals = self.acl_users.groups.listAssignedPrincipals(self.getId()+'_teachers') return [ x[0] for x in principals] def add_teachers(self, teachers): grname = self.getId()+'_teachers' fle = getUtility(IFLE) fle.acl_users.groups.manage_addPrincipalsToGroup(grname, teachers) def get_students(self): """Get students.""" principals = self.acl_users.groups.listAssignedPrincipals(self.getId()) return [ x[0] for x in principals] security.declareProtected(perm_view, 'get_start_dd') # No additional comments. def get_start_dd(self): """Return starting day.""" if self.__starting_date: return time.localtime(self.__starting_date)[2] else: return '' security.declareProtected(perm_view, 'get_start_mm') # No additional comments. def get_start_mm(self): """Return starting month.""" if self.__starting_date: return time.localtime(self.__starting_date)[1] else: return '' security.declareProtected(perm_view, 'get_start_yyyy') # No additional comments. def get_start_yyyy(self): """Return starting year.""" if self.__starting_date: return time.localtime(self.__starting_date)[0] else: return '' security.declareProtected(perm_view, 'get_end_dd') # No additional comments. def get_end_dd(self): """Return ending day.""" if self.__ending_date: return time.localtime(self.__ending_date)[2] else: return '' security.declareProtected(perm_view, 'get_end_mm') # No additional comments. def get_end_mm(self): """Return ending month.""" if self.__ending_date: return time.localtime(self.__ending_date)[1] else: return '' security.declareProtected(perm_view, 'get_end_yyyy') # No additional comments. def get_end_yyyy(self): """Return ending year.""" if self.__ending_date: return time.localtime(self.__ending_date)[0] else: return '' # security.declareProtected(perm_view, 'get_printable_starting_date') security.declarePublic('get_printable_starting_date') #FIX:security check # No additional comments. def get_printable_starting_date(self, REQUEST): """Get starting date.""" return time.strftime(translate(self,'short_date_format',default="%Y-%m-%d"), time.localtime(self.__starting_date)) # security.declareProtected(perm_view, 'get_printable_ending_date') security.declarePublic('get_printable_ending_date') #FIX:security check # No additional comments. def get_printable_ending_date(self, REQUEST): """Get ending date.""" if self.__ending_date == 0: return '' return time.strftime(translate(self,'short_date_format',default="%Y-%m-%d"), time.localtime(self.__ending_date)) security.declarePrivate('get_start_date') def get_start_date(self): return self.__starting_date security.declarePrivate('get_end_date') def get_end_date(self): return self.__ending_date security.declarePublic('get_n_users') def get_n_users(self): return len(self.get_all_users_id()) security.declareProtected(perm_view, 'get_all_users') def get_all_users(self): """Return a list of all users on course.""" rv = [] for uname in self.get_all_users_id(): try: rv.append(self.fle_users.get_user_info(uname)) except Exception: pass return rv security.declareProtected(perm_edit, 'kasutajateKodutoodeArhiiv') def kasutajateKodutoodeArhiiv(self, REQUEST): """ Arhiiv, kuhu paigutatakse kasutaja esitatud materjalid """ import tempfile import zipfile failinimi=tempfile.mktemp() arhiiv=zipfile.ZipFile(failinimi,"w",zipfile.ZIP_DEFLATED) kasutajad=self.get_all_users() for x in kasutajad: try: kc=getattr(x.webtop, 'c'+self.id) except AttributeError: continue for too in self.kodutood.objectValues(): if too.tyyp==1: #materjal if hasattr(kc, 'portfolio'): if hasattr(kc.portfolio, too.id): tookataloog=getattr(kc.portfolio, too.id) tookataloog.looArhiiv(REQUEST, arhiiv, x.id+'/') if too.tyyp==2: #grupp mitu=0 punkte=-1 for kg in x.kysiKursuseSisegrupid(self): if kg.id in too.grupid: #XXX:TODO: check this, might be incorrect esitluskaust = getattr(kg.portfolio, too.id) esitluskaust.looArhiiv(REQUEST, arhiiv, kg.id+'/') arhiiv.close() file = open(failinimi,"rb") export_data=file.read() file.close() import os os.remove(failinimi) REQUEST.RESPONSE.setHeader('Content-type','application/zip') REQUEST.RESPONSE.setHeader('Content-disposition','attachment; filename=tooarhiiv.zip') return export_data def fotogaKasutajad(self): """return list of users with photo""" users = self.get_all_users_id() um = getUtility(IUserManager) #for u in users: q = {'userid': users, 'has_photo':1} res = um.userinfo_zcatalog(q) return res def fototaKasutajad(self): """return a list of users without a photo uploaded""" users = self.get_all_users_id() um = getUtility(IUserManager) q = {'userid': users, 'has_photo': 0} res = um.userinfo_zcatalog(q) return res security.declareProtected(perm_view, 'get_all_users_id') def get_all_users_id(self): """Return a list of all users on this course. Same thing as get_all_users, but this one returns a list of id's of UserInfo objects, not the UserInfo object reference.""" return self.get_teachers()+self.get_students() def get_sorted_student_list(self): nimed = [] rolliga = self.get_students() for aaa in rolliga: nimed.append([self.lastAndFirst(aaa), aaa]) nimed.sort() return [ x[1] for x in nimed ] security.declareProtected(perm_edit, 'add_student') #The person is added with Student role access. # NOTE: This method is no longer needed, except in the test cases! def add_student(self, name): """Add person to the course.""" # Check that user exists... Raises exception if not. #XXX: is this really needed!? now commented out print "adding student..." self.acl_users.groups.manage_addPrincipalsToGroup(str(self.getId()), (name,)) security.declareProtected(perm_manage, 'remove_person') def remove_person(self, person): """Remove person from the course.""" kasutaja = getUtility(IUserManager).get_user_info(person) kasutaja.jooksev_kursus = 0 self.acl_users.groups.manage_removePrincipalsFromGroup(self.getId(), (person,)) security.declareProtected(perm_view, 'has_role') # No additional comments. def has_role(self, person, role): """Return whether the user is in the specified role.""" return role in get_roles(self,person) security.declareProtected(perm_edit, 'add_users_form_handler') def add_users_form_handler(self, REQUEST, users_None=[], # checkboxes users_Teacher=[], users_Student=[], None_to_Teacher='', # submit buttons Teacher_to_None='', None_to_Student='', Student_to_None=''): """Adding and removing users from a given course.""" temp = getattr(self.fle_root(), 'fle_users') grname = str(self.getId()) if None_to_Student: # add students to course if not self.isCourseReady(self): return self.restrictedTraverse('message_dialog_error.html')( title="Error", message="Students can be added to the course when all required course information is submitted and course status is open(not preparing, not finished). Adding teachers is OK.", action="manage_course_info.html") self.acl_users.groups.manage_addPrincipalsToGroup(grname, users_None) for user in users_None: try: koht = getattr(temp, user) koht2 = getattr(koht, 'kirjad') uinf = getUtility(IUserManager).get_user_info(user) tlang = uinf.get_language() teade = translate(self,'You have been added to course ',target=tlang).encode('utf-8') koht2.saadaKiri(REQUEST, uinf.get_uname(), uinf.get_uname(), teade,'\n\n\n'+\ teade+": "+\ str(self.get_name()),9999) except AttributeError: pass except KeyError: pass elif Student_to_None: # remote students from course self.acl_users.groups.manage_removePrincipalsFromGroup(grname, users_Student) elif None_to_Teacher: # give users teacher role grname += '_teachers' self.acl_users.groups.manage_addPrincipalsToGroup(grname, users_None) elif Teacher_to_None: # remove teacher role from users grname += '_teachers' self.acl_users.groups.manage_removePrincipalsFromGroup(grname, users_Teacher) else: return self.restrictedTraverse('message_dialog_error.html')( title='Error', message='Please select some users first', action='manage_participants.html') self.reindex_object() return REQUEST.RESPONSE.redirect('manage_participants.html') # FIXME: input_checks: tt_set_name not checked # FIXME: input_checks: two course contexts can have identical name. security.declareProtected(perm_add_lo, 'add_course_context') # Handler for add_course_context_form def add_course_context( self, my_name, description, tt_set_name, description_long, REQUEST, use_roleplay='', # New in ZPTIVA show_only_roleplay=0, # Submit buttons. publish='', cancel='', ): """Add CourseContext object.""" if publish: error_fields = [] errors = [] my_name = my_name.strip() if not is_valid_title(my_name): error_fields.append(translate(self,'title of context')) if my_name in [x[1] for x in self.get_course_context_names()]: errors.append(translate(self,"Name '%s' taken") % my_name) # Variables 'description' and 'description_long' are not checked # because render_description() and render_long_description() # methods in CourseContext filter out unwanted HTML tags. if len(error_fields) > 0 or len(errors) > 0: msg = ", ".join(errors) if len(error_fields) > 0: msg = msg + "
" + 'Invalid fields' + \ ": '" + "' , '".join(error_fields) + "'" return self.restrictedTraverse('message_dialog_error.html')( self, REQUEST, title='Invalid input', message=msg, action=apply( make_action, ['add_course_context_form'] + [(x, eval(x)) for x in ('my_name', 'description', 'tt_set_name', 'description_long')])) uname=str(REQUEST.AUTHENTICATED_USER) obj = CourseContext( self, my_name, description, description_long, tt_set_name, uname,) obj.public = 1 if int(show_only_roleplay)==1: #obj.__roleplay_only = 1 obj.set_roleplay_only(1) id = obj.get_id() self._setObject(id, obj) #XXX: statistics - lisaSyndmus(konteksteLisatud. should it be somewhere? #self.lisaSyndmus(REQUEST, 'konteksteLisatud') try: obj.changeOwnership(self.acl_users.getUser(uname).__of__(self.acl_users)) obj.manage_setLocalRoles(uname,('Owner',)) except: pass if REQUEST: pagename="" if use_roleplay: pagename="course_setup_roleplay_form.html" REQUEST.RESPONSE.redirect('%s/%s' % (str(id),pagename)) elif cancel: return REQUEST.RESPONSE.redirect(REQUEST.URL1) else: raise "add_course_context called without 'publish' or 'cancel'" security.declareProtected(perm_view, 'get_course_context_names') # No additional comments. def get_course_context_names(self): """Return a list of CourseContext names.""" retval = {} for e in self.get_children('CourseContext'): id = e.get_id() name = e.get_name() retval[id] = name return retval.items() #security.declareProtected(perm_view, 'get_n_notes') def get_n_notes(self): """Returns a sum of all notes in all contexts.""" count = 0 for cc in self.get_course_contexts(): count += cc.get_n_notes() return count security.declareProtected(perm_view, 'get_n_unread_notes') def get_n_unread_notes(self,uname): """Returns true if course as unread notes in KB.""" # TODO: figure out how many messages are unread uname = str(getSecurityManager().getUser()) st = getUtility(IStatistics) lv = st.getUserCourseStat(self.getId(), 'visit_knowledgebuilding', uname) lu = st.getCourseStat(self.getId(), 'modified_knowledgebuilding') if lu>lv: return True # count = 0 # for cc in self.get_course_contexts(): # count += cc.get_n_unread_notes(uname) # return count return False security.declarePublic('update') # Parameters are received from the form (apparently). def update( self, name, description, organisation, methods, starting_date, ending_date, tekst, logo=None, status=0, regStatus = 0, cCat = 0, credits='', courseID='', delete_logo = '', lock_course=0, ): """Edit course information.""" self.__name = name self.__description = description self.__organisation = organisation self.__methods = methods self.__starting_date = starting_date self.__ending_date = ending_date self.setQuote(tekst) if logo: self.setLogo(logo) self.setStatus(status) self.setRegStatus(regStatus) if cCat: self.setCourseCategory(int(cCat)) self.set_credits(credits) self.set_courseID(courseID) self.course_locked = lock_course if delete_logo: self.removeLogo() #XXX: remove self.default_catalog = 'courses_zcatalog' self.reindex_object() security.declareProtected(perm_view, 'get_course_contexts') def get_course_contexts(self): """Return a list of all course contexts in this course.""" for abc in self.get_children('CourseContext'): if getattr(abc, 'public', None) is None: abc.public = 1 return self.get_children('CourseContext') security.declareProtected(perm_view, 'get_course_context_ids_in_order') def get_course_context_ids_in_order(self, id_list): """Return a list of ids of all course contexts in this course.""" return [o.get_id() for o in self.get_course_contexts_in_order(id_list)] security.declareProtected(perm_edit, 'delete_course_context') def context_form_handler(self,REQUEST,cc_id,staatus='', kustuta=''): """ delete or change status """ self.hide_show_course_context(cc_id) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/kb_index.html') security.declareProtected(perm_edit, 'delete_course_context') def delete_course_context(self,id): for c in self.get_children('CourseContext'): if c.get_id() == id: self._delObject(id) return security.declareProtected(perm_edit, 'hide_show_course_context') def hide_show_course_context(self,id): for c in self.get_children('CourseContext'): if c.get_id() == id: if c.public == 1: c.public = 0 else: c.public = 1 c._p_changed = 1 break return # FIXME: See the random comment below, random is not # FIXME: probably the order that we really want. security.declareProtected(perm_view, 'get_course_contexts_in_order') def get_course_contexts_in_order(self, id_list): """Return a list of all course contexts in this course.""" contexts = {} for c in self.get_children('CourseContext'): contexts[c.get_id()] = c retval = [] if id_list and id_list != ['']: if type(id_list) == types.StringType: id_list = (id_list, ) # Return courses contexts in a given order. for identifier in id_list: try: retval.append(contexts[identifier]) del contexts[identifier] except KeyError: # invalid id_list pass # If we still course contexts left (id_list is shorter # than the actual number of course_contexts), append # them to list in a random order. for key in contexts.keys(): retval.append(contexts[key]) return retval security.declarePublic('may_view_course') def may_view_course(self, REQUEST): """Return boolean depending on wether user may or may not view the course.""" from AccessControl.PermissionRole import rolesForPermissionOn return intersect_bool( get_roles(self,str(REQUEST.AUTHENTICATED_USER)), rolesForPermissionOn(perm_view,self)) security.declareProtected(perm_view, 'may_add_course_context') def may_add_course_context(self, person): """Return boolean depending on wether user may or may not add a course context to the course.""" from AccessControl.PermissionRole import rolesForPermissionOn return intersect_bool( get_roles(self,person), rolesForPermissionOn(perm_add_lo,self)) security.declareProtected(perm_view, 'may_edit_course') def may_edit_course(self, person): """Return boolean depending on whether person can edit the course or not.""" from AccessControl.PermissionRole import rolesForPermissionOn return intersect_bool( get_roles(self,person), rolesForPermissionOn(perm_edit,self)) security.declareProtected(perm_view, 'has_group_folder') def has_group_folder(self): """Return whether course has a group folder or not.""" return len(self.objectIds('GroupFolder'))>0 def add_folder(self, my_name): from GroupFolder import GroupFolder new_id = 'gf' fol = GroupFolder(None,my_name) fol.id=new_id self._setObject(new_id,fol) fol=fol.__of__(self) security.declareProtected(perm_edit, 'teacher_import_handler') def teacher_import_handler(self, REQUEST, file='', to_import=''): """ Form handler for teacher """ kursusenr = self.get_course_id_from_req(REQUEST) if not self.kas_opetaja(REQUEST): return "Keelatud" uname = REQUEST.AUTHENTICATED_USER.getUserName() from ImportIMS import Importer from ImportExport import Exporter from StringIO import StringIO out = StringIO() 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(),'',getattr(self.fle_root().courses, kursusenr),kasuta_jooksvat=1, out=out) imported.loadZip(filename) element = imported.loadFile('imsmanifest.xml') imported.processFile(self,element) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/'+str(kursusenr)+'/course_info.html') security.declareProtected(perm_edit, 'teacher_export') def teacher_export(self,REQUEST): """ teacher export """ return self.iva_ims_export(REQUEST) security.declareProtected(perm_edit, 'iva_ims_export') def iva_ims_export(self, REQUEST=None, testinimi='',teacherExporting=1): """ Course or a quiz export. XML format. """ from ExportIMS import Kirjutus from ExportIMS import kirjutaYldM import tempfile, os failinimi = tempfile.mktemp() # self.fle_users.export_users(REQUEST,failinimi='ivaexport.zip',kursusega='jah',palju='self.fle_root().fle_users.get_users()') if testinimi: yld = kirjutaYldM() yld.kirjutaYldmanifest(self.get_id(),'QTI test','imsqti_xmlv1p1','','qti.xml') yld.lisaFaili(failinimi) k= Kirjutus(self.fle_root(), self.get_id()) #k.looKursuseFail() #k.exportQTI(failinimi,'') parent = k.looTestiFail() k.kirjutaTestid(parent,testinimi, users='None', teacherExporting=teacherExporting) k.kirjutaTestidZipi(failinimi,'') #k.pakiKursusFaili(failinimi) # k.pakiTulemusFaili(failinimi,testsonly="testsonly") else: yld = kirjutaYldM() yld.kirjutaYldmanifest(self.get_id(), self.get_description(),base=str(self.get_id())+"/") yld.lisaFaili(failinimi) k = Kirjutus(self.fle_root(), self.get_id()) k.looKursuseFail() k.kirjutavCal(self.syndmused,'kursus','GROUP') k.kirjutaWebtop(self.gf,tiitel='RAAMATURIIUL') for sisegr in self.subgroups.objectValues('GroupFolder'): k.kirjutaWebtop(sisegr,'SISEGRUPP:'+sisegr.get_name()) try: k.kirjutavCal(sisegr.syndmused,sisegr.get_name(),'GROUP') except: pass if not teacherExporting: k.kirjutaKasutajaWebtop(self.get_all_users()) k.kirjutaKasutajaBlog(self.get_all_users()) #k.kirjutaKasutajaSyndmus(self.get_all_users()) self.fle_users.export_users(REQUEST,failinimi=failinimi,kursusega=self.get_id(),palju='kursus.get_all_users()') k.kirjutaItem(k.organization, tiitel='KURSUSE KASUTAJAD',parameters='KURSUSE KASUTAJAD') k.kirjutaResource(k.resources, 'imsent_xmlv1p1','users.xml') k.kirjutaBlog(self.Blog, k.organization, k.resources) k.exportQTI(failinimi,str(self.get_id())+"/",teacherExporting=teacherExporting) k.exportPajad(failinimi,str(self.get_id())+"/",startonly=teacherExporting) k.exportKodutood(self.kodutood,failinimi,str(self.get_id())+"/") k.pakiKursusFaili(failinimi) if REQUEST: file = open(failinimi,"rb") export_data=file.read() file.close() os.remove(failinimi) REQUEST.RESPONSE.setHeader('Content-disposition','attachment; filename=ivaexport.zip') REQUEST.RESPONSE.setHeader('content-type','application/zip') return export_data else: import os import shutil name='iva_'+re.sub('[^a-zA-Z0-9]', '_', self.get_name()) name2 = name number = 1 searched=0 #print os.path.isfile(os.path.join(Globals.INSTANCE_HOME,'var',name+'.zip')) #print os.path.join(Globals.INSTANCE_HOME,'var',name+'.zip') while os.path.isfile(os.path.join(Globals.INSTANCE_HOME,'var',name+'.zip')): number = number + 1 name = name2+'_ver'+str(number) searched=1 name += '.zip' shutil.move(failinimi,os.path.join(Globals.INSTANCE_HOME,'var',name)) return self.get_name() + ' - ' + os.path.join(Globals.INSTANCE_HOME,'var',name) def write_touchgraph_data(self,obj,type,children_types=None,extra_links=None): links = '' if children_types: links = ' '.join(obj.objectIds(children_types)) if extra_links: links = ' '.join((links,extra_links)) if not links: links=' ' return obj.get_id()+'\t'+\ type+"\t"+\ self.REQUEST.BASE0+self.get_url_to_object(obj)+'\t'+\ obj.get_name()+'\t'\ +links+'\t' # But should we protect it somehow? # The Java client would then need authentication def touchgraph_data(self): """This is a public method.""" data=self.write_touchgraph_data(self,"COURSE","CourseContext") for ctx in self.objectValues("CourseContext"): data=data+ctx.touchgraph_data() return data+"[END DATA]" def getQuote(self): "Kursuse uudis/moto. Vastab tyhja kui pole" return self.__quote def setQuote(self, tekst): """ set quote """ self.__quote=tekst security.declareProtected(perm_edit, 'removeLogo') def removeLogo(self): """ remove logo """ imgp = getattr(self.fle_root().images, 'courses_imgs', None) try: imgp._delObject('course_'+str(self.get_id())+'_image') except AttributeError: pass security.declareProtected(perm_edit, 'setLogo') def setLogo(self, img): """ add course logo """ resize = 1 try: from PIL import Image except ImportError: resize = 0 import cStringIO imgp = getattr(self.fle_root().images, 'courses_imgs', None) if imgp is None: return 0 try: s = cStringIO.StringIO(img) if resize: im = Image.open(s) (width, height) = im.size if height == 80 and im.format == 'JPEG': pass else: if im.mode != 'RGB': im = im.convert('RGB') if height > 300: mod = float(300)/float(height) width = width*mod height = height*mod im = im.resize((int(width),int(height))) if width > 300: mod = float(300)/float(width) width = width*mod height = height*mod im = im.resize((int(width),int(height))) s = cStringIO.StringIO() try: im.save(s, "JPEG", quality=100) except KeyError: im.save(s, "GIF", quality=100) s.seek(0) img = s.read() try: imgp._delObject('course_'+str(self.get_id())+'_image') except AttributeError: pass imgp.manage_addImage('course_'+str(self.get_id())+'_image', img, 'image') img = getattr(imgp, 'course_'+str(self.get_id())+'_image') img.ZCacheable_setManagerId('HTTPCache') img.ZCacheable_invalidate() except: return 0 self.setLogoThumbnail() return 1 security.declarePrivate('setLogoThumbnail') def setLogoThumbnail(self): """ creates thumbnail from uploaded course logo """ resize = 1 try: from PIL import Image except ImportError: resize = 0 if not resize: return 1 import cStringIO imgp = getattr(self.fle_root().images, 'courses_imgs', None) if imgp is None: return 1 oimg = getattr(imgp, 'course_'+str(self.get_id())+'_image', None) try: s = cStringIO.StringIO(oimg.data) except TypeError: s = cStringIO.StringIO(str(oimg.data)) if s is None: return 1 im = Image.open(s) (width, height) = im.size # resize image 59x59 if height > 59: mod = float(59)/float(height) width = width*mod height = height*mod im = im.resize((int(width), int(height))) if width > 59: mod = float(59)/float(width) width = width*mod height = height*mod im = im.resize((int(width), int(height))) s = cStringIO.StringIO() try: im.save(s, "JPEG", quality=100) except KeyError: im.save(s, "GIF", quality=100) s.seek(0) img = s.read() try: imgp._delObject('course_'+str(self.get_id())+'_image_thumbnail') except AttributeError: pass imgp.manage_addImage('course_'+str(self.get_id())+'_image_thumbnail', img, 'image') img = getattr(imgp, 'course_'+str(self.get_id())+'_image_thumbnail') img.ZCacheable_setManagerId('HTTPCache') img.ZCacheable_invalidate() def hasLogo(self): """ if course has its own logo """ # XXX:weird hack if not getattr(self.images.courses_imgs, 'course_'+str(self.get_id())+'_image', None): return 0 return 1 def hasLogoThumbnail(self): """ if course logo has thumbnail """ if not self.hasLogo(): return 0 if not getattr(self.images.courses_imgs, 'course_'+str(self.get_id())+'_image_thumbnail', None): self.setLogoThumbnail() return 0 return 1 def getLogoTag(self, REQUEST=None): """ ' def getLogoURL(self): """ URL to course logo """ if self.hasLogo(): return getattr(self.images.courses_imgs, 'course_'+str(self.get_id())+'_image', None).absolute_url(1) return '++resource++images/course_default.gif' def getLogoThumbnailURL(self): """ URL to course logo thumbnail """ if self.hasLogoThumbnail(): return self.fle_url()+"/images/courses_imgs/course_%s_image_thumbnail" % str(self.get_id()) #return getattr(self.images.courses_imgs, 'course_'+str(self.get_id())+'_image_thumbnail', None).absolute_url(1) return '++resource++images/course_default.gif' def setStatus(self, sttus): """ set course statuse """ self.staatus=sttus def setRegStatus(self, regStatus): """ set registration status """ self.regStatus = regStatus def getStatus(self): """ get course status """ if getattr(self, 'staatus', None) is None: return 0 else: return self.staatus def getRegStatus(self): """ return how students get to course """ return getattr(self, 'regStatus', 0) security.declareProtected(perm_view,'getCourseCategory') def getCourseCategory(self): return self.course_category security.declarePrivate('setcourseCategory') def setCourseCategory(self, id): self.course_category = id self._p_changed = 1 def get_n_artefacts(self): """ number of artefacts on a course """ total = 0 for x in self.jamming.objectValues('JamSession'): total += x.get_n_artefacts() return total def total_n_unseen_jams(self,REQUEST): """ returns True if Jamming has been updated after user's last visit """ uname = str(getSecurityManager().getUser()) st = getUtility(IStatistics) lv = st.getUserCourseStat(self.getId(), 'visit_jamming', uname) lu = st.getCourseStat(self.getId(), 'modified_jamming') if lu>lv: return True #total = 0 #for x in self.jamming.objectValues('JamSession'): # total += x.get_n_unread_artefacts(REQUEST) #return total return False def isCourseAlive(self): """ when was last input, new objects, artefacts? """ #XXX: isCourseAlive ZZZ, this is serious and need a rewrite! curtime = time.time() aeg = curtime-86400*30 #XXX: go away! if self.lastModify() time: time = tmp return float(time) def lastModify_printable(self,REQUEST): """ returns printable last modification date """ return time.strftime(translate(self,'short_date_format',default="%Y-%m-%d"),time.localtime(self.lastModify())) def lastActive(self): """ return last visit to course """ last = 0 for x in self.get_all_users(): tmp = getUtility(IStatistics).getUserCourseStat(int(self.get_id()), 'lastAccess', x.get_uname()) if tmp > last: last = tmp return last def lastActive_printable(self,REQUEST): """ return last visit to course """ return time.strftime(translate(self,'short_date_format',default="%Y-%m-%d"),time.localtime(self.lastActive())) security.declareProtected(perm_manage, 'getUniqId') def getUniqId(self): """ return uniq id. we take it with us when exporting and put it back when importing should be random enough to avoid collision """ return self.__uniq_id security.declarePrivate('setUniqId') def setUniqId(self, uniq_id): """ set course uniq id. don't change it. method is here only for beauty """ if not uniq_id: raise 'IVA error:', 'uniq id not passed' self.__uniq_id = uniq_id return 1 security.declareProtected(perm_view, 'hasWordmaps') def hasWordmaps(self): """ if course has wordmaps """ return getattr(self, 'wordmaps', None) security.declareProtected(perm_edit, 'createWordmaps') def createWordmaps(self): """ create wordmaps """ import WordMapTools wf=WordMapTools.WordmapFolder() wf.id="wordmaps" self._setObject(wf.id, wf) security.declarePrivate('createTracker') def createTracker(self, REQUEST=None): """ create tracker. Trackers came in IVA 1.0 """ from Tracker import Tracker tracker = Tracker() try: self._setObject(tracker.getId(), tracker) except: self._delObject(tracker.getId()) self._setObject(tracker.getId(), tracker) if REQUEST: return self.restrictedTraverse('message_dialog.html')(message='Tracker created', action='tracker') else: return 0 security.declareProtected(perm_view, 'getSubgroupList') def getSubgroupList(self): """ return subgroups and list of participants """ res = {} for x in self.subgroups.getSubgroups(): res[x.get_id()] = x.subgroup_members() return res security.declareProtected(perm_view, 'getExistingWebtops') def getExistingWebtops(self): """ return a list of usernames who have webtop for this course """ urls = [] fler = '/'.join(self.fle_root().getPhysicalPath()) for x in self.get_all_users_id(): urls.append(fler+'/fle_users/'+x+'/webtop/c'+str(self.getId())) q = {'path': urls, 'meta_type': 'Portfolio'} res = self.catalog_webtop_items(q) # get_author can be someone else. it is safer to use url to get username return [ r.getRelativeContentURL.split('/')[1] for r in res ] Globals.default__class_init__(Course) @adapter(ICourse, IObjectWillBeRemovedEvent) def removing(obj, event): obj.unindex_object() stat = queryUtility(IStatistics) if stat is not None: stat._delCourseHistory(int(obj.get_id())) @adapter(ICourse, IObjectAddedEvent) def added(obj, event): #Each course should have its own copy of the ThinkingTypeSets, #as the course administrator (teacher) should be able to edit them, #create new ones and so forth. These changes must not propagate to #other courses: hence the copying of the set. # give teacher and student roles to groups obj.manage_setLocalRoles(obj.id, ('Student',)) obj.manage_setLocalRoles(obj.id+'_teachers', ('Teacher',)) # add subgroups manager sg = Subgroups.SubgroupManager() obj._setObject(sg.id, sg) # add quizes qm = QuizManager() obj._setObject(qm.id, qm) # add jamming obj._setObject('jamming', Jamming('jamming')) # add wordmaps obj.createWordmaps() # add assignments k=Kodutoo.KodutoodeKataloog() k.id='kodutood' obj._setObject(k.id, k) obj.index_object() # add blog b = Blog.Blog() b.id = "Blog" obj._setObject(b.id,b) # add calendar cal = Kalender.KalendriSyndmusteKataloog() obj._setObject('syndmused', cal) # add tracker obj.createTracker() # add SQL row from Statistics import DummySQLMethods sta = getUtility(IFLE) if not isinstance(sta.iva_sql_methods, DummySQLMethods): try: sta.iva_sql_methods._sql_populate_course_table(course_list='('+str(obj.getId())+')') except DatabaseError: logger.error('MySQL database connection is broken!') # EOF