~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/tutorial/service.py

Move the remaining images to the new framework, in the new ivle.webapp.core
plugin.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# IVLE - Informatics Virtual Learning Environment
2
 
# Copyright (C) 2007-2009 The University of Melbourne
 
2
# Copyright (C) 2007-2008 The University of Melbourne
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
17
 
18
 
# Author: Matt Giuca, Nick Chadwick
19
 
 
20
 
'''AJAX backend for the tutorial application.'''
21
 
 
 
18
# Module: TutorialService
 
19
# Author: Matt Giuca
 
20
# Date:   25/1/2008
 
21
 
 
22
# Provides the AJAX backend for the tutorial application.
 
23
# This allows several actions to be performed on the code the student has
 
24
# typed into one of the exercise boxes.
 
25
 
 
26
# Calling syntax
 
27
# Path must be empty.
 
28
# The arguments determine what is to be done on this file.
 
29
 
 
30
# "action". One of the tutorialservice actions.
 
31
# "exercise" - The path to a exercise file (including the .xml extension),
 
32
#              relative to the subjects base directory.
 
33
# action "save" or "test" (POST only):
 
34
#   "code" - Full text of the student's code being submitted.
 
35
# action "getattempts": No arguments. Returns a list of
 
36
#   {'date': 'formatted_date', 'complete': bool} dicts.
 
37
# action "getattempt":
 
38
#   "date" - Formatted date. Gets most recent attempt before (and including)
 
39
#   that date.
 
40
#   Returns JSON string containing code, or null.
 
41
 
 
42
# Returns a JSON response string indicating the results.
 
43
 
 
44
import os
 
45
import time
22
46
import datetime
23
47
 
24
 
import genshi
25
 
from storm.locals import Store
26
 
 
27
 
import ivle.database
28
 
from ivle.database import Exercise, ExerciseAttempt, ExerciseSave, Worksheet, \
29
 
                          Offering, Subject, Semester, User, WorksheetExercise
30
 
import ivle.worksheet.utils
31
 
from ivle.webapp.base.rest import (JSONRESTView, write_operation,
32
 
                                   require_permission)
33
 
from ivle.webapp.errors import NotFound
34
 
 
 
48
import cjson
 
49
 
 
50
from ivle import util
 
51
from ivle import console
 
52
import ivle.database
 
53
import ivle.worksheet
 
54
import ivle.conf
 
55
import ivle.webapp.tutorial.test # XXX: Really .test, not real test.
 
56
 
 
57
from ivle.webapp.base.rest import JSONRESTView
 
58
import ivle.database
 
59
from ivle.webapp.base.rest import named_operation
 
60
 
 
61
# If True, getattempts or getattempt will allow browsing of inactive/disabled
 
62
# attempts. If False, will not allow this.
 
63
HISTORY_ALLOW_INACTIVE = False
35
64
 
36
65
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
37
66
 
38
67
 
39
68
class AttemptsRESTView(JSONRESTView):
40
 
    '''REST view of a user's attempts at an exercise.'''
41
 
 
42
 
    @require_permission('edit')
 
69
    '''
 
70
    Class to return a list of attempts for a given exercise, or add an Attempt
 
71
    '''
43
72
    def GET(self, req):
44
73
        """Handles a GET Attempts action."""
45
 
        attempts = req.store.find(ExerciseAttempt, 
46
 
                ExerciseAttempt.ws_ex_id == self.context.worksheet_exercise.id,
47
 
                ExerciseAttempt.user_id == self.context.user.id)
 
74
        exercise = ivle.database.Exercise.get_by_name(req.store, 
 
75
                                                        self.exercise)
 
76
        user = ivle.database.User.get_by_login(req.store, self.username)
 
77
 
 
78
        attempts = ivle.worksheet.get_exercise_attempts(req.store, user,
 
79
                            exercise, allow_inactive=HISTORY_ALLOW_INACTIVE)
48
80
        # attempts is a list of ExerciseAttempt objects. Convert to dictionaries
49
81
        time_fmt = lambda dt: datetime.datetime.strftime(dt, TIMESTAMP_FORMAT)
50
82
        attempts = [{'date': time_fmt(a.date), 'complete': a.complete}
51
83
                for a in attempts]
52
 
 
 
84
                
53
85
        return attempts
54
 
 
55
 
 
56
 
    @require_permission('edit')
 
86
        
57
87
    def PUT(self, req, data):
58
 
        """ Tests the given submission """
59
 
        test_results = ivle.worksheet.utils.test_exercise_submission(
60
 
            req.config, req.user, self.context.worksheet_exercise.exercise,
61
 
            data['code'])
 
88
        ''' Tests the given submission '''
 
89
        exercisefile = util.open_exercise_file(self.exercise)
 
90
        if exercisefile is None:
 
91
            req.throw_error(req.HTTP_NOT_FOUND,
 
92
                "The exercise was not found.")
 
93
 
 
94
        # Start a console to run the tests on
 
95
        jail_path = os.path.join(ivle.conf.jail_base, req.user.login)
 
96
        working_dir = os.path.join("/home", req.user.login)
 
97
        cons = console.Console(req.user.unixid, jail_path, working_dir)
 
98
 
 
99
        # Parse the file into a exercise object using the test suite
 
100
        exercise_obj = ivle.webapp.tutorial.test.parse_exercise_file(
 
101
                                                            exercisefile, cons)
 
102
        exercisefile.close()
 
103
 
 
104
        # Run the test cases. Get the result back as a JSONable object.
 
105
        # Return it.
 
106
        test_results = exercise_obj.run_tests(data['code'])
 
107
 
 
108
        # Close the console
 
109
        cons.close()
 
110
 
 
111
        # Get the Exercise from the database
 
112
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
62
113
 
63
114
        attempt = ivle.database.ExerciseAttempt(user=req.user,
64
 
            worksheet_exercise = self.context.worksheet_exercise,
65
 
            date = datetime.datetime.now(),
66
 
            complete = test_results['passed'],
67
 
            text = unicode(data['code'])
68
 
        )
 
115
                                                exercise=exercise,
 
116
                                                date=datetime.datetime.now(),
 
117
                                                complete=test_results['passed'],
 
118
                                                # XXX
 
119
                                                text=unicode(data['code']))
69
120
 
70
121
        req.store.add(attempt)
71
 
 
 
122
        req.store.commit()
72
123
        # Query the DB to get an updated score on whether or not this problem
73
124
        # has EVER been completed (may be different from "passed", if it has
74
125
        # been completed before), and the total number of attempts.
75
 
        completed, attempts = ivle.worksheet.utils.get_exercise_status(
76
 
                req.store, req.user, self.context.worksheet_exercise)
 
126
        completed, attempts = ivle.worksheet.get_exercise_status(req.store,
 
127
            req.user, exercise)
77
128
        test_results["completed"] = completed
78
129
        test_results["attempts"] = attempts
79
130
 
80
131
        return test_results
81
 
 
 
132
        
82
133
 
83
134
class AttemptRESTView(JSONRESTView):
84
 
    '''REST view of an exercise attempt.'''
85
 
 
86
 
    @require_permission('view')
 
135
    '''
 
136
    View used to extract the data of a specified attempt
 
137
    '''
 
138
    
87
139
    def GET(self, req):
88
 
        return {'code': self.context.text}
89
 
 
90
 
 
91
 
class WorksheetExerciseRESTView(JSONRESTView):
92
 
    '''REST view of a worksheet exercise.'''
93
 
 
94
 
    @write_operation('view')
 
140
        # Get an actual date object, rather than a string
 
141
        date = datetime.datetime.strptime(self.date, TIMESTAMP_FORMAT)
 
142
        
 
143
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
144
        attempt = ivle.worksheet.get_exercise_attempt(req.store, req.user,
 
145
            exercise, as_of=date, allow_inactive=HISTORY_ALLOW_INACTIVE)
 
146
        if attempt is not None:
 
147
            attempt = attempt.text
 
148
        # attempt may be None; will write "null"
 
149
        return {'code': attempt}
 
150
        
 
151
class ExerciseRESTView(JSONRESTView):
 
152
    '''
 
153
    Handles a save action. This saves the user's code without executing it.
 
154
    '''
 
155
    @named_operation
95
156
    def save(self, req, text):
96
 
        # Find the appropriate WorksheetExercise to save to. If its not found,
97
 
        # the user is submitting against a non-existant worksheet/exercise
98
 
 
99
 
        old_save = req.store.find(ExerciseSave,
100
 
            ExerciseSave.ws_ex_id == self.context.id,
101
 
            ExerciseSave.user == req.user).one()
102
 
        
103
 
        #Overwrite the old, or create a new if there isn't one
104
 
        if old_save is None:
105
 
            new_save = ExerciseSave()
106
 
            req.store.add(new_save)
107
 
        else:
108
 
            new_save = old_save
109
 
        
110
 
        new_save.worksheet_exercise = self.context
111
 
        new_save.user = req.user
112
 
        new_save.text = unicode(text)
113
 
        new_save.date = datetime.datetime.now()
114
 
 
 
157
        # Need to open JUST so we know this is a real exercise.
 
158
        # (This avoids users submitting code for bogus exercises).
 
159
        exercisefile = util.open_exercise_file(self.exercise)
 
160
        if exercisefile is None:
 
161
            req.throw_error(req.HTTP_NOT_FOUND,
 
162
                "The exercise was not found.")
 
163
        exercisefile.close()
 
164
 
 
165
        exercise = ivle.database.Exercise.get_by_name(req.store, self.exercise)
 
166
        ivle.worksheet.save_exercise(req.store, req.user, exercise,
 
167
                                     unicode(text), datetime.datetime.now())
 
168
        req.store.commit()
115
169
        return {"result": "ok"}
116
 
 
117
 
 
118
 
class WorksheetsRESTView(JSONRESTView):
119
 
    """View used to update and create Worksheets."""
120
 
 
121
 
    @write_operation('edit_worksheets')
122
 
    def move_up(self, req, worksheetid):
123
 
        """Takes a list of worksheet-seq_no pairs and updates their 
124
 
        corresponding Worksheet objects to match."""
125
 
        
126
 
        worksheet_below = req.store.find(Worksheet,
127
 
            Worksheet.offering_id == self.context.id,
128
 
            Worksheet.identifier == unicode(worksheetid)).one()
129
 
        if worksheet_below is None:
130
 
            raise NotFound('worksheet_below')
131
 
        worksheet_above = req.store.find(Worksheet,
132
 
            Worksheet.offering_id == self.context.id,
133
 
            Worksheet.seq_no == (worksheet_below.seq_no - 1)).one()
134
 
        if worksheet_above is None:
135
 
            raise NotFound('worksheet_above')
136
 
 
137
 
        worksheet_below.seq_no = worksheet_below.seq_no - 1
138
 
        worksheet_above.seq_no = worksheet_above.seq_no + 1
139
 
        
140
 
        return {'result': 'ok'}
141
 
 
142
 
    @write_operation('edit_worksheets')
143
 
    def move_down(self, req, worksheetid):
144
 
        """Takes a list of worksheet-seq_no pairs and updates their 
145
 
        corresponding Worksheet objects to match."""
146
 
        
147
 
        worksheet_above = req.store.find(Worksheet,
148
 
            Worksheet.offering_id == self.context.id,
149
 
            Worksheet.identifier == unicode(worksheetid)).one()
150
 
        if worksheet_above is None:
151
 
            raise NotFound('worksheet_below')
152
 
        worksheet_below = req.store.find(Worksheet,
153
 
            Worksheet.offering_id == self.context.id,
154
 
            Worksheet.seq_no == (worksheet_above.seq_no + 1)).one()
155
 
        if worksheet_below is None:
156
 
            raise NotFound('worksheet_above')
157
 
 
158
 
        worksheet_below.seq_no = worksheet_below.seq_no - 1
159
 
        worksheet_above.seq_no = worksheet_above.seq_no + 1
160
 
        
161
 
        return {'result': 'ok'}