Skip to main content
Rollback to Revision 3
Source Link
Simon Forsberg
  • 59.4k
  • 9
  • 156
  • 309

Below is a script to find cheating students in a quiz in a daily moodle activity log exported in .xls. It works fine and is written in a procedural way.

Essentially the script isolates the activity log entry in a worksheet for students listed in another worksheet, identifies when they are taking a quiz (looking ahead in the activity log) and if they are taking an unusually long time or doing something different while taking a quiz (they will complete). These different scenarios are then highlighted in a different row colour in an xl file listing all activity for each student in the list. The script also writes to a .txt file, logging a summary for each student and all quizzes completed and the students listed who did not take any quizzes.

The questions are simple:

  1. How can I make it run faster?
  2. How can I write it in a OOP paradigm, and will it run faster?
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from# __future__The importmaster print_function
importdata sys
importset os
importis re
fromcontained xlrdin importexcel open_workbook
importfile xlwt
from('moodle.xls'). xlwtThe importtoponomy easyxf
fromfor operatorthe importactivity itemgetter,is attrgetter
fromthe timeone importin gmtime,a strftime2.5+ moodle instance. The colums in excel follow the format
from# datetime importcolumns datetime= module name/date,time/(Not timedeltaused):e.g. IP address/name student/activity type (full)/activity name
from# xlutils.copy importrows copy= chronological order in time period


if# sysThe class (moodlecheat) returns: a log containing a summary of cheats per students, a set of individual xl files with a log per student with quiz question highlighted.version
# >Each '3'row is colour coded to facilitate detection: yellow/any valid quiz question answered | light yellow/any question which is answered in more than 2 minutes | grey/any non quiz activity during a quiz | Red/any activity taking longer than 5 minutes during a quiz 
# The data listed in unicodea =log str(log.tx) which details the running times per module and the activity for all students highlighting any suspected cheating

# Worksheet 1 = xl report for moodle activity log for the time period e.g. a day 
# Worksheet 2 = Name of users/students whose quiz data is to be mined out

class MoodleCheat(object):
 
    def __init__(self, log_filepath, source_filepath):
        self.source_filepath = source_filepath
        self.log_filepath = log_filepath
        self.students_entries = []
        self.xl_out = []
  # The typical running time for self.valid_quizzesthe =class []
on a 8GB Mac with dual processor OSX self10.done_quizzes = []
        self9.list_of_quizzes = []
  3 is 31 seconds to find self.inlisted_studentsthe =quiz []
activity of 120 students for 3 quizzes amongst self.listed_students12k =students []records

        self.stylo = {
                'normal': easyxf('pattern: pattern no_fill;'
                    + 'font: name Arial,'
                   from +mmap 'heightimport 260mmap, italic True'
                                         ),ACCESS_READ
                'answer':from easyxf('pattern:xlrd patternimport solid_fillopen_workbook,'
                    + 'fore_colour yellow;'
                    + 'font: name ArialXL_CELL_TEXT,'
                    + 'height 260'
                                         )cellname,
                'problem': easyxf('pattern: pattern solid_fillcellnameabs,'
                    + 'fore_colour gray50;'
                    + 'font: name Arial,'colname
                    + 'heightimport 260'xlwt
                                       from xlwt import )Workbook,
                'too long': easyxf('pattern: pattern solid_fill, '
                   #from +xlutils.styles 'fore_colourimport light_yellow;'Styles
                    +from 'font:operator nameimport Arial,'itemgetter
                   from +time 'heightimport 260gmtime,'
                    + 'bold True'
                                           )strftime,
                'way too long': easyxf('pattern: pattern solid_fillstrptime, '
                    + 'fore_colour light_orange;'clock
                    +from 'font:datetime nameimport Arialdatetime,'
                    + 'height 260'timedelta
                                        from xlutils.copy import )}copy

    defclass moodledateconvertmoodlecheat(self, date_inobject):
        """ function to return a moodle date in date.time/python convention """
        split_date = ' '.join(re.findall(r"[\w']+"mois={'january':'jan', date_in))
        return datetime.strptime(split_date'February':'Feb', '%d %B %Y %I %M %p')'March':'Mar','April':'Apr','May':'May','June':'Jun','July':'Jul','August':'Aug','September':'Sep','October':'Oct','November':'Nov','December':'Dec'}
    
    def timegap__init__(self, time_latelogfilepath, time_earlysourcefilepath):
        """self.sourcefilepath calculates= sourcefilepath
  between the difference between 2 dates self.logfilepath=logfilepath
        format 
 e       self.gstudentsentries=[]
        self.xlout=[]
 <'01 September 2001, 11:50AM'> -> time/python convention""" self.validquizzes=[]
        returnself.donequizzes=[]
        self.moodledateconvert(time_late)listofquizzes=[]
 -       self.moodledateconvert(time_early)inactivestudents=[]
        self.activestudents=[]

    def openworkbook(   self.stylo =  {1:easyxf('pattern: pattern no_fill;' 'font: name Arial, height 260, italic True'),
                2:easyxf('pattern: pattern solid_fill, fore_colour yellow;' 'font: name Arial, height 260'),
        """ open the workbook containing the activity entries. """3:easyxf('pattern: pattern solid_fill, fore_colour gray50;' 'font: name Arial, height 260'),
        self.work_book = open_workbook      4:easyxf(os.path.join'pattern: pattern solid_fill, fore_colour light_yellow;' 'font: name Arial, height 260, bold True'),
                5:easyxf(self.source_filepath'pattern: +pattern 'activitylog.xlsx'solid_fill, fore_colour light_orange;' 'font: name Arial, height 260')}
# ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#                             0                           1                            2                                                             3                                  4                                                   5                                   
# studentsentries    module name       date,time               IP address                                              name student               activity type (full)                          activity name
# xlout                     name student,      time                       activity name                                on_demand=True          activity type (short)      time since previous question        xl file colour row
# valid quizzes        name student       activity name         number of question answered in quiz.
# done quizzes       name student        activity name
# done quizzes       name student        activity name
# list of quizzes      activity name 
# inactivestudents  name student
# active students    name student        flag student taking quiz
#                             0                           1                                                                                                                                            
# /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            
    # ------------------------------- Functions -------------------------
    # function returning True when a student is taking a quiz
    def takingquiz(self,item,series):
        position = [(i, name.index(item)) for i, name in enumerate(series) if item in name]
        return (series[position[0][0]][(position[0][1])+1])

    # function to return a moodle date in date.time/python convention <y=01 m=11 d=1 sec=50 msec=00>  <- unicode strings/moodle moodle convention <01 September 2001, 11:50AM>
    def extractstudentslistudconv(self,datein):
        """d=datein.split(',')[0]
 extract the list of all students from thed1=datein.split(',')[1]
 2nd worksheet      d2=d+d1
        d3 = moodlecheat.mois[d2.split(' ')[1]]
        d4=d2.split(' ')[0]+' '+d3+' '+d2.split(' ')[2]+' '+d2.split(' ')[3]+d2.split(' ')[4]
        d5 = datetime.strptime(d4, '%d %b %Y  %I:%M%p')
        return d5
    
    # function to calculate between 2 dates in unicode string/moodle log format e.g. <'01 September 2001, 11:50AM'> -> time/python convention 
    def timegap(self,i,j):
            self.gap = self.udconv(i) - self.udconv(j)
            return self.gap

        Keyword arguments:
        listed_students -- list of students, a flag showing taking a quiz
        """
        self.sheet = self.work_book.sheet_by_index(1)
        self.listed_students = [Student(_student) for _student in self.sheet.col_values(0)]
        self.listed_students.sort(key=attrgetter('_name'))

    def extractstudents_entries(self):
        """ Extract all entries for the students listed in [listed_students].

        Keyword arguments:
        students_entries# -- all entries for the students listed
        row -- row number in the data entries worksheet
        """
       ------------------------ self.sheetMethods =----------------------------------------------- self.work_book.sheet_by_index(0)
        for row indef rangeopenworkbook(self.sheet.nrows):
            for x in self.listed_students:
                if x._name == self.sheet.cell_value(row, 3):
                    self.students_entries.append(Entry(*self.sheet.row_values(row)))
                    break
        self.students_entrieswb = sortedopen_workbook(self.students_entriessourcefilepath+'activitylog.xlsx',
                                                       key=attrgetter('_name'))
        self.students_entries.reverse(on_demand=True)
        
    def listquizzesextractstudentslist(self):
        """#  Extract the list [activestudents] of all quizesstudents thatfrom werethe completed3rd worksheet (list student names) in moodle.xlsx
        self.sh = self.wb.sheet_by_index(1)
        self.activestudents = [[x,False] for x in self.sh.col_values(0)]

        Keyworddef argumentsextractstudentsentries(self):
        valid_quizzes -- list of tuples (student name, quizzes# completed)
Extract activity from the master dataset <studentsentries> for _entryall --the anstudents entrylisted in the<activestudents>
 [students_entries] list
      self.sh = """self.wb.sheet_by_index(0)
        for _entryr in range(self.students_entriessh.nrows):
            if [self.sh.cell_value(r,3),False] in self.activestudents:
              _entry._action[:10] == 'quiz close'self.studentsentries.append(self.sh.row_values(r))
                     
    anddef listquizzes(_entry._name, _entry._infoself):
        # list all quizes that were completed into <validquizzes>  
        notfor x in self.valid_quizzesstudentsentries:
                         if x[4][:10]=='quiz close' and [x[3],x[5]] not in )self.validquizzes:
                self.valid_quizzesvalidquizzes.append((_entry._name[x[3], _entry._info)x[5],-1])

    def fliptakingflip_quiz(self, _searched_studentitem,series):
        """ Changes# themethod attributeto forchange the student classrecord to show name is taking quiz."""
        for _student in self.listed_students:
            if _student._namea ==_searched_student:quiz
                _student._taking_quizposition = not (_student._taking_quiz)
                break
            
    def flipactive[(selfi, _searched_student):
        """ Changes the flag for the student to show is taking quiz."""
        for _student in self.listed_students:
            if _student._name ==_searched_student:
                _studentname._active = True
                break
            
    def istakingindex(self, _searched_studentitem):
        """ Changes the flag) for the student to show has completed a quiz."""
        fori, _studentname in self.listed_students:
            if _student._name ==_searched_student:
                return _student._taking_quiz
        
    def logtimestampenumerate(self, messageseries):
        """ Writes a message with timeif stampitem in sourcefilepath/log.txt."""
        self.message = messagename]
        with open(os.path.joinseries[position[0][0]][(self.log_filepath, 'log.txt'position[0][1]), 'a'+1]) as file:
             file.write('\n == {} at {}'.
                            format(
                             self.message,
                            strftime=not('%Y-%m-%d %H:%M:%S',gmtimeseries[position[0][0]][())
                                         position[0][1])+1])

    def logmessagelogtimestamp(self, message):
        """ Writes# awrites message<message> in log.txt inat sourcefilepath/log.txt."""
        self.message = messagemessage=message
        withflog=open open(os.path.join(self.log_filepath, 'loglogfilepath+'log.txt'), 'a'"a") 
 as file:
      flog.write('\n == '+self.message+ ' at '+strftime("%Y-%m-%d file.write%H:%M:%S", gmtime('\n')))
 +       print self.message)

    def plogmessage(self, namemessage):
        """ for test, returns# thewrites value<message> ofin log.taking_quiz attributetxt at \logfilepath
             for _searched_studentself.
             """message=message
        for x inflog=open (self.listed_students:
            if xlogfilepath+'log._name ==txt', n._name:"a")
          flog.write('\n'+self.message)      print (x._name, x._taking_quiz)
         
    def perstudentquizreport(self):
        """ logs quiz information for# eachwrites studentstart and xl ouput table.

         Keyword arguments:
        xl_out -- list of tuples (student name, quizzes completed)
        _entry -- an entry in the [students_entries] list
   completion times for each students """
        self.logmessage ('\n\nReport on quizzes')
        self.logmessage ('=================\n')
    
        self.old_timeoldtime = str('25 April 2014, 11:41 AM')
        # Main loop
        for _recordm in (sorted(reversed(self.students_entriesstudentsentries), key=itemgetter(3))):
            # log messagesself.sc=self.stylo[1]
 and set flags for each entry when in quiz
   # If student starting a quiz and quiz has self.scnot =been self.stylo['normal']
completed yet => log the entries into [quizzdb], log the quizz name ifinto (([validquizzes], flip the flag to show student is taking a quiz  _record._action.split(' (')[0] == 'quiz continue attempt' \
                if (m[4].split(' (')[0]=='quiz continue attempt' or     _record._actionm[4].split(' (')[0][0]=='quiz ==attempt' 'quiz) attempt'and (self.takingquiz(m[3],self.activestudents)==False) \
and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)) and not (any(p[0]==m[3] and p[1]==m[5] for p in self.donequizzes)):
            and (_record   self._nameflip_quiz(m[3], _recordself._infoactivestudents) 
 not in self.done_quizzes \
            self.logmessage (m[3]+' has made first answer in the quiz and'+m[5]+ self ' at '+m[1].istakingsplit(_record._name', ')[1])
 == False \
              
        and ((_record._name, _record._info) in selfif (m[4].valid_quizzes
split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt' ) and (self.takingquiz(m[3],self.activestudents)==False) and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)) and (any(p[0]==m[3] and p[1]==m[5] for p in self.donequizzes)):
                self.fliptakingflip_quiz(_recordm[3],self._nameactivestudents)
                self.logmessage( '{} has made first answer(m[3]+' inretook the quiz {}'+m[5]+  ' at {}'+m[1].split(', '
 )[1]+' - This is an issue if the quiz is not a practice quiz')
             
            elif m[4][:10]=='quiz close' .formatand (
 any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)): # finished a quiz
                self.donequizzes.append([m[3],m[5]])
              _record  self._nameflip_quiz(m[3],self.activestudents)
                self.logmessage(m[3]+' has finished the quiz '+m[5]+  ' at '+m[1].split(', ')[1])
                _recordself._info,logmessage(('-')*130)
                
            if (self.takingquiz(m[3],self.activestudents)==True):  
                if m[4][:4]!='quiz': # _record._time.split(',')[1]
unusual action during the quiz
                    self.logmessage('!! -- '+unicode(m[4].split('(')[0])+' '+unicode(m[5])+' at '+unicode(m[1].split(', ')[1]))
                    self.sc = ))self.stylo[3]

            if (        (_record._action.split(' (')[0] == 'quiz continue attempt' \
                     or _record._action.split(' (')[0] == 'quiz attempt'
                        )) \
                     and (_record._name, _record._info) in self.done_quizzes \
                     and self.istaking(_record._name) == False \
                     and ((_record._name, _record._info) in self.valid_quizzes
                          ):
                self.fliptaking(_record._name)
                self.logmessage('{} retook the quiz {} at {} -This is an issue' +
                    ' if the quiz is not a practice quiz'
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))
            elif _record._action[:10] == 'quiz close':
                self.done_quizzes.append((_record._name, _record._info))
                self.fliptaking(_record._name)
                self.flipactive(_record._name)
                self.logmessage('{} has finished the quiz {} at {}'
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.splittimegap('m[1],')[1]
                                                    ))
                self.logmessage(('-'oldtime) * 130)
                        
            # unusual actions during a valid quiz
            if self.istaking>timedelta(_record._nameminutes=1) == True:
                if _record._action[:4] != 'quiz':
                    self.logmessage('!! -- {}  {} at {}'
                                            .format(
                                            _record._action.split('('),
                                            _record._info,
                                            _record._time.split(',')[1]
                                                 # More than 1 minute for an ))action
                    self.sc = self.stylo['problem']                   
                elif ((self.timegap(_record._time, selfsc=self.old_time))
                                > timedelta(minutes=1)):stylo[4]
                            self.sc = self.stylo['too long']
                else:
                    self.scsc=self.stylo[2]# =Answering self.stylo['answer']quiz
                    
                if (self.timegap(_record._timem[1], self.old_timeoldtime)) > timedelta>timedelta(minutes=3):
  # More than 3 minutes for an action
                    self.logmessage('!! - Time gap between answers is greater'  +
                                                    'greater than 3 minutes. '+unicode({}) at {}'
                                                    .format(
                                                    self.timegap(_record._timem[1], self.old_timeoldtime),
                                                  ))+' at _record._time'+unicode(m[1].split(', ')[1]
                                                                ))
                        self.sc = self.stylo['way too long']stylo[5]
                
            self.xl_out.append([_record._name,
                            
                if _record._time(m[4].split(', (')[1],
  [0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt'): #log the number of questions answered
                    self.quest=[ l[2] for l in self.validquizzes if l[0]==m[3] and l[1]==m[5] _record._info,]
                        self.validquizzes.remove([m[3],m[5],self.quest[0]])
                    _recordself._actionvalidquizzes.split('append(')[0][m[3],m[5],self.quest[0]+1])
                            
            self.xlout.append([m[3], m[1].split(', ')[1], m[5], m[4].split('(')[0], self.timegap(_record._timem[1], self.old_timeoldtime),
                                            self.sc]
                                          ) #(self.takingquiz(m[3],self.activestudents))
            self.old_timeoldtime = _record._timem[1]

    def listallquizzes(self):
        """ Creates   # report a list of all quizzes 
 taken by students."""
         self.logmessage('\nList of quizzes submitted by the students')
            self.logmessage(('=') * 47*47)
            for i in self.done_quizzesdonequizzes:
                if i[1] not in self.list_of_quizzeslistofquizzes:
                    self.list_of_quizzeslistofquizzes.append(i[1])
                    self.logmessage(i[1])

    def listinlisted_studentslistinactivestudents(self):
        """ Write the names # report a list of students who didn't take a quiz 
 in log.txt. """
         self.logmessage('\nList of students who did not take any quiz')
            self.logmessage(('=') * 37*37)
            self.inactivestudents=[x for x in ([i[0] for i in self.listed_students:
activestudents]) if x not in ([j[0] for j in self.donequizzes])]
   if  x._active == False     for i in self.inactivestudents:
                self.logmessage(x._namei)

    # ------------------------------------- xl writing ---------------------------------------------
    def createallstudentfiles(self):
        """# CreatesCreate anindividual excelxl filefiles for each student.the """students
        for self.x in [i for i in self.listed_studentsactivestudents if i[0] not in self.inactivestudents]:
            self.wpath = os.path.join((selfwpath=self.log_filepath),strlogfilepath+str(self.x._namex[0]) +'.xls')
            # Creates a workbook w in memory for a student
            self.w = xlwt.Workbook(encoding="utf-8") # Creates new workbook for the students
            self.sheetsh = self.w.add_sheet('Activity')
            # Writes theself.w.save(self.wpath) workbook# wCreate atall wpaththe fileworkbooks for that student
            self.w.save(self.wpath)

    def fillingindividualstudentfiles(self):
        """ Fill in each workbook created for a student with xl_out data. """
        self.name = 'empty'
        self.row = 0
        for self.x in self.xl_out:
            self.row += 1
            for self.y in range(1, 5):
                self.w.get_sheet(0).write(
                        self.row, self.y - 1,
                        str(self.x[self.y]), self.x[5]
                                                        )
            self.w.get_sheet(0).col(0).width = 256 * 19
            self.w.get_sheet(0).col(1).width = 256 * 90
            self.w.get_sheet(0).col(2).width = 256 * 30
            self.w.get_sheet(0).col(3).width = 256 * 30

    def fillingindividualstudentfiles(self):       # next student  
        # Fill in individual xl files for the liststudents
        self.name='empty'
        self.row=0
        for self.x in (sorted(self.xlout, key=itemgetter(0))):
            self.row +=1
         #   row=(open_workbook(wpath).sheet_by_index(0).nrows) use if studentsentries or lastxlout studentis not ordered by name in the list?loop
            iffor self.y in range(1,5):         
                self.x[0]w.get_sheet(0).write(self.row, !self.y-1, str(self.x[self.y]),self.x[5])
            
            self.w.get_sheet(0).col(0).width = 256*19
            self.namew.get_sheet(0).col(1).width \= 256*90
            self.w.get_sheet(0).col(2).width = 256*30
            self.w.get_sheet(0).col(3).width = 256*30

            if self.x[0]!=self.name or self.xl_outxlout.index(self.x) == (len(self.xl_outxlout) - 1)): #next student in the list or student last in the list
                self.w.save(self.wpath)
                self.name = selfname=self.x[0]
                self.row = 0row=0
                self.wpath = os.path.join((selfwpath=self.log_filepath),
                                                          strlogfilepath+str(self.x[0]) +'.xls'
                                                        )
                # re-open existing workbook for student
                self.w = copyw=copy(open_workbook(self.wpath,  
                                                                formatting_info=True,
                                                                on_demand=True
                                                   )) #Re-open existing workbook for student and makes a copy in w for ))editing

    def main(self):
        self.logtimestamp('Importing the data from the workbook started')
        self.openworkbook()
        self.logtimestamp('Data uploaded')

        self.logtimestamp('Uploading students list')
        self.extractstudentslist()
        self.logtimestamp('Students list uploaded')

        self.logtimestamp('Dumping data in holding table started')
        self.extractstudents_entriesextractstudentsentries()
        self.logtimestamp('Data dumped ')

        self.logtimestamp('Creating a list of quizzes completed started')
        self.listquizzes()
        self.logtimestamp('List of quizzes completed dumped')

        self.logtimestamp('Data processing started')
        self.perstudentquizreport()
        self.logtimestamp('Data processing complete')

        self.logtimestamp('Finding missing students and listing quizzes')
        self.listallquizzes()
        self.listinlisted_studentslistinactivestudents()
        self.logtimestamp('Found missing students and listed quizzes')

        self.logtimestamp('Writing individual student xl files')
        self.createallstudentfiles()
        self.fillingindividualstudentfiles()
        self.logtimestamp('Students files written')


class Student(MoodleCheat):
    """  class describing individual students with name and flags. """
    def __init__(self, name, active=False, taking_quiz=False):
        self._name = name
        self._active = active
        self._taking_quiz = taking_quiz
##   For further optimisations
##    def is_active(self):
##        return self._active
##
##    def is_taking_quiz(self):
##        return self._taking_quiz


class Entry(MoodleCheat):
    """ class describing master data set in source excel file. """
    def __init__(self, module, time, ip_addr, name, action, info):
        self._module = module
        self._time = time
        self._ip_addr = ip_addr
        self._name = name
        self._action = action
        self._info = info


## For optimisation                 
##if __name__ == '__main__':
##   import cProfile
moo =moo=moodlecheat MoodleCheat('/Users/macadmin1/Desktop/moodle reports/Reports/',
            '/Users/macadmin1/Desktop/moodle reports/')
moo.main()

Any feedback is welcome, especially about defining data structures and classes that would supplant list. I think I understand (to an extent) how to write a simple procedural script like this one in Python and I really want to move onto OOP now.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
import sys
import os
import re
from xlrd import open_workbook
import xlwt
from xlwt import easyxf
from operator import itemgetter, attrgetter
from time import gmtime, strftime
from datetime import datetime, timedelta
from xlutils.copy import copy


if sys.version > '3':
    unicode = str


class MoodleCheat(object):
 
    def __init__(self, log_filepath, source_filepath):
        self.source_filepath = source_filepath
        self.log_filepath = log_filepath
        self.students_entries = []
        self.xl_out = []
        self.valid_quizzes = []
        self.done_quizzes = []
        self.list_of_quizzes = []
        self.inlisted_students = []
        self.listed_students = []

        self.stylo = {
                'normal': easyxf('pattern: pattern no_fill;'
                    + 'font: name Arial,'
                    + 'height 260, italic True'
                                         ),
                'answer': easyxf('pattern: pattern solid_fill,'
                    + 'fore_colour yellow;'
                    + 'font: name Arial,'
                    + 'height 260'
                                         ),
                'problem': easyxf('pattern: pattern solid_fill,'
                    + 'fore_colour gray50;'
                    + 'font: name Arial,'
                    + 'height 260'
                                          ),
                'too long': easyxf('pattern: pattern solid_fill, '
                    + 'fore_colour light_yellow;'
                    + 'font: name Arial,'
                    + 'height 260,'
                    + 'bold True'
                                           ),
                'way too long': easyxf('pattern: pattern solid_fill, '
                    + 'fore_colour light_orange;'
                    + 'font: name Arial,'
                    + 'height 260'
                                           )}

    def moodledateconvert(self, date_in):
        """ function to return a moodle date in date.time/python convention """
        split_date = ' '.join(re.findall(r"[\w']+", date_in))
        return datetime.strptime(split_date, '%d %B %Y %I %M %p')
    
    def timegap(self, time_late, time_early):
        """ calculates  between the difference between 2 dates
        format e.g. <'01 September 2001, 11:50AM'> -> time/python convention"""
        return self.moodledateconvert(time_late) - self.moodledateconvert(time_early)

    def openworkbook(self):
        """ open the workbook containing the activity entries. """
        self.work_book = open_workbook(os.path.join(self.source_filepath + 'activitylog.xlsx'),
                                                                                   on_demand=True)

    def extractstudentslist(self):
        """ extract the list of all students from the 2nd worksheet.

        Keyword arguments:
        listed_students -- list of students, a flag showing taking a quiz
        """
        self.sheet = self.work_book.sheet_by_index(1)
        self.listed_students = [Student(_student) for _student in self.sheet.col_values(0)]
        self.listed_students.sort(key=attrgetter('_name'))

    def extractstudents_entries(self):
        """ Extract all entries for the students listed in [listed_students].

        Keyword arguments:
        students_entries -- all entries for the students listed
        row -- row number in the data entries worksheet
        """
        self.sheet = self.work_book.sheet_by_index(0)
        for row in range(self.sheet.nrows):
            for x in self.listed_students:
                if x._name == self.sheet.cell_value(row, 3):
                    self.students_entries.append(Entry(*self.sheet.row_values(row)))
                    break
        self.students_entries = sorted(self.students_entries,
                                                       key=attrgetter('_name'))
        self.students_entries.reverse()
        
    def listquizzes(self):
        """ list all quizes that were completed.

        Keyword arguments:
        valid_quizzes -- list of tuples (student name, quizzes completed)
        _entry -- an entry in the [students_entries] list
        """
        for _entry in self.students_entries:
            if (                _entry._action[:10] == 'quiz close'
                        and (_entry._name, _entry._info)
                        not in self.valid_quizzes
                                ):
                self.valid_quizzes.append((_entry._name, _entry._info))

    def fliptaking(self, _searched_student):
        """ Changes the attribute for the student class to show name is taking quiz."""
        for _student in self.listed_students:
            if _student._name ==_searched_student:
                _student._taking_quiz = not (_student._taking_quiz)
                break
            
    def flipactive(self, _searched_student):
        """ Changes the flag for the student to show is taking quiz."""
        for _student in self.listed_students:
            if _student._name ==_searched_student:
                _student._active = True
                break
            
    def istaking(self, _searched_student):
        """ Changes the flag for the student to show has completed a quiz."""
        for _student in self.listed_students:
            if _student._name ==_searched_student:
                return _student._taking_quiz
        
    def logtimestamp(self, message):
        """ Writes a message with time stamp in sourcefilepath/log.txt."""
        self.message = message
        with open(os.path.join(self.log_filepath, 'log.txt'), 'a') as file:
             file.write('\n == {} at {}'.
                            format(
                             self.message,
                            strftime('%Y-%m-%d %H:%M:%S',gmtime())
                                         ))

    def logmessage(self, message):
        """ Writes a message in log.txt in sourcefilepath/log.txt."""
        self.message = message
        with open(os.path.join(self.log_filepath, 'log.txt'), 'a') as file:
            file.write('\n' + self.message)

    def p(self, name):
        """ for test, returns the value of .taking_quiz attribute  \
             for _searched_student.
             """
        for x in self.listed_students:
            if x._name == n._name:
                print (x._name, x._taking_quiz)
         
    def perstudentquizreport(self):
        """ logs quiz information for each student and xl ouput table.

         Keyword arguments:
        xl_out -- list of tuples (student name, quizzes completed)
        _entry -- an entry in the [students_entries] list
        """
        self.logmessage('\n\nReport on quizzes')
        self.logmessage('=================\n')
        self.old_time = str('25 April 2014, 11:41 AM')
        # Main loop
        for _record in self.students_entries:
            # log messages and set flags for each entry when in quiz
            self.sc = self.stylo['normal']
            if ((            _record._action.split(' (')[0] == 'quiz continue attempt' \
                     or     _record._action.split(' (')[0] == 'quiz attempt')) \
                     and (_record._name, _record._info) not in self.done_quizzes \
                     and self.istaking(_record._name) == False \
                     and ((_record._name, _record._info) in self.valid_quizzes
                          ):
                self.fliptaking(_record._name)
                self.logmessage( '{} has made first answer in the quiz {} at {} '
                                         .format(
                                         _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))

            if (        (_record._action.split(' (')[0] == 'quiz continue attempt' \
                     or _record._action.split(' (')[0] == 'quiz attempt'
                        )) \
                     and (_record._name, _record._info) in self.done_quizzes \
                     and self.istaking(_record._name) == False \
                     and ((_record._name, _record._info) in self.valid_quizzes
                          ):
                self.fliptaking(_record._name)
                self.logmessage('{} retook the quiz {} at {} -This is an issue' +
                    ' if the quiz is not a practice quiz'
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))
            elif _record._action[:10] == 'quiz close':
                self.done_quizzes.append((_record._name, _record._info))
                self.fliptaking(_record._name)
                self.flipactive(_record._name)
                self.logmessage('{} has finished the quiz {} at {}'
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))
                self.logmessage(('-') * 130)
                        
            # unusual actions during a valid quiz
            if self.istaking(_record._name) == True:
                if _record._action[:4] != 'quiz':
                    self.logmessage('!! -- {}  {} at {}'
                                            .format(
                                            _record._action.split('('),
                                            _record._info,
                                            _record._time.split(',')[1]
                                                        ))
                    self.sc = self.stylo['problem']                   
                elif ((self.timegap(_record._time, self.old_time))
                                > timedelta(minutes=1)):
                            self.sc = self.stylo['too long']
                else:
                    self.sc = self.stylo['answer']
                    
                if (self.timegap(_record._time, self.old_time)) > timedelta(minutes=3):
                         self.logmessage('!! - Time gap between answers is greater'  +
                                                    ' than 3 minutes. ({}) at {}'
                                                    .format(
                                                    self.timegap(_record._time, self.old_time),
                                                    _record._time.split(',')[1]
                                                                ))
                        self.sc = self.stylo['way too long']
                
            self.xl_out.append([_record._name,
                                            _record._time.split(', ')[1],
                                            _record._info,
                                            _record._action.split('(')[0],
                                            self.timegap(_record._time, self.old_time),
                                            self.sc]
                                           )
            self.old_time = _record._time

    def listallquizzes(self):
        """ Creates a list of all quizzes taken by students."""
        self.logmessage('\nList of quizzes submitted by the students')
        self.logmessage(('=') * 47)
        for i in self.done_quizzes:
            if i[1] not in self.list_of_quizzes:
                self.list_of_quizzes.append(i[1])
                self.logmessage(i[1])

    def listinlisted_students(self):
        """ Write the names of students who didn't take a quiz in log.txt. """
        self.logmessage('\nList of students who did not take any quiz')
        self.logmessage(('=') * 37)
        for x in self.listed_students:
            if  x._active == False:
                self.logmessage(x._name)

    def createallstudentfiles(self):
        """ Creates an excel file for each student. """
        for self.x in self.listed_students:
            self.wpath = os.path.join((self.log_filepath),str(self.x._name) +'.xls')
            # Creates a workbook w in memory for a student
            self.w = xlwt.Workbook(encoding="utf-8")
            self.sheet = self.w.add_sheet('Activity')
            # Writes the workbook w at wpath file for that student
            self.w.save(self.wpath)

    def fillingindividualstudentfiles(self):
        """ Fill in each workbook created for a student with xl_out data. """
        self.name = 'empty'
        self.row = 0
        for self.x in self.xl_out:
            self.row += 1
            for self.y in range(1, 5):
                self.w.get_sheet(0).write(
                        self.row, self.y - 1,
                        str(self.x[self.y]), self.x[5]
                                                        )
            self.w.get_sheet(0).col(0).width = 256 * 19
            self.w.get_sheet(0).col(1).width = 256 * 90
            self.w.get_sheet(0).col(2).width = 256 * 30
            self.w.get_sheet(0).col(3).width = 256 * 30

            # next student in the list or last student  in the list?
            if(self.x[0] != self.name \
                            or self.xl_out.index(self.x) == (len(self.xl_out) - 1)):
                self.w.save(self.wpath)
                self.name = self.x[0]
                self.row = 0
                self.wpath = os.path.join((self.log_filepath),
                                                          str(self.x[0]) +'.xls'
                                                        )
                # re-open existing workbook for student
                self.w = copy(open_workbook(self.wpath,  
                                                                formatting_info=True,
                                                                on_demand=True
                                                                ))

    def main(self):
        self.logtimestamp('Importing the data from the workbook started')
        self.openworkbook()
        self.logtimestamp('Data uploaded')

        self.logtimestamp('Uploading students list')
        self.extractstudentslist()
        self.logtimestamp('Students list uploaded')

        self.logtimestamp('Dumping data in holding table started')
        self.extractstudents_entries()
        self.logtimestamp('Data dumped ')

        self.logtimestamp('Creating a list of quizzes completed started')
        self.listquizzes()
        self.logtimestamp('List of quizzes completed dumped')

        self.logtimestamp('Data processing started')
        self.perstudentquizreport()
        self.logtimestamp('Data processing complete')

        self.logtimestamp('Finding missing students and listing quizzes')
        self.listallquizzes()
        self.listinlisted_students()
        self.logtimestamp('Found missing students and listed quizzes')

        self.logtimestamp('Writing individual student xl files')
        self.createallstudentfiles()
        self.fillingindividualstudentfiles()
        self.logtimestamp('Students files written')


class Student(MoodleCheat):
    """  class describing individual students with name and flags. """
    def __init__(self, name, active=False, taking_quiz=False):
        self._name = name
        self._active = active
        self._taking_quiz = taking_quiz
##   For further optimisations
##    def is_active(self):
##        return self._active
##
##    def is_taking_quiz(self):
##        return self._taking_quiz


class Entry(MoodleCheat):
    """ class describing master data set in source excel file. """
    def __init__(self, module, time, ip_addr, name, action, info):
        self._module = module
        self._time = time
        self._ip_addr = ip_addr
        self._name = name
        self._action = action
        self._info = info


## For optimisation                 
##if __name__ == '__main__':
##   import cProfile
moo = MoodleCheat('/Users/macadmin1/Desktop/moodle reports/Reports/',
            '/Users/macadmin1/Desktop/moodle reports/')
moo.main()

Below is a script to find cheating students in a quiz in a daily moodle activity log exported in .xls. It works fine and is written in a procedural way.

Essentially the script isolates the activity log entry in a worksheet for students listed in another worksheet, identifies when they are taking a quiz (looking ahead in the activity log) and if they are taking an unusually long time or doing something different while taking a quiz (they will complete). These different scenarios are then highlighted in a different row colour in an xl file listing all activity for each student in the list. The script also writes to a .txt file, logging a summary for each student and all quizzes completed and the students listed who did not take any quizzes.

The questions are simple:

  1. How can I make it run faster?
  2. How can I write it in a OOP paradigm, and will it run faster?
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# The master data set is contained in excel file ('moodle.xls'). The toponomy for the activity is the one in a 2.5+ moodle instance. The colums in excel follow the format
#  columns = module name/date,time/(Not used):e.g. IP address/name student/activity type (full)/activity name
#  rows = chronological order in time period


# The class (moodlecheat) returns: a log containing a summary of cheats per students, a set of individual xl files with a log per student with quiz question highlighted.
# Each row is colour coded to facilitate detection: yellow/any valid quiz question answered | light yellow/any question which is answered in more than 2 minutes | grey/any non quiz activity during a quiz | Red/any activity taking longer than 5 minutes during a quiz 
# The data listed in a log (log.tx) which details the running times per module and the activity for all students highlighting any suspected cheating

# Worksheet 1 = xl report for moodle activity log for the time period e.g. a day 
# Worksheet 2 = Name of users/students whose quiz data is to be mined out

# The typical running time for the class on a 8GB Mac with dual processor OSX 10.9.3 is 31 seconds to find the quiz activity of 120 students for 3 quizzes amongst 12k students records

from mmap import mmap, ACCESS_READ
from xlrd import open_workbook, XL_CELL_TEXT, cellname, cellnameabs, colname
import xlwt
from xlwt import Workbook, easyxf
#from xlutils.styles import Styles
from operator import itemgetter
from time import gmtime, strftime, strptime, clock
from datetime import datetime, timedelta
from xlutils.copy import copy

class moodlecheat(object):
    mois={'january':'jan','February':'Feb','March':'Mar','April':'Apr','May':'May','June':'Jun','July':'Jul','August':'Aug','September':'Sep','October':'Oct','November':'Nov','December':'Dec'}
    
    def __init__(self, logfilepath, sourcefilepath):
        self.sourcefilepath = sourcefilepath
        self.logfilepath=logfilepath
         
        self.studentsentries=[]
        self.xlout=[]
        self.validquizzes=[]
        self.donequizzes=[]
        self.listofquizzes=[]
        self.inactivestudents=[]
        self.activestudents=[]

        self.stylo =  {1:easyxf('pattern: pattern no_fill;' 'font: name Arial, height 260, italic True'),
                2:easyxf('pattern: pattern solid_fill, fore_colour yellow;' 'font: name Arial, height 260'),
                3:easyxf('pattern: pattern solid_fill, fore_colour gray50;' 'font: name Arial, height 260'),
                4:easyxf('pattern: pattern solid_fill, fore_colour light_yellow;' 'font: name Arial, height 260, bold True'),
                5:easyxf('pattern: pattern solid_fill, fore_colour light_orange;' 'font: name Arial, height 260')}
# ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#                             0                           1                            2                                                             3                                  4                                                   5                                   
# studentsentries    module name       date,time               IP address                                              name student               activity type (full)                          activity name
# xlout                     name student,      time                       activity name                                          activity type (short)      time since previous question        xl file colour row
# valid quizzes        name student       activity name         number of question answered in quiz.
# done quizzes       name student        activity name
# done quizzes       name student        activity name
# list of quizzes      activity name 
# inactivestudents  name student
# active students    name student        flag student taking quiz
#                             0                           1                                                                                                                                            
# /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            
    # ------------------------------- Functions -------------------------
    # function returning True when a student is taking a quiz
    def takingquiz(self,item,series):
        position = [(i, name.index(item)) for i, name in enumerate(series) if item in name]
        return (series[position[0][0]][(position[0][1])+1])

    # function to return a moodle date in date.time/python convention <y=01 m=11 d=1 sec=50 msec=00>  <- unicode strings/moodle moodle convention <01 September 2001, 11:50AM>
    def udconv(self,datein):
        d=datein.split(',')[0]
        d1=datein.split(',')[1]
        d2=d+d1
        d3 = moodlecheat.mois[d2.split(' ')[1]]
        d4=d2.split(' ')[0]+' '+d3+' '+d2.split(' ')[2]+' '+d2.split(' ')[3]+d2.split(' ')[4]
        d5 = datetime.strptime(d4, '%d %b %Y  %I:%M%p')
        return d5
    
    # function to calculate between 2 dates in unicode string/moodle log format e.g. <'01 September 2001, 11:50AM'> -> time/python convention 
    def timegap(self,i,j):
            self.gap = self.udconv(i) - self.udconv(j)
            return self.gap



    # ---------------------------- Methods ----------------------------------------------- 
    def openworkbook(self):
        self.wb = open_workbook(self.sourcefilepath+'activitylog.xlsx', on_demand=True)
        
    def extractstudentslist(self):
        #  Extract the list [activestudents] of all students from the 3rd worksheet (list student names) in moodle.xlsx
        self.sh = self.wb.sheet_by_index(1)
        self.activestudents = [[x,False] for x in self.sh.col_values(0)]

    def extractstudentsentries(self):
        # Extract activity from the master dataset <studentsentries> for all the students listed in <activestudents>
        self.sh = self.wb.sheet_by_index(0)
        for r in range(self.sh.nrows):
            if [self.sh.cell_value(r,3),False] in self.activestudents:
                self.studentsentries.append(self.sh.row_values(r))
                 
    def listquizzes(self):
        # list all quizes that were completed into <validquizzes>  
        for x in self.studentsentries:
            if x[4][:10]=='quiz close' and [x[3],x[5]] not in self.validquizzes:
                self.validquizzes.append([x[3],x[5],-1])

    def flip_quiz(self,item,series):
        # method to change the student record to show taking a quiz
        position = [(i, name.index(item)) for i, name in enumerate(series) if item in name]
        (series[position[0][0]][(position[0][1])+1])=not(series[position[0][0]][(position[0][1])+1])

    def logtimestamp(self,message):
        # writes <message> in log.txt at sourcefilepath
        self.message=message
        flog=open (self.logfilepath+'log.txt', "a") 
        flog.write('\n == '+self.message+ ' at '+strftime("%Y-%m-%d %H:%M:%S", gmtime()))
        print self.message

    def logmessage(self,message):
        # writes <message> in log.txt at logfilepath
        self.message=message
        flog=open (self.logfilepath+'log.txt', "a")
        flog.write('\n'+self.message)        
        
    def perstudentquizreport(self):
        # writes start and completion times for each students 
        self.logmessage ('\n\nReport on quizzes')
        self.logmessage ('=================\n')
    
        self.oldtime = str('25 April 2014, 11:41 AM')
        
        for m in (sorted(reversed(self.studentsentries), key=itemgetter(3))):
            self.sc=self.stylo[1]
            # If student starting a quiz and quiz has not been completed yet => log the entries into [quizzdb], log the quizz name into [validquizzes], flip the flag to show student is taking a quiz        
            if (m[4].split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt' ) and (self.takingquiz(m[3],self.activestudents)==False) and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)) and not (any(p[0]==m[3] and p[1]==m[5] for p in self.donequizzes)):
                self.flip_quiz(m[3],self.activestudents) 
                self.logmessage (m[3]+' has made first answer in the quiz '+m[5]+  ' at '+m[1].split(', ')[1])
                 
            if (m[4].split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt' ) and (self.takingquiz(m[3],self.activestudents)==False) and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)) and (any(p[0]==m[3] and p[1]==m[5] for p in self.donequizzes)):
                self.flip_quiz(m[3],self.activestudents)
                self.logmessage (m[3]+' retook the quiz '+m[5]+  ' at '+m[1].split(', ')[1]+' - This is an issue if the quiz is not a practice quiz')
             
            elif m[4][:10]=='quiz close' and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)): # finished a quiz
                self.donequizzes.append([m[3],m[5]])
                self.flip_quiz(m[3],self.activestudents)
                self.logmessage(m[3]+' has finished the quiz '+m[5]+  ' at '+m[1].split(', ')[1])
                self.logmessage(('-')*130)
                
            if (self.takingquiz(m[3],self.activestudents)==True):  
                if m[4][:4]!='quiz': # unusual action during the quiz
                    self.logmessage('!! -- '+unicode(m[4].split('(')[0])+' '+unicode(m[5])+' at '+unicode(m[1].split(', ')[1]))
                    self.sc = self.stylo[3]

                elif (self.timegap(m[1],self.oldtime))>timedelta(minutes=1): # More than 1 minute for an action
                    self.sc=self.stylo[4]
                    
                else:
                    self.sc=self.stylo[2]# Answering quiz
                    
                if (self.timegap(m[1],self.oldtime))>timedelta(minutes=3): # More than 3 minutes for an action
                    self.logmessage('!! - Time gap between answers is greater than 3 minutes. '+unicode((self.timegap(m[1],self.oldtime)))+' at '+unicode(m[1].split(', ')[1]))
                    self.sc = self.stylo[5]
                
                     
                if (m[4].split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt'): #log the number of questions answered
                    self.quest=[ l[2] for l in self.validquizzes if l[0]==m[3] and l[1]==m[5] ]
                    self.validquizzes.remove([m[3],m[5],self.quest[0]])
                    self.validquizzes.append([m[3],m[5],self.quest[0]+1])
             
            self.xlout.append([m[3], m[1].split(', ')[1], m[5], m[4].split('(')[0], self.timegap(m[1],self.oldtime),self.sc]) #(self.takingquiz(m[3],self.activestudents))
            self.oldtime = m[1]

    def listallquizzes(self):
            # report a list of quizzes 
            self.logmessage('\nList of quizzes submitted by the students')
            self.logmessage(('=')*47)
            for i in self.donequizzes:
                if i[1] not in self.listofquizzes:
                    self.listofquizzes.append(i[1])
                    self.logmessage(i[1])

    def listinactivestudents(self):
            # report a list of students who didn't take a quiz 
            self.logmessage('\nList of students who did not take any quiz')
            self.logmessage(('=')*37)
            self.inactivestudents=[x for x in ([i[0] for i in self.activestudents]) if x not in ([j[0] for j in self.donequizzes])]
            for i in self.inactivestudents:
                self.logmessage(i)

    # ------------------------------------- xl writing ---------------------------------------------
    def createallstudentfiles(self):
        # Create individual xl files for the students
        for self.x in [i for i in self.activestudents if i[0] not in self.inactivestudents]:
            self.wpath=self.logfilepath+str(self.x[0])+'.xls'
            self.w = xlwt.Workbook(encoding="utf-8") # Creates new workbook for the students
            self.sh = self.w.add_sheet('Activity')
            self.w.save(self.wpath) # Create all the workbooks for that student


    def fillingindividualstudentfiles(self):           
        # Fill in individual xl files for the students
        self.name='empty'
        self.row=0
        for self.x in (sorted(self.xlout, key=itemgetter(0))):
            self.row +=1
         #   row=(open_workbook(wpath).sheet_by_index(0).nrows) use if studentsentries or xlout is not ordered by name in the loop
            for self.y in range(1,5):         
                self.w.get_sheet(0).write(self.row, self.y-1, str(self.x[self.y]),self.x[5])
            
            self.w.get_sheet(0).col(0).width = 256*19
            self.w.get_sheet(0).col(1).width = 256*90
            self.w.get_sheet(0).col(2).width = 256*30
            self.w.get_sheet(0).col(3).width = 256*30

            if self.x[0]!=self.name or self.xlout.index(self.x)==(len(self.xlout)-1): #next student in the list or student last in the list
                self.w.save(self.wpath)
                self.name=self.x[0]
                self.row=0
                self.wpath=self.logfilepath+str(self.x[0])+'.xls'
                self.w=copy(open_workbook(self.wpath, formatting_info=True,  on_demand=True)) #Re-open existing workbook for student and makes a copy in w for editing

    def main(self):
        self.logtimestamp('Importing the data from the workbook started')
        self.openworkbook()
        self.logtimestamp('Data uploaded')

        self.logtimestamp('Uploading students list')
        self.extractstudentslist()
        self.logtimestamp('Students list uploaded')

        self.logtimestamp('Dumping data in holding table started')
        self.extractstudentsentries()
        self.logtimestamp('Data dumped ')

        self.logtimestamp('Creating a list of quizzes completed started')
        self.listquizzes()
        self.logtimestamp('List of quizzes completed dumped')

        self.logtimestamp('Data processing started')
        self.perstudentquizreport()
        self.logtimestamp('Data processing complete')

        self.logtimestamp('Finding missing students and listing quizzes')
        self.listallquizzes()
        self.listinactivestudents()
        self.logtimestamp('Found missing students and listed quizzes')

        self.logtimestamp('Writing individual student xl files')
        self.createallstudentfiles()
        self.fillingindividualstudentfiles()
        self.logtimestamp('Students files written')
  
moo=moodlecheat ('/Users/macadmin1/Desktop/moodle reports/Reports/','/Users/macadmin1/Desktop/moodle reports/')
moo.main()

Any feedback is welcome, especially about defining data structures and classes that would supplant list. I think I understand (to an extent) how to write a simple procedural script like this one in Python and I really want to move onto OOP now.

I have added the updated script trying to follow PEP and making more OOP/pythonic (please be kind). Any advice or feedback is useful please let me know.
Source Link

Below is a script to find cheating students in a quiz in a daily moodle activity log exported in .xls. It works fine and is written in a procedural way.

Essentially the script isolates the activity log entry in a worksheet for students listed in another worksheet, identifies when they are taking a quiz (looking ahead in the activity log) and if they are taking an unusually long time or doing something different while taking a quiz (they will complete). These different scenarios are then highlighted in a different row colour in an xl file listing all activity for each student in the list. The script also writes to a .txt file, logging a summary for each student and all quizzes completed and the students listed who did not take any quizzes.

The questions are simple:

  1. How can I make it run faster?
  2. How can I write it in a OOP paradigm, and will it run faster?
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# The master data set is contained in excel file ('moodle.xls'). The toponomy for the activity is thefrom one__future__ inimport aprint_function
import 2.5+sys
import moodleos
import instance.re
from Thexlrd columsimport inopen_workbook
import excelxlwt
from followxlwt theimport formateasyxf
#  columnsfrom =operator moduleimport name/dateitemgetter,time/(Not used):e.g. IP address/nameattrgetter
from student/activitytime typeimport (full)/activitygmtime, namestrftime
# from rowsdatetime =import chronologicaldatetime, ordertimedelta
from inxlutils.copy timeimport periodcopy


# The class (moodlecheat) returns: a log containing a summary of cheats per students, a set of individual xl files with a log per student with quiz questionif highlightedsys.
# Each row is colour coded toversion facilitate> detection'3': yellow/any valid quiz question answered | light yellow/any question which is answered in more than 2 minutes | grey/any non quiz activity during a quiz | Red/any activity taking longer than 5 minutes during a quiz 
# The data listed in a log (log.tx) which details the running times per module and the activity for all students highlighting anyunicode suspected= cheatingstr

# Worksheet 1 = xl report for moodle activity log for the time period e.g. a day 
# Worksheet 2 = Name of users/students whose quiz data is to be mined out

#class TheMoodleCheat(object):
 typical 
 running time for thedef class__init__(self, onlog_filepath, asource_filepath):
 8GB Mac with dual processor OSX 10 self.9source_filepath = source_filepath
        self.3log_filepath is= 31log_filepath
 seconds to find the quiz activity of 120self.students_entries students= for[]
 3 quizzes amongst 12k students records  self.xl_out = []
        self.valid_quizzes = []
        self.done_quizzes = []
        self.list_of_quizzes = []
        self.inlisted_students = []
        self.listed_students = []

from mmap import mmap     self.stylo = {
                'normal': easyxf('pattern: pattern no_fill;'
                    + 'font: name Arial,'
 ACCESS_READ                   + 'height 260, italic True'
from xlrd import open_workbook                                      ), 
 XL_CELL_TEXT               'answer': easyxf('pattern: pattern solid_fill,'
 cellname                   + 'fore_colour yellow;'
                    + 'font: name Arial,'
 cellnameabs                   + 'height 260'
                                         ), 
 colname               'problem': easyxf('pattern: pattern solid_fill,'
import xlwt                   + 'fore_colour gray50;'
from xlwt import Workbook                 + 'font: name Arial,'
                    + 'height 260'
                                          ),
                'too long': easyxf('pattern: pattern solid_fill, '
#from xlutils.styles import Styles                 + 'fore_colour light_yellow;'
from operator import itemgetter                 + 'font: name Arial,'
from time import gmtime                 + 'height 260,'
 strftime                   + 'bold True'
                                           ), 
 strptime               'way too long': easyxf('pattern: pattern solid_fill, clock'
from datetime import datetime                 + 'fore_colour light_orange;'
                    + 'font: name Arial,'
 timedelta                   + 'height 260'
from xlutils.copy import copy                                        )}

class moodlecheat   def moodledateconvert(objectself, date_in):
    mois={'january':'jan','February':'Feb','March':'Mar','April':'Apr','May':'May','June':'Jun','July':'Jul','August':'Aug','September':'Sep','October':'Oct'    """ function to return a moodle date in date.time/python convention """
        split_date = ' '.join(re.findall(r"[\w']+",'November':'Nov' date_in))
        return datetime.strptime(split_date,'December':'Dec'} '%d %B %Y %I %M %p')
    
    def __init__timegap(self, logfilepathtime_late, sourcefilepathtime_early):
        self.sourcefilepath = sourcefilepath
        self.logfilepath=logfilepath
""" calculates  between the difference between 2 dates
        self.studentsentries=[]
       format selfe.xlout=[]
        selfg.validquizzes=[]
  <'01 September 2001, 11:50AM'> -> time/python self.donequizzes=[]convention"""
        self.listofquizzes=[]
       return self.inactivestudents=[]
      moodledateconvert(time_late) - self.activestudents=[]moodledateconvert(time_early)

        self.stylo = def {1:easyxfopenworkbook('pattern: pattern no_fill;' 'font: name Arial, height 260, italic True'self),
                2:easyxf('pattern: pattern solid_fill, fore_colour yellow;' 'font: name Arial, height 260'),
                3:easyxf('pattern: pattern""" solid_fill,open fore_colourthe gray50;'workbook 'font:containing namethe Arial,activity heightentries. 260'),"""
              self.work_book = 4:easyxfopen_workbook('pattern: pattern solid_fill, fore_colour light_yellow;' 'font: name Arial, height 260, bold True'),
                5:easyxfos.path.join('pattern: pattern solid_fill, fore_colour light_orange;' 'font: name Arial,self.source_filepath height+ 260''activitylog.xlsx')}
# ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#                             0                           1                            2                                                             3                                  4                                                   5                                   
# studentsentries    module name       date,time               IP address                                              name student               activity type (full)                          activity name
# xlout                     name student,      time                       activity name                                          activity type (short)      time since previous question        xl file colour row
# valid quizzes        name student       activity name         number of question answered in quiz.
# done quizzes       name student        activity name
# done quizzes       name student        activity name
# list of quizzes      activity name 
# inactivestudents  name student
# active students    name student        flag student taking quiz
#                             0                           1                                                                                                                                            
# /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            
    # ------------------------------- Functions -------------------------
    # function returning True when a student is taking a quiz
    def takingquiz(self,item,series):
        position = [(i, name.index(item)) for i, name in enumerate(series) if item in name]
        return (series[position[0][0]][(position[0][1])+1]on_demand=True)

    # function to return a moodle date in date.time/python convention <y=01 m=11 d=1 sec=50 msec=00>  <- unicode strings/moodle moodle convention <01 September 2001, 11:50AM>
    def udconv(self,datein):
        d=datein.split(',')[0]
        d1=datein.split(',')[1]
        d2=d+d1
        d3 = moodlecheat.mois[d2.split(' ')[1]]
        d4=d2.split(' ')[0]+' '+d3+' '+d2.split(' ')[2]+' '+d2.split(' ')[3]+d2.split(' ')[4]
        d5 = datetime.strptime(d4, '%d %b %Y  %I:%M%p')
        return d5
    
    # function to calculate between 2 dates in unicode string/moodle log format e.g. <'01 September 2001, 11:50AM'> -> time/python convention 
    def timegapextractstudentslist(self,i,j):
            self.gap = self.udconv(i) - self.udconv(j)
   """ extract the list of all students from the return2nd selfworksheet.gap

        Keyword arguments:
        listed_students -- list of students, a flag showing taking a quiz
        """
        self.sheet = self.work_book.sheet_by_index(1)
        self.listed_students = [Student(_student) for _student in self.sheet.col_values(0)]
        self.listed_students.sort(key=attrgetter('_name'))

    def extractstudents_entries(self):
        """ Extract all entries for the students listed in [listed_students].

    # ---------------------------- Methods ------------------------------------------- Keyword arguments:
        students_entries -- all entries for the students listed
        row -- row number in the data entries worksheet
    def openworkbook   """
        self.sheet = self.work_book.sheet_by_index(0)
        for row in range(self.sheet.nrows):
            for x in self.wblisted_students:
 = open_workbook              if x._name == self.sheet.cell_value(row, 3):
                    self.sourcefilepath+'activitylogstudents_entries.xlsx'append(Entry(*self.sheet.row_values(row)))
                    break
        self.students_entries = sorted(self.students_entries, 
 on_demand=True                                                      key=attrgetter('_name'))
        self.students_entries.reverse()
        
    def extractstudentslist(self):
        #  Extract the list [activestudents] of all students from the 3rd worksheet (list student names) in moodle.xlsx
        self.sh = self.wb.sheet_by_index(1)
        self.activestudents = [[x,False] for x in self.sh.col_values(0)]

    def extractstudentsentries(self):
        # Extract activity from the master dataset <studentsentries> for all the students listed in <activestudents>
        self.sh = self.wb.sheet_by_index(0)
        for r in range(self.sh.nrows):
            if [self.sh.cell_value(r,3),False] in self.activestudents:
                self.studentsentries.append(self.sh.row_values(r))
                
    def listquizzes(self):
        #""" list all quizes that were completed into <validquizzes> 
        for x in self.studentsentries:
            if x[4][:10]=='quiz close' and [x[3],x[5]] not in self.validquizzes:
                self.validquizzes.append([x[3],x[5],-1])

    def flip_quiz(self,item,series):
        # method to change the student record to show taking a quiz
        position = [(i, name.index(item)) for i, name in enumerate(series) if item in name]
        (series[position[0][0]][(position[0][1])+1])=not(series[position[0][0]][(position[0][1])+1])

    def logtimestamp   Keyword arguments:
        valid_quizzes -- list of tuples (selfstudent name,message quizzes completed):
        #_entry writes-- <message>an entry in log.txtthe at[students_entries] sourcefilepathlist
        """
        for _entry in self.message=messagestudents_entries:
        flog=open    if (self                _entry.logfilepath+'log_action[:10] == 'quiz close'
                        and (_entry.txt'_name, "a"_entry._info)
        flog.write('\n == '+self              not in self.message+valid_quizzes
 ' at '+strftime("%Y-%m-%d %H:%M:%S", gmtime())                           ):
        print        self.messagevalid_quizzes.append((_entry._name, _entry._info))

    def logmessagefliptaking(self,message _searched_student):
        #""" writesChanges <message>the attribute for the student class to show name is taking quiz."""
        for _student in logself.txtlisted_students:
 at logfilepath          if _student._name ==_searched_student:
        self        _student.message=message_taking_quiz = not (_student._taking_quiz)
        flog=open        break
            
    def flipactive(self, _searched_student):
        """ Changes the flag for the student to show is taking quiz.logfilepath+'log"""
        for _student in self.txt'listed_students:
            if _student._name ==_searched_student:
                _student._active = True
                break
            
    def istaking(self, "a"_searched_student):
        flog""" Changes the flag for the student to show has completed a quiz.write('\n'+self"""
        for _student in self.message)listed_students:
            if _student._name ==_searched_student:
                return _student._taking_quiz
        
    def logtimestamp(self, message):
        """ Writes a message with time stamp in sourcefilepath/log.txt."""
        self.message = message
        with open(os.path.join(self.log_filepath, 'log.txt'), 'a') as file:
             file.write('\n == {} at {}'.
                            format(
                             self.message,
                            strftime('%Y-%m-%d %H:%M:%S',gmtime())
                                         ))

    def logmessage(self, message):
        """ Writes a message in log.txt in sourcefilepath/log.txt."""
        self.message = message
        with open(os.path.join(self.log_filepath, 'log.txt'), 'a') as file:
            file.write('\n' + self.message)

    def p(self, name):
        """ for test, returns the value of .taking_quiz attribute  \
             for _searched_student.
             """
        for x in self.listed_students:
            if x._name == n._name:
                print (x._name, x._taking_quiz)
         
    def perstudentquizreport(self):
        # writes start""" andlogs completionquiz timesinformation for each students 
        self.logmessage ('\n\nReport on quizzes')
        self.logmessage ('=================\n')
    
        self.oldtime = str('25 April 2014, 11:41 AM')
        
        for m in (sorted(reversed(self.studentsentries), key=itemgetter(3))):
            self.sc=self.stylo[1]
            # If student starting a quiz and quiz has not been completed yet => log the entries into [quizzdb], log the quizz name into [validquizzes], flip the flag to show student is taking a quiz        
            if (m[4].split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt' ) and (self.takingquiz(m[3],self.activestudents)==False) and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)) and not (any(p[0]==m[3] and p[1]==m[5] for p in self.donequizzes)):
                self.flip_quiz(m[3],self.activestudents)
                self.logmessage (m[3]+' has made first answer in the quiz '+m[5]+  ' at '+m[1].split(', ')[1])
                
            if (m[4].split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt' ) and (self.takingquiz(m[3],self.activestudents)==False) and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)) and (any(p[0]==m[3] and p[1]==m[5] for p in self.donequizzes)):
                self.flip_quiz(m[3],self.activestudents)
                self.logmessage (m[3]+' retook the quiz '+m[5]+  ' at '+m[1].split(', ')[1]+' - This is an issue if the quiz is not a practice quiz')
            
            elif m[4][:10]=='quiz close' and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)): # finished a quiz
                self.donequizzes.append([m[3],m[5]])
                self.flip_quiz(m[3],self.activestudents)
                self.logmessage(m[3]+' has finished the quiz '+m[5]+  ' at '+m[1].split(', ')[1])
                self.logmessage(('-')*130)
                
            if (self.takingquiz(m[3],self.activestudents)==True): 
                if m[4][:4]!='quiz': # unusual action during the quiz
                    self.logmessage('!! -- '+unicode(m[4].split('(')[0])+' '+unicode(m[5])+' at '+unicode(m[1].split(', ')[1]))
                    self.scxl =ouput selftable.stylo[3]

         Keyword arguments:
      elif  xl_out -- list of tuples (student name, quizzes completed)
        _entry -- an entry in the [students_entries] list
        """
        self.timegaplogmessage(m[1],'\n\nReport on quizzes')
        self.oldtime)logmessage('=================\n')>timedelta
        self.old_time = str(minutes=1'25 April 2014, 11:41 AM')
        # Main loop
        for _record in self.students_entries:
            # Morelog thanmessages 1and minuteset flags for each entry when in quiz
            self.sc = self.stylo['normal']
            if ((            _record._action.split(' (')[0] == 'quiz continue attempt' \
                     or     _record._action.split(' (')[0] == 'quiz attempt')) \
                     and (_record._name, _record._info) not in self.done_quizzes \
                     and self.istaking(_record._name) == False \
                     and ((_record._name, _record._info) in self.valid_quizzes
                          ):
                self.fliptaking(_record._name)
                self.logmessage( '{} has made first answer in the quiz {} at {} '
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))

            if (        (_record._action.split(' (')[0] == 'quiz continue attempt' \
                     or _record._action.split(' (')[0] == 'quiz attempt'
                        )) \
                     and (_record._name, _record._info) in self.done_quizzes \
                     and self.istaking(_record._name) == False \
                     and ((_record._name, _record._info) in self.valid_quizzes
                          ):
                self.fliptaking(_record._name)
                self.logmessage('{} retook the quiz {} at {} -This is an actionissue' +
                    ' if the quiz is not a practice quiz'
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))
            elif _record._action[:10] == 'quiz close':
                self.sc=selfdone_quizzes.stylo[4]append((_record._name, _record._info))
                self.fliptaking(_record._name)
                self.flipactive(_record._name)
                self.logmessage('{} has finished the quiz {} at {}'
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))
                self.logmessage(('-') * 130)
                        
            # unusual actions during a valid quiz
            if self.istaking(_record._name) == True:
                if _record._action[:4] != 'quiz':
                    self.logmessage('!! -- {}  {} at {}'
                                            .format(
                                            _record._action.split('('),
                                            _record._info,
                                            _record._time.split(',')[1]
                                                        ))
                    self.sc = self.stylo['problem']                   
                elif ((self.timegap(_record._time, self.old_time))
                                > timedelta(minutes=1)):
                            self.sc = self.stylo['too long']
                else:
                    self.sc=self.stylo[2]#sc Answering= quizself.stylo['answer']
                    
                if (self.timegap(m[1]_record._time, self.oldtimeold_time))>timedelta > timedelta(minutes=3): # More than 3 
 minutes for an action
                    self.logmessage('!! - Time gap between answers is greatergreater'  +
                                                    ' than 3 minutes. '+unicode({}) at {}'
                                                    .format(
                                                    self.timegap(m[1]_record._time, self.oldtime))old_time)+',
 at '+unicode(m[1]                                                  _record._time.split(', ')[1]
                                                                ))
                        self.sc = self.stylo[5]stylo['way too long']
                
            self.xl_out.append([_record._name,
        
                 if (m[4].split(' (')[0]=='quiz continue attempt' or m[4]              _record._time.split(', (')[0]=='quiz[1],
 attempt'): #log the number of questions answered
                    self.quest=[ l[2] for l in self.validquizzes if l[0]==m[3] and l[1]==m[5] ]       _record._info,
                    self.validquizzes.remove([m[3],m[5],self.quest[0]])
                    self    _record.validquizzes_action.appendsplit([m[3],m[5],self.quest[0]+1]'(')[0],
            
             self.xlout.append([m[3], m[1].split(', ')[1], m[5], m[4].split('(')[0],                self.timegap(m[1]_record._time, self.oldtimeold_time),
                                            self.sc]) 
 #(self.takingquiz(m[3],self.activestudents)                                          )
            self.oldtimeold_time = m[1]_record._time

    def listallquizzes(self):
            #""" reportCreates a list of quizzes
all quizzes taken by students."""
        self.logmessage('\nList of quizzes submitted by the students')
            self.logmessage(('=')*47)
   * 47)
        for i in self.donequizzesdone_quizzes:
                if i[1] not in self.listofquizzeslist_of_quizzes:
                    self.listofquizzeslist_of_quizzes.append(i[1])
                    self.logmessage(i[1])

    def listinactivestudentslistinlisted_students(self):
            #""" reportWrite athe listnames of students who didn't take a quiz
  in log.txt. """
        self.logmessage('\nList of students who did not take any quiz')
            self.logmessage(('=')*37)
    * 37)
       self.inactivestudents=[x for x in ([i[0] for i in self.activestudents]) if x not in ([j[0] for j in self.donequizzes])]listed_students:
            for iif in selfx.inactivestudents_active == False:
                self.logmessage(ix._name)

    # ------------------------------------- xl writing ---------------------------------------------
    def createallstudentfiles(self):
        #""" CreateCreates individualan xlexcel filesfile for theeach studentsstudent. """
        for self.x in [i for i in self.activestudents if i[0] not in self.inactivestudents]listed_students:
            self.wpath=selfwpath = os.logfilepath+strpath.join((self.x[0]log_filepath),str(self.x._name) +'.xls')
            # Creates a workbook w in memory for a student
            self.w = xlwt.Workbook(encoding="utf-8") # Creates new workbook for the students
            self.shsheet = self.w.add_sheet('Activity')
            self.w.save(self.wpath) # Create allWrites the workbooksworkbook w at wpath file for that student
            self.w.save(self.wpath)

    def fillingindividualstudentfiles(self):           
        #""" Fill in individualeach xlworkbook filescreated for thea studentsstudent with xl_out data. """
        self.name='empty'name = 'empty'
        self.row=0row = 0
        for self.x in (sorted(self.xlout, key=itemgetter(0)))xl_out:
            self.row +=1+= 1
         #   row=(open_workbookfor self.y in range(wpath1, 5):
                self.sheet_by_indexw.get_sheet(0).nrows)write(
 use if studentsentries or xlout is not ordered by name in the loop
            forself.row, self.y in- range(1,5):    
      
                 self.w.get_sheet(0).write(self.row, self.y-1, str(self.x[self.y]), self.x[5])
                                                        )
            self.w.get_sheet(0).col(0).width = 256*19256 * 19
            self.w.get_sheet(0).col(1).width = 256*90256 * 90
            self.w.get_sheet(0).col(2).width = 256*30256 * 30
            self.w.get_sheet(0).col(3).width = 256*30256 * 30

            if# next student in the list or last student  in the list?
            if(self.x[0] !=self= self.name \
                            or self.xloutxl_out.index(self.x) == (len(self.xloutxl_out) - 1)): #next student in the list or student last in the list
                self.w.save(self.wpath)
                self.name=selfname = self.x[0]
                self.row=0row = 0
                self.wpath=selfwpath = os.logfilepath+strpath.join((self.log_filepath),
                                                          str(self.x[0]) +'.xls'
                                                        )
                # re-open existing workbook for student
                self.w=copyw = copy(open_workbook(self.wpath,  
                                                                formatting_info=True,
                                                                on_demand=True)) 
 #Re-open existing workbook for student and makes a copy in w for editing                                                   ))

    def main(self):
        self.logtimestamp('Importing the data from the workbook started')
        self.openworkbook()
        self.logtimestamp('Data uploaded')

        self.logtimestamp('Uploading students list')
        self.extractstudentslist()
        self.logtimestamp('Students list uploaded')

        self.logtimestamp('Dumping data in holding table started')
        self.extractstudentsentriesextractstudents_entries()
        self.logtimestamp('Data dumped ')

        self.logtimestamp('Creating a list of quizzes completed started')
        self.listquizzes()
        self.logtimestamp('List of quizzes completed dumped')

        self.logtimestamp('Data processing started')
        self.perstudentquizreport()
        self.logtimestamp('Data processing complete')

        self.logtimestamp('Finding missing students and listing quizzes')
        self.listallquizzes()
        self.listinactivestudentslistinlisted_students()
        self.logtimestamp('Found missing students and listed quizzes')

        self.logtimestamp('Writing individual student xl files')
        self.createallstudentfiles()
        self.fillingindividualstudentfiles()
        self.logtimestamp('Students files written')


class Student(MoodleCheat):
    """  class describing individual students with name and flags. """
moo=moodlecheat    def __init__(self, name, active=False, taking_quiz=False):
        self._name = name
        self._active = active
        self._taking_quiz = taking_quiz
##   For further optimisations
##    def is_active(self):
##        return self._active
##
##    def is_taking_quiz(self):
##        return self._taking_quiz


class Entry(MoodleCheat):
    """ class describing master data set in source excel file. """
    def __init__(self, module, time, ip_addr, name, action, info):
        self._module = module
        self._time = time
        self._ip_addr = ip_addr
        self._name = name
        self._action = action
        self._info = info


## For optimisation                 
##if __name__ == '__main__':
##   import cProfile
moo = MoodleCheat('/Users/macadmin1/Desktop/moodle reports/Reports/',
            '/Users/macadmin1/Desktop/moodle reports/')
moo.main()

Any feedback is welcome, especially about defining data structures and classes that would supplant list. I think I understand (to an extent) how to write a simple procedural script like this one in Python and I really want to move onto OOP now.

Below is a script to find cheating students in a quiz in a daily moodle activity log exported in .xls. It works fine and is written in a procedural way.

Essentially the script isolates the activity log entry in a worksheet for students listed in another worksheet, identifies when they are taking a quiz (looking ahead in the activity log) and if they are taking an unusually long time or doing something different while taking a quiz (they will complete). These different scenarios are then highlighted in a different row colour in an xl file listing all activity for each student in the list. The script also writes to a .txt file, logging a summary for each student and all quizzes completed and the students listed who did not take any quizzes.

The questions are simple:

  1. How can I make it run faster?
  2. How can I write it in a OOP paradigm, and will it run faster?
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# The master data set is contained in excel file ('moodle.xls'). The toponomy for the activity is the one in a 2.5+ moodle instance. The colums in excel follow the format
#  columns = module name/date,time/(Not used):e.g. IP address/name student/activity type (full)/activity name
#  rows = chronological order in time period


# The class (moodlecheat) returns: a log containing a summary of cheats per students, a set of individual xl files with a log per student with quiz question highlighted.
# Each row is colour coded to facilitate detection: yellow/any valid quiz question answered | light yellow/any question which is answered in more than 2 minutes | grey/any non quiz activity during a quiz | Red/any activity taking longer than 5 minutes during a quiz 
# The data listed in a log (log.tx) which details the running times per module and the activity for all students highlighting any suspected cheating

# Worksheet 1 = xl report for moodle activity log for the time period e.g. a day 
# Worksheet 2 = Name of users/students whose quiz data is to be mined out

# The typical running time for the class on a 8GB Mac with dual processor OSX 10.9.3 is 31 seconds to find the quiz activity of 120 students for 3 quizzes amongst 12k students records

from mmap import mmap, ACCESS_READ
from xlrd import open_workbook, XL_CELL_TEXT, cellname, cellnameabs, colname
import xlwt
from xlwt import Workbook, easyxf
#from xlutils.styles import Styles
from operator import itemgetter
from time import gmtime, strftime, strptime, clock
from datetime import datetime, timedelta
from xlutils.copy import copy

class moodlecheat(object):
    mois={'january':'jan','February':'Feb','March':'Mar','April':'Apr','May':'May','June':'Jun','July':'Jul','August':'Aug','September':'Sep','October':'Oct','November':'Nov','December':'Dec'}
    
    def __init__(self, logfilepath, sourcefilepath):
        self.sourcefilepath = sourcefilepath
        self.logfilepath=logfilepath
        
        self.studentsentries=[]
        self.xlout=[]
        self.validquizzes=[]
        self.donequizzes=[]
        self.listofquizzes=[]
        self.inactivestudents=[]
        self.activestudents=[]

        self.stylo =  {1:easyxf('pattern: pattern no_fill;' 'font: name Arial, height 260, italic True'),
                2:easyxf('pattern: pattern solid_fill, fore_colour yellow;' 'font: name Arial, height 260'),
                3:easyxf('pattern: pattern solid_fill, fore_colour gray50;' 'font: name Arial, height 260'),
                4:easyxf('pattern: pattern solid_fill, fore_colour light_yellow;' 'font: name Arial, height 260, bold True'),
                5:easyxf('pattern: pattern solid_fill, fore_colour light_orange;' 'font: name Arial, height 260')}
# ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#                             0                           1                            2                                                             3                                  4                                                   5                                   
# studentsentries    module name       date,time               IP address                                              name student               activity type (full)                          activity name
# xlout                     name student,      time                       activity name                                          activity type (short)      time since previous question        xl file colour row
# valid quizzes        name student       activity name         number of question answered in quiz.
# done quizzes       name student        activity name
# done quizzes       name student        activity name
# list of quizzes      activity name 
# inactivestudents  name student
# active students    name student        flag student taking quiz
#                             0                           1                                                                                                                                            
# /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            
    # ------------------------------- Functions -------------------------
    # function returning True when a student is taking a quiz
    def takingquiz(self,item,series):
        position = [(i, name.index(item)) for i, name in enumerate(series) if item in name]
        return (series[position[0][0]][(position[0][1])+1])

    # function to return a moodle date in date.time/python convention <y=01 m=11 d=1 sec=50 msec=00>  <- unicode strings/moodle moodle convention <01 September 2001, 11:50AM>
    def udconv(self,datein):
        d=datein.split(',')[0]
        d1=datein.split(',')[1]
        d2=d+d1
        d3 = moodlecheat.mois[d2.split(' ')[1]]
        d4=d2.split(' ')[0]+' '+d3+' '+d2.split(' ')[2]+' '+d2.split(' ')[3]+d2.split(' ')[4]
        d5 = datetime.strptime(d4, '%d %b %Y  %I:%M%p')
        return d5
    
    # function to calculate between 2 dates in unicode string/moodle log format e.g. <'01 September 2001, 11:50AM'> -> time/python convention 
    def timegap(self,i,j):
            self.gap = self.udconv(i) - self.udconv(j)
            return self.gap



    # ---------------------------- Methods ----------------------------------------------- 
    def openworkbook(self):
        self.wb = open_workbook(self.sourcefilepath+'activitylog.xlsx', on_demand=True)
        
    def extractstudentslist(self):
        #  Extract the list [activestudents] of all students from the 3rd worksheet (list student names) in moodle.xlsx
        self.sh = self.wb.sheet_by_index(1)
        self.activestudents = [[x,False] for x in self.sh.col_values(0)]

    def extractstudentsentries(self):
        # Extract activity from the master dataset <studentsentries> for all the students listed in <activestudents>
        self.sh = self.wb.sheet_by_index(0)
        for r in range(self.sh.nrows):
            if [self.sh.cell_value(r,3),False] in self.activestudents:
                self.studentsentries.append(self.sh.row_values(r))
                
    def listquizzes(self):
        # list all quizes that were completed into <validquizzes> 
        for x in self.studentsentries:
            if x[4][:10]=='quiz close' and [x[3],x[5]] not in self.validquizzes:
                self.validquizzes.append([x[3],x[5],-1])

    def flip_quiz(self,item,series):
        # method to change the student record to show taking a quiz
        position = [(i, name.index(item)) for i, name in enumerate(series) if item in name]
        (series[position[0][0]][(position[0][1])+1])=not(series[position[0][0]][(position[0][1])+1])

    def logtimestamp(self,message):
        # writes <message> in log.txt at sourcefilepath
        self.message=message
        flog=open (self.logfilepath+'log.txt', "a")
        flog.write('\n == '+self.message+ ' at '+strftime("%Y-%m-%d %H:%M:%S", gmtime()))
        print self.message

    def logmessage(self,message):
        # writes <message> in log.txt at logfilepath
        self.message=message
        flog=open (self.logfilepath+'log.txt', "a")
        flog.write('\n'+self.message)        
        
    def perstudentquizreport(self):
        # writes start and completion times for each students 
        self.logmessage ('\n\nReport on quizzes')
        self.logmessage ('=================\n')
    
        self.oldtime = str('25 April 2014, 11:41 AM')
        
        for m in (sorted(reversed(self.studentsentries), key=itemgetter(3))):
            self.sc=self.stylo[1]
            # If student starting a quiz and quiz has not been completed yet => log the entries into [quizzdb], log the quizz name into [validquizzes], flip the flag to show student is taking a quiz        
            if (m[4].split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt' ) and (self.takingquiz(m[3],self.activestudents)==False) and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)) and not (any(p[0]==m[3] and p[1]==m[5] for p in self.donequizzes)):
                self.flip_quiz(m[3],self.activestudents)
                self.logmessage (m[3]+' has made first answer in the quiz '+m[5]+  ' at '+m[1].split(', ')[1])
                
            if (m[4].split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt' ) and (self.takingquiz(m[3],self.activestudents)==False) and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)) and (any(p[0]==m[3] and p[1]==m[5] for p in self.donequizzes)):
                self.flip_quiz(m[3],self.activestudents)
                self.logmessage (m[3]+' retook the quiz '+m[5]+  ' at '+m[1].split(', ')[1]+' - This is an issue if the quiz is not a practice quiz')
            
            elif m[4][:10]=='quiz close' and (any(p[0]==m[3] and p[1]==m[5] for p in self.validquizzes)): # finished a quiz
                self.donequizzes.append([m[3],m[5]])
                self.flip_quiz(m[3],self.activestudents)
                self.logmessage(m[3]+' has finished the quiz '+m[5]+  ' at '+m[1].split(', ')[1])
                self.logmessage(('-')*130)
                
            if (self.takingquiz(m[3],self.activestudents)==True): 
                if m[4][:4]!='quiz': # unusual action during the quiz
                    self.logmessage('!! -- '+unicode(m[4].split('(')[0])+' '+unicode(m[5])+' at '+unicode(m[1].split(', ')[1]))
                    self.sc = self.stylo[3]

                elif (self.timegap(m[1],self.oldtime))>timedelta(minutes=1): # More than 1 minute for an action
                    self.sc=self.stylo[4]
                    
                else:
                    self.sc=self.stylo[2]# Answering quiz
                    
                if (self.timegap(m[1],self.oldtime))>timedelta(minutes=3): # More than 3 minutes for an action
                    self.logmessage('!! - Time gap between answers is greater than 3 minutes. '+unicode((self.timegap(m[1],self.oldtime)))+' at '+unicode(m[1].split(', ')[1]))
                    self.sc = self.stylo[5]
                
                    
                 if (m[4].split(' (')[0]=='quiz continue attempt' or m[4].split(' (')[0]=='quiz attempt'): #log the number of questions answered
                    self.quest=[ l[2] for l in self.validquizzes if l[0]==m[3] and l[1]==m[5] ]
                    self.validquizzes.remove([m[3],m[5],self.quest[0]])
                    self.validquizzes.append([m[3],m[5],self.quest[0]+1])
            
             self.xlout.append([m[3], m[1].split(', ')[1], m[5], m[4].split('(')[0], self.timegap(m[1],self.oldtime),self.sc]) #(self.takingquiz(m[3],self.activestudents))
            self.oldtime = m[1]

    def listallquizzes(self):
            # report a list of quizzes
            self.logmessage('\nList of quizzes submitted by the students')
            self.logmessage(('=')*47)
            for i in self.donequizzes:
                if i[1] not in self.listofquizzes:
                    self.listofquizzes.append(i[1])
                    self.logmessage(i[1])

    def listinactivestudents(self):
            # report a list of students who didn't take a quiz
            self.logmessage('\nList of students who did not take any quiz')
            self.logmessage(('=')*37)
            self.inactivestudents=[x for x in ([i[0] for i in self.activestudents]) if x not in ([j[0] for j in self.donequizzes])]
            for i in self.inactivestudents:
                self.logmessage(i)

    # ------------------------------------- xl writing ---------------------------------------------
    def createallstudentfiles(self):
        # Create individual xl files for the students
        for self.x in [i for i in self.activestudents if i[0] not in self.inactivestudents]:
            self.wpath=self.logfilepath+str(self.x[0])+'.xls'
            self.w = xlwt.Workbook(encoding="utf-8") # Creates new workbook for the students
            self.sh = self.w.add_sheet('Activity')
            self.w.save(self.wpath) # Create all the workbooks for that student


    def fillingindividualstudentfiles(self):           
        # Fill in individual xl files for the students
        self.name='empty'
        self.row=0
        for self.x in (sorted(self.xlout, key=itemgetter(0))):
            self.row +=1
         #   row=(open_workbook(wpath).sheet_by_index(0).nrows) use if studentsentries or xlout is not ordered by name in the loop
            for self.y in range(1,5):         
                 self.w.get_sheet(0).write(self.row, self.y-1, str(self.x[self.y]),self.x[5])
            
            self.w.get_sheet(0).col(0).width = 256*19
            self.w.get_sheet(0).col(1).width = 256*90
            self.w.get_sheet(0).col(2).width = 256*30
            self.w.get_sheet(0).col(3).width = 256*30

            if self.x[0]!=self.name or self.xlout.index(self.x)==(len(self.xlout)-1): #next student in the list or student last in the list
                self.w.save(self.wpath)
                self.name=self.x[0]
                self.row=0
                self.wpath=self.logfilepath+str(self.x[0])+'.xls'
                self.w=copy(open_workbook(self.wpath, formatting_info=True,  on_demand=True)) #Re-open existing workbook for student and makes a copy in w for editing

    def main(self):
        self.logtimestamp('Importing the data from the workbook started')
        self.openworkbook()
        self.logtimestamp('Data uploaded')

        self.logtimestamp('Uploading students list')
        self.extractstudentslist()
        self.logtimestamp('Students list uploaded')

        self.logtimestamp('Dumping data in holding table started')
        self.extractstudentsentries()
        self.logtimestamp('Data dumped ')

        self.logtimestamp('Creating a list of quizzes completed started')
        self.listquizzes()
        self.logtimestamp('List of quizzes completed dumped')

        self.logtimestamp('Data processing started')
        self.perstudentquizreport()
        self.logtimestamp('Data processing complete')

        self.logtimestamp('Finding missing students and listing quizzes')
        self.listallquizzes()
        self.listinactivestudents()
        self.logtimestamp('Found missing students and listed quizzes')

        self.logtimestamp('Writing individual student xl files')
        self.createallstudentfiles()
        self.fillingindividualstudentfiles()
        self.logtimestamp('Students files written')
  
moo=moodlecheat ('/Users/macadmin1/Desktop/moodle reports/Reports/','/Users/macadmin1/Desktop/moodle reports/')
moo.main()

Any feedback is welcome, especially about defining data structures and classes that would supplant list. I think I understand (to an extent) how to write a simple procedural script like this one in Python and I really want to move onto OOP now.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function
import sys
import os
import re
from xlrd import open_workbook
import xlwt
from xlwt import easyxf
from operator import itemgetter, attrgetter
from time import gmtime, strftime
from datetime import datetime, timedelta
from xlutils.copy import copy


if sys.version > '3':
    unicode = str


class MoodleCheat(object):
  
    def __init__(self, log_filepath, source_filepath):
        self.source_filepath = source_filepath
        self.log_filepath = log_filepath
        self.students_entries = []
        self.xl_out = []
        self.valid_quizzes = []
        self.done_quizzes = []
        self.list_of_quizzes = []
        self.inlisted_students = []
        self.listed_students = []

        self.stylo = {
                'normal': easyxf('pattern: pattern no_fill;'
                    + 'font: name Arial,'
                    + 'height 260, italic True'
                                         ), 
                'answer': easyxf('pattern: pattern solid_fill,'
                    + 'fore_colour yellow;'
                    + 'font: name Arial,'
                    + 'height 260'
                                         ), 
                'problem': easyxf('pattern: pattern solid_fill,'
                    + 'fore_colour gray50;'
                    + 'font: name Arial,'
                    + 'height 260'
                                          ),
                'too long': easyxf('pattern: pattern solid_fill, '
                    + 'fore_colour light_yellow;'
                    + 'font: name Arial,'
                    + 'height 260,'
                    + 'bold True'
                                           ), 
                'way too long': easyxf('pattern: pattern solid_fill, '
                    + 'fore_colour light_orange;'
                    + 'font: name Arial,'
                    + 'height 260'
                                           )}

    def moodledateconvert(self, date_in):
        """ function to return a moodle date in date.time/python convention """
        split_date = ' '.join(re.findall(r"[\w']+", date_in))
        return datetime.strptime(split_date, '%d %B %Y %I %M %p')
    
    def timegap(self, time_late, time_early):
        """ calculates  between the difference between 2 dates
        format e.g. <'01 September 2001, 11:50AM'> -> time/python convention"""
        return self.moodledateconvert(time_late) - self.moodledateconvert(time_early)

    def openworkbook(self):
        """ open the workbook containing the activity entries. """
        self.work_book = open_workbook(os.path.join(self.source_filepath + 'activitylog.xlsx'),
                                                                                   on_demand=True)

    def extractstudentslist(self):
        """ extract the list of all students from the 2nd worksheet.

        Keyword arguments:
        listed_students -- list of students, a flag showing taking a quiz
        """
        self.sheet = self.work_book.sheet_by_index(1)
        self.listed_students = [Student(_student) for _student in self.sheet.col_values(0)]
        self.listed_students.sort(key=attrgetter('_name'))

    def extractstudents_entries(self):
        """ Extract all entries for the students listed in [listed_students].

        Keyword arguments:
        students_entries -- all entries for the students listed
        row -- row number in the data entries worksheet
        """
        self.sheet = self.work_book.sheet_by_index(0)
        for row in range(self.sheet.nrows):
            for x in self.listed_students:
                if x._name == self.sheet.cell_value(row, 3):
                    self.students_entries.append(Entry(*self.sheet.row_values(row)))
                    break
        self.students_entries = sorted(self.students_entries, 
                                                       key=attrgetter('_name'))
        self.students_entries.reverse()
        
    def listquizzes(self):
        """ list all quizes that were completed.

        Keyword arguments:
        valid_quizzes -- list of tuples (student name, quizzes completed)
        _entry -- an entry in the [students_entries] list
        """
        for _entry in self.students_entries:
            if (                _entry._action[:10] == 'quiz close'
                        and (_entry._name, _entry._info)
                        not in self.valid_quizzes
                                ):
                self.valid_quizzes.append((_entry._name, _entry._info))

    def fliptaking(self, _searched_student):
        """ Changes the attribute for the student class to show name is taking quiz."""
        for _student in self.listed_students:
            if _student._name ==_searched_student:
                _student._taking_quiz = not (_student._taking_quiz)
                break
            
    def flipactive(self, _searched_student):
        """ Changes the flag for the student to show is taking quiz."""
        for _student in self.listed_students:
            if _student._name ==_searched_student:
                _student._active = True
                break
            
    def istaking(self, _searched_student):
        """ Changes the flag for the student to show has completed a quiz."""
        for _student in self.listed_students:
            if _student._name ==_searched_student:
                return _student._taking_quiz
        
    def logtimestamp(self, message):
        """ Writes a message with time stamp in sourcefilepath/log.txt."""
        self.message = message
        with open(os.path.join(self.log_filepath, 'log.txt'), 'a') as file:
             file.write('\n == {} at {}'.
                            format(
                             self.message,
                            strftime('%Y-%m-%d %H:%M:%S',gmtime())
                                         ))

    def logmessage(self, message):
        """ Writes a message in log.txt in sourcefilepath/log.txt."""
        self.message = message
        with open(os.path.join(self.log_filepath, 'log.txt'), 'a') as file:
            file.write('\n' + self.message)

    def p(self, name):
        """ for test, returns the value of .taking_quiz attribute  \
             for _searched_student.
             """
        for x in self.listed_students:
            if x._name == n._name:
                print (x._name, x._taking_quiz)
         
    def perstudentquizreport(self):
        """ logs quiz information for each student and xl ouput table.

         Keyword arguments:
        xl_out -- list of tuples (student name, quizzes completed)
        _entry -- an entry in the [students_entries] list
        """
        self.logmessage('\n\nReport on quizzes')
        self.logmessage('=================\n')
        self.old_time = str('25 April 2014, 11:41 AM')
        # Main loop
        for _record in self.students_entries:
            # log messages and set flags for each entry when in quiz
            self.sc = self.stylo['normal']
            if ((            _record._action.split(' (')[0] == 'quiz continue attempt' \
                     or     _record._action.split(' (')[0] == 'quiz attempt')) \
                     and (_record._name, _record._info) not in self.done_quizzes \
                     and self.istaking(_record._name) == False \
                     and ((_record._name, _record._info) in self.valid_quizzes
                          ):
                self.fliptaking(_record._name)
                self.logmessage( '{} has made first answer in the quiz {} at {} '
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))

            if (        (_record._action.split(' (')[0] == 'quiz continue attempt' \
                     or _record._action.split(' (')[0] == 'quiz attempt'
                        )) \
                     and (_record._name, _record._info) in self.done_quizzes \
                     and self.istaking(_record._name) == False \
                     and ((_record._name, _record._info) in self.valid_quizzes
                          ):
                self.fliptaking(_record._name)
                self.logmessage('{} retook the quiz {} at {} -This is an issue' +
                    ' if the quiz is not a practice quiz'
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))
            elif _record._action[:10] == 'quiz close':
                self.done_quizzes.append((_record._name, _record._info))
                self.fliptaking(_record._name)
                self.flipactive(_record._name)
                self.logmessage('{} has finished the quiz {} at {}'
                                        .format(
                                        _record._name,
                                        _record._info,
                                        _record._time.split(',')[1]
                                                    ))
                self.logmessage(('-') * 130)
                        
            # unusual actions during a valid quiz
            if self.istaking(_record._name) == True:
                if _record._action[:4] != 'quiz':
                    self.logmessage('!! -- {}  {} at {}'
                                            .format(
                                            _record._action.split('('),
                                            _record._info,
                                            _record._time.split(',')[1]
                                                        ))
                    self.sc = self.stylo['problem']                   
                elif ((self.timegap(_record._time, self.old_time))
                                > timedelta(minutes=1)):
                            self.sc = self.stylo['too long']
                else:
                    self.sc = self.stylo['answer']
                    
                if (self.timegap(_record._time, self.old_time)) > timedelta(minutes=3): 
                        self.logmessage('!! - Time gap between answers is greater'  +
                                                    ' than 3 minutes. ({}) at {}'
                                                    .format(
                                                    self.timegap(_record._time, self.old_time),
                                                    _record._time.split(',')[1]
                                                                ))
                        self.sc = self.stylo['way too long']
                
            self.xl_out.append([_record._name,
                                            _record._time.split(', ')[1],
                                            _record._info,
                                            _record._action.split('(')[0],
                                            self.timegap(_record._time, self.old_time),
                                            self.sc] 
                                           )
            self.old_time = _record._time

    def listallquizzes(self):
        """ Creates a list of all quizzes taken by students."""
        self.logmessage('\nList of quizzes submitted by the students')
        self.logmessage(('=') * 47)
        for i in self.done_quizzes:
            if i[1] not in self.list_of_quizzes:
                self.list_of_quizzes.append(i[1])
                self.logmessage(i[1])

    def listinlisted_students(self):
        """ Write the names of students who didn't take a quiz in log.txt. """
        self.logmessage('\nList of students who did not take any quiz')
        self.logmessage(('=') * 37)
        for x in self.listed_students:
            if  x._active == False:
                self.logmessage(x._name)

    def createallstudentfiles(self):
        """ Creates an excel file for each student. """
        for self.x in self.listed_students:
            self.wpath = os.path.join((self.log_filepath),str(self.x._name) +'.xls')
            # Creates a workbook w in memory for a student
            self.w = xlwt.Workbook(encoding="utf-8")
            self.sheet = self.w.add_sheet('Activity')
            # Writes the workbook w at wpath file for that student
            self.w.save(self.wpath)

    def fillingindividualstudentfiles(self):
        """ Fill in each workbook created for a student with xl_out data. """
        self.name = 'empty'
        self.row = 0
        for self.x in self.xl_out:
            self.row += 1
            for self.y in range(1, 5):
                self.w.get_sheet(0).write(
                        self.row, self.y - 1, 
                        str(self.x[self.y]), self.x[5]
                                                        )
            self.w.get_sheet(0).col(0).width = 256 * 19
            self.w.get_sheet(0).col(1).width = 256 * 90
            self.w.get_sheet(0).col(2).width = 256 * 30
            self.w.get_sheet(0).col(3).width = 256 * 30

            # next student in the list or last student  in the list?
            if(self.x[0] != self.name \
                            or self.xl_out.index(self.x) == (len(self.xl_out) - 1)):
                self.w.save(self.wpath)
                self.name = self.x[0]
                self.row = 0
                self.wpath = os.path.join((self.log_filepath),
                                                          str(self.x[0]) +'.xls'
                                                        )
                # re-open existing workbook for student
                self.w = copy(open_workbook(self.wpath,  
                                                                formatting_info=True,
                                                                on_demand=True 
                                                                ))

    def main(self):
        self.logtimestamp('Importing the data from the workbook started')
        self.openworkbook()
        self.logtimestamp('Data uploaded')

        self.logtimestamp('Uploading students list')
        self.extractstudentslist()
        self.logtimestamp('Students list uploaded')

        self.logtimestamp('Dumping data in holding table started')
        self.extractstudents_entries()
        self.logtimestamp('Data dumped ')

        self.logtimestamp('Creating a list of quizzes completed started')
        self.listquizzes()
        self.logtimestamp('List of quizzes completed dumped')

        self.logtimestamp('Data processing started')
        self.perstudentquizreport()
        self.logtimestamp('Data processing complete')

        self.logtimestamp('Finding missing students and listing quizzes')
        self.listallquizzes()
        self.listinlisted_students()
        self.logtimestamp('Found missing students and listed quizzes')

        self.logtimestamp('Writing individual student xl files')
        self.createallstudentfiles()
        self.fillingindividualstudentfiles()
        self.logtimestamp('Students files written')


class Student(MoodleCheat):
    """  class describing individual students with name and flags. """
    def __init__(self, name, active=False, taking_quiz=False):
        self._name = name
        self._active = active
        self._taking_quiz = taking_quiz
##   For further optimisations
##    def is_active(self):
##        return self._active
##
##    def is_taking_quiz(self):
##        return self._taking_quiz


class Entry(MoodleCheat):
    """ class describing master data set in source excel file. """
    def __init__(self, module, time, ip_addr, name, action, info):
        self._module = module
        self._time = time
        self._ip_addr = ip_addr
        self._name = name
        self._action = action
        self._info = info


## For optimisation                 
##if __name__ == '__main__':
##   import cProfile
moo = MoodleCheat('/Users/macadmin1/Desktop/moodle reports/Reports/',
            '/Users/macadmin1/Desktop/moodle reports/')
moo.main()
edited tags
Link
200_success
  • 144.2k
  • 22
  • 188
  • 473
deleted 22 characters in body; edited tags; edited title
Source Link
Jamal
  • 34.9k
  • 13
  • 133
  • 237
Loading
Source Link
Loading