delete old file when attachment changes
[edumed.git] / wtem / models.py
1 # -*- coding: utf-8 -*-
2 import random
3 import string
4 import os
5 import json
6
7 from django.db import models
8 from django.contrib.auth.models import User
9 from django.core.exceptions import ValidationError
10 from django.utils.translation import ugettext as _
11 from jsonfield import JSONField
12
13 from contact.models import Contact
14
15 f = file(os.path.dirname(__file__) + '/fixtures/exercises.json')
16 exercises = json.loads(f.read())
17 f.close()
18
19 DEBUG_KEY = '12345'
20
21
22 class Submission(models.Model):
23     contact = models.ForeignKey(Contact, null=True)
24     key = models.CharField(max_length=30, unique=True)
25     first_name = models.CharField(max_length=100)
26     last_name = models.CharField(max_length=100)
27     email = models.EmailField(max_length=100, unique=True)
28     answers = models.CharField(max_length=65536, null=True, blank=True)
29     key_sent = models.BooleanField(default=False)
30     marks = JSONField(default={})
31     examiners = models.ManyToManyField(User, null=True, blank=True)
32     end_time = models.CharField(max_length=5, null=True, blank=True)
33
34     def __unicode__(self):
35         return ', '.join((self.last_name, self.first_name, self.email))
36
37     @classmethod
38     def generate_key(cls):
39         key = ''
40         while not key or key in [record['key'] for record in cls.objects.values('key')]:
41             key = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits)
42                           for i in range(30))
43         return key
44
45     @classmethod
46     def create(cls, first_name, last_name, email, key=None, contact=None):
47         submission = cls(
48             contact=contact,
49             key=key if key else Submission.generate_key(),
50             first_name=first_name,
51             last_name=last_name,
52             email=email
53         )
54
55         submission.save()
56         return submission
57
58     def get_mark(self, user_id, exercise_id):
59         mark = None
60         user_id = str(user_id)
61         exercise_id = str(exercise_id)
62         if self.marks and user_id in self.marks:
63             mark = self.marks[user_id].get(exercise_id, None)
64         return mark
65
66     def set_mark(self, user_id, exercise_id, mark):
67         user_id = str(user_id)
68         exercise_id = str(exercise_id)
69         if not self.marks:
70             self.marks = dict()
71         
72         self.marks.setdefault(user_id, {})[exercise_id] = mark
73         if mark == 'None':
74             del self.marks[user_id][exercise_id]
75
76     def get_exercise_marks_by_examiner(self, exercise_id):
77         marks = dict()
78         for examiner_id, examiner_marks in self.marks.items():
79             mark = examiner_marks.get(exercise_id, None)
80             if mark is not None:
81                 marks[examiner_id] = mark
82         return marks
83
84     def get_final_exercise_mark(self, exercise_id):
85         # exercise = exercises[int(exercise_id)-1]
86         exercise = [e for e in exercises if str(e['id']) == str(exercise_id)][0]
87         if exercise_checked_manually(exercise):
88             marks_by_examiner = self.get_exercise_marks_by_examiner(exercise_id)
89             if len(marks_by_examiner):
90                 return sum(map(float, marks_by_examiner.values())) / float(len(marks_by_examiner))
91             else:
92                 return None
93         else:
94             if not self.answers:
95                 return None
96             answer = json.loads(self.answers)[exercise_id]['closed_part']
97             t = exercise['type']
98             if t == 'edumed_uporzadkuj':
99                 return exercise['points'] if map(int, answer) == exercise['answer'] else 0
100             if t == 'edumed_przyporzadkuj':
101                 toret = 0
102                 for bucket_id, items in answer.items():
103                     for item_id in items:
104                         is_corect = False
105                         if exercise.get('answer_mode', None) == 'possible_buckets_for_item':
106                             is_correct = int(bucket_id) in exercise['answer'].get(item_id)
107                         else:
108                             is_correct = int(item_id) in exercise['answer'].get(bucket_id, [])
109                         if is_correct:
110                             toret += exercise['points_per_hit']
111                 return toret
112             if t == 'edumed_wybor':
113                 if len(exercise['answer']) == 1:
114                     if len(answer) and int(answer[0]) == exercise['answer'][0]:
115                         return exercise['points']
116                     else:
117                         return 0
118                 else:
119                     toret = 0
120                     if exercise.get('answer_mode', None) == 'all_or_nothing':
121                         toret = exercise['points'] if map(int, answer) == exercise['answer'] else 0
122                     else:
123                         for answer_id in map(int, answer):
124                             if answer_id in exercise['answer']:
125                                 toret += exercise['points_per_hit']
126                     return toret
127             if t == 'edumed_prawdafalsz':
128                 toret = 0
129                 for idx, statement in enumerate(exercise['statements']):
130                     if answer[idx] == 'true':
131                         given = True
132                     elif answer[idx] == 'false':
133                         given = False
134                     else:
135                         given = None
136                     if given == statement[1]:
137                         toret += exercise['points_per_hit']
138                 return toret
139             raise NotImplementedError
140
141     @property
142     def final_result(self):
143         final = 0
144         # for exercise_id in map(str,range(1, len(exercises) + 1)):
145         for exercise_id in [str(x['id']) for x in exercises]:
146             mark = self.get_final_exercise_mark(exercise_id)
147             if mark is not None:
148                 final += mark
149         return final
150
151     @property
152     def final_result_as_string(self):
153         return ('%.2f' % self.final_result).rstrip('0').rstrip('.')
154
155
156 class Attachment(models.Model):
157     submission = models.ForeignKey(Submission)
158     exercise_id = models.IntegerField()
159     tag = models.CharField(max_length=128, null=True, blank=True)
160     file = models.FileField(upload_to='wtem/attachment')
161
162
163 class Assignment(models.Model):
164     user = models.ForeignKey(User, unique=True)
165     exercises = JSONField()
166
167     def clean(self):
168         if not isinstance(self.exercises, list):
169             raise ValidationError(_('Assigned exercises must be declared in a list format'))
170         # for exercise in self.exercises:
171         #     if not isinstance(exercise, int) or exercise < 1:
172         #         raise ValidationError(_('Invalid exercise id: %s' % exercise))
173
174     def __unicode__(self):
175         return self.user.username + ': ' + ','.join(map(str, self.exercises))
176
177
178 def exercise_checked_manually(exercise):
179     return (exercise['type'] in ('open', 'file_upload')) or 'open_part' in exercise