Switch to unified view

a/OSSEval/analysis/models.py b/OSSEval/analysis/models.py
1
from django.db import models
1
from datetime import datetime
2
from datetime import datetime
3
from random import randrange
4
from pygal import Radar, Bar
5
from pygal.style import NeonStyle, DarkSolarizedStyle, LightSolarizedStyle, LightStyle, CleanStyle, RedBlueStyle, DarkColorizedStyle, LightColorizedStyle, TurquoiseStyle, LightGreenStyle, DarkGreenStyle, DarkGreenBlueStyle, BlueStyle
6
from entity.models import Entity
7
from OSSEval.utils import xmlMinidom
8
from xml.dom.minidom import Node
2
9
3
from django.db import models
10
class Methodology(models.Model):
11
    name = models.CharField(max_length=200)
12
    description = models.CharField(max_length=2000,null=True,blank=True)
13
    documentation = models.TextField(null=True,blank=True)
14
    active = models.BooleanField(default=True)
15
    # The entity type the methodology helps you assess
16
    entity = models.ForeignKey(Entity)
17
    
18
    def __str__(self):
19
        return self.name
20
    
21
    def from_xml(self, xmldoc, insert = True):
22
        if not insert:
23
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
24
        self.name = xmlMinidom.getStringAttribute(xmldoc, 'Name')
25
        self.description = xmlMinidom.getString(xmldoc, 'Description')
26
        self.documentation = xmlMinidom.getString(xmldoc, 'Documentation')
27
        self.active = xmlMinidom.getStringAttribute(xmldoc, 'Active')
28
        e = Entity()
29
        xml_entity = xmldoc.getElementsByTagName('Entity')[0]
30
        e.from_xml(xml_entity, insert)
31
        self.entity = e
32
        # I save so I get the ID (in case insert == True)
33
        self.save()
34
        
35
    def to_xml(self):
36
        str_xml = "<Description><![CDATA[" + self.description + "]]></Description>"
37
        str_xml += "<Documentation><![CDATA[" + self.documentation + "]]></Documentation>"
38
        str_xml += self.entity.to_xml()
39
        return '<Methodology Id="' + str(self.id) + '" Name="' + self.name + '" Active="' + str(self.active) + '">' + str_xml + "</Methodology>"
4
40
5
from methodology.models import WeightScenario
41
class HasPages(models.Model):
42
    bar_chart = models.TextField(blank=True)
43
    radar_chart = models.TextField(blank=True)
44
    def create_graphs(self, instances):
45
        # I draw the graphs for the report
46
        #styles = [NeonStyle, DarkSolarizedStyle, LightSolarizedStyle, LightStyle, CleanStyle, RedBlueStyle, DarkColorizedStyle, LightColorizedStyle, TurquoiseStyle, LightGreenStyle, DarkGreenStyle, DarkGreenBlueStyle, BlueStyle]
47
        #style=styles[randrange(len(styles))]
48
        style=LightColorizedStyle
49
        bar_chart = Bar(width=300, height=400, explicit_size=True, rounded_bars=5, disable_xml_declaration=True, style=style)
50
        radar_chart = Radar(width=400, height=400, explicit_size=True, legend_at_bottom=True, disable_xml_declaration=True, style=style)
51
        if self.__class__.__name__ == "Page":
52
            radar_chart.title = self.name
53
        else:
54
            radar_chart.title = 'Summary graph'
55
        radar_chart.x_labels = []
56
        instance_scores = {}
57
        for instance in instances:
58
            instance_scores[instance.id] = []
59
        for page in self.page_set.all():
60
            radar_chart.x_labels.append(page.name)
61
            for instance in instances:
62
                try:
63
                    ps = PageScore.objects.get(page=page, instance=instance)
64
                    instance_scores[instance.id].append(ps.score)
65
                except:
66
                    instance_scores[instance.id].append(0)
67
        for instance in instances:
68
            radar_chart.add(instance.name, instance_scores[instance.id])
69
            bar_chart.add(instance.name, sum(instance_scores[instance.id]))
70
        self.radar_chart = radar_chart.render()
71
        self.bar_chart = bar_chart.render()
72
        self.save()
73
    class Meta:
74
        abstract = True
75
76
class MethodologyVersion(HasPages):
77
    number = models.IntegerField()
78
    created = models.DateField() 
79
    current = models.BooleanField(default=False)
80
    methodology = models.ForeignKey(Methodology)
81
    
82
    def __str__(self):
83
        return self.methodology.name + " - " + str(self.number) + (" (Active version)" if self.current else "")
84
85
    def from_xml(self, xmldoc, insert = True):
86
        if not insert:
87
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
88
        self.number = xmldoc.attributes["Number"].firstChild.data
89
        self.created = xmlMinidom.getStringAttribute(xmldoc, 'Created')
90
        self.current = xmlMinidom.getStringAttribute(xmldoc, 'Current')
91
        m = Methodology()
92
        xml_methodology = xmldoc.getElementsByTagName('Methodology')[0]
93
        m.from_xml(xml_methodology, insert)
94
        self.methodology = m
95
        # I save so I get the ID (if insert == True)
96
        self.save()
97
        # Pages
98
        for xml_child in xmldoc.childNodes:
99
            # Some nodes are text nodes (e.g. u'\n     ') I need to look just at ELEMENT_NODE
100
            if xml_child.nodeType == Node.ELEMENT_NODE and xml_child.tagName == 'Pages':
101
                xml_pages = xml_child
102
                break
103
        for xml_page in xml_pages.childNodes:
104
            if xml_page.nodeType == Node.ELEMENT_NODE and xml_page.tagName == 'Page':
105
                p = Page()
106
                p.from_xml(xml_page, self, None, insert)
107
        #WeightScenarios
108
        xml_weight_scenarios = xmldoc.getElementsByTagName('WeightScenarios')
109
        for xml_weight_scenario in xml_weight_scenarios:
110
            ws = WeightScenario()
111
            ws.from_xml(xml_weight_scenario, self, insert)
112
        
113
    def to_xml(self):
114
        str_xml = self.methodology.to_xml()
115
        str_xml += "<Pages>"
116
        for page in self.page_set.all():
117
            str_xml += page.to_xml()
118
        str_xml += "</Pages>"
119
        str_xml += "<WeightScenarios>"
120
        for weight_scenario in self.weightscenario_set.all():
121
            str_xml += weight_scenario.to_xml()
122
        str_xml += "</WeightScenarios>"
123
             
124
        return '<MethodologyVersion Id="' + str(self.id) + '" Number="' + str(self.number) + '" Created="' + str(self.created) + '" Current="' + str(self.current) + '">' + str_xml + "</MethodologyVersion>"
125
126
class Page(HasPages):
127
    name = models.CharField(max_length=200)
128
    order = models.IntegerField(null=False,blank=False)
129
    parent = models.ForeignKey('self',null=True,blank=True)
130
    methodology_version = models.ForeignKey(MethodologyVersion,null=True,blank=True)
131
    def questions(self):
132
        q = list(self.question_set.all())
133
        for p in self.page_set.all():
134
            q += p.questions()
135
        return q
136
    
137
    def __str__(self):
138
        return self.name
139
    
140
    def from_xml(self, xmldoc, methodology_version, parent_page, insert = True):
141
        if not insert:
142
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
143
        self.name = xmlMinidom.getStringAttribute(xmldoc, 'Name')
144
        self.order = xmldoc.attributes["Order"].firstChild.data
145
        if methodology_version is not None:
146
            self.methodology_version = methodology_version
147
        if parent_page is not None:
148
            self.parent = parent_page
149
        self.save()
150
        # Pages
151
        for xml_child in xmldoc.childNodes:
152
            # Some nodes are text nodes (e.g. u'\n     ') I need to look just at ELEMENT_NODE
153
            if xml_child.nodeType == Node.ELEMENT_NODE and xml_child.tagName == 'Pages':
154
                xml_pages = xml_child
155
                break
156
        for xml_page in xml_pages.childNodes:
157
            if xml_page.nodeType == Node.ELEMENT_NODE and xml_page.tagName == 'Page':
158
                p = Page()
159
                p.from_xml(xml_page, None, self, insert)
160
        # Questions
161
        for xml_child in xmldoc.childNodes:
162
            # Some nodes are text nodes (e.g. u'\n     ') I need to look just at ELEMENT_NODE
163
            if xml_child.nodeType == Node.ELEMENT_NODE and xml_child.tagName == 'Questions':
164
                xml_questions = xml_child
165
                break
166
        for xml_question in xml_questions.childNodes:
167
            if xml_question.nodeType == Node.ELEMENT_NODE and xml_question.tagName == 'Question':
168
                q = Question()
169
                q.from_xml(xml_question, self, insert)
170
        
171
    def to_xml(self):
172
        str_xml = "<Pages>"
173
        for page in self.page_set.all():
174
            str_xml += page.to_xml()
175
        str_xml += "</Pages>"
176
        str_xml += "<Questions>"
177
        for question in self.question_set.all():
178
            str_xml += question.to_xml()
179
        str_xml += "</Questions>"
180
        return '<Page Id="' + str(self.id) + '" Name="' + self.name + '" Order="' + str(self.order) + '">' + str_xml + "</Page>"
181
182
    def calculate_scores(self, instance, weight_scenario, instances):
183
#         Let's reset the score to 0
184
        try:
185
            ps = PageScore.objects.get(page=self, instance=instance)
186
        except:
187
            ps = PageScore(page=self, instance=instance, score=0)
188
        ps.score = 0
189
#         and calculate the score for child pages and add it to current page's score
190
        for page in self.page_set.all():
191
            ps.score += page.calculate_scores(instance, weight_scenario, instances)
192
#         Loop on questions
193
        for question in self.questions():
194
            weight = weight_scenario.question_weight(question.id)
195
            try:
196
                answer = Answer.objects.get(question=question, instance=instance)
197
                answer.score = weight * (answer.value_integer-1) 
198
                answer.save()
199
                ps.score += answer.score
200
            except Exception as ex:
201
                #I haven't found an answer, nothing to add
202
                print ex.message
203
                pass
204
        ps.save()
205
        self.create_graphs(instances)
206
        return ps.score
207
        
208
    class Meta:
209
        ordering = ['order']
210
211
class QuestionType(models.Model):
212
    '''
213
    For future use; at the moment a question is just a multiple choice
214
    '''
215
    name = models.CharField(max_length=200)
216
    
217
    def from_xml(self, xmldoc, insert = True):
218
        if not insert:
219
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
220
        self.name = xmlMinidom.getStringAttribute(xmldoc, 'Name')
221
    
222
    def to_xml(self):
223
        return '<QuestionType Id="' + str(self.id) + '" Name="' + self.name + '"/>'
224
    
225
class Question(models.Model):
226
    page = models.ForeignKey(Page)
227
    text = models.CharField(max_length=200)
228
    order = models.IntegerField()
229
    eval_description = models.TextField(null=True,blank=True)
230
    eval_value = models.TextField(null=True,blank=True)
231
    question_type =  models.ForeignKey(QuestionType)
232
    
233
    def __str__(self):
234
        return self.page.name + " - " + self.text
235
    
236
    def from_xml(self, xmldoc, page, insert = True):
237
        if not insert:
238
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
239
        self.text = xmlMinidom.getStringAttribute(xmldoc, 'Text')
240
        self.order = xmlMinidom.getNaturalAttribute(xmldoc, 'Order')
241
        #Do so that QuestionType default is 1
242
        qt = QuestionType.objects.get(pk=1)
243
        self.page = page
244
        self.question_type = qt
245
        self.eval_description = xmlMinidom.getString(xmldoc, 'EvalDescription')
246
        self.eval_value = xmlMinidom.getString(xmldoc, 'EvalValue')
247
        self.save()
248
        # Queries
249
        xml_queries = xmldoc.getElementsByTagName('Query')
250
        for xml_query in xml_queries:
251
            q = Query()
252
            q.from_xml(xml_query, self, insert)
253
        # Choices
254
        xml_choices = xmldoc.getElementsByTagName('Choice')
255
        for xml_choice in xml_choices:
256
            c = Choice()
257
            c.from_xml(xml_choice, self, insert)
258
        
259
    def to_xml(self):
260
        str_xml = "<EvalDescription><![CDATA[" + ("" if self.eval_description is None else self.eval_description) + "]]></EvalDescription>"
261
        str_xml += "<EvalValue><![CDATA[" + ("" if self.eval_value is None else self.eval_value) + "]]></EvalValue>"
262
#         We do not use question_type at the moment as we have just one type
263
#         str_xml += self.question_type.to_xml()
264
        str_xml += "<Queries>"
265
        for query in self.query_set.all():
266
            str_xml += query.to_xml()
267
        str_xml += "</Queries>"
268
        str_xml += "<Choices>"
269
        for choice in self.choice_set.all():
270
            str_xml += choice.to_xml()
271
        str_xml += "</Choices>"
272
        return '<Question Id="' + str(self.id) + '" Text="' + self.text + '" Order="' + str(self.order) + '">' + str_xml + '</Question>'
273
274
    class Meta:
275
        ordering = ['order']
276
277
class Query(models.Model):
278
    '''
279
    Queries against web search engines used to automate the answer to a question and/or as a hint to the reviewer 
280
    '''
281
    question = models.ForeignKey(Question)
282
    eval_text = models.CharField(max_length=2000)
283
    eval_site = models.CharField(max_length=2000)
284
    eval_site_exclude = models.CharField(max_length=2000)
285
    
286
    def from_xml(self, xmldoc, question, insert = True):
287
        if not insert:
288
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
289
        self.question = question
290
        self.eval_text = xmlMinidom.getString(xmldoc, 'EvalText')
291
        self.eval_site = xmlMinidom.getString(xmldoc, 'EvalSite')
292
        self.eval_site_exclude = ""
293
        self.save()
294
        
295
    def to_xml(self):
296
        str_xml = "<EvalText><![CDATA[" + self.eval_text + "]]></EvalText>"
297
        str_xml += "<EvalSite><![CDATA[" + self.eval_site + "]]></EvalSite>"
298
        return '<Query Id="' + str(self.id) + '">' + str_xml + '</Query>'
299
300
class Choice(models.Model):
301
    '''
302
    '''
303
    question = models.ForeignKey(Question)
304
    text = models.CharField(max_length=200)
305
    order = models.IntegerField()
306
    todo = models.CharField(max_length=2000)
307
    
308
    def from_xml(self, xmldoc, question, insert = True):
309
        if not insert:
310
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
311
        self.question = question
312
        self.text = xmlMinidom.getStringAttribute(xmldoc, 'Text')
313
        self.order = xmlMinidom.getNaturalAttribute(xmldoc, 'Order')
314
        self.todo = xmlMinidom.getString(xmldoc, 'Todo')
315
        self.save()
316
    
317
    def to_xml(self):
318
        str_xml = "<Todo>" + self.todo + "</Todo>"
319
        return '<Choice Id="' + str(self.id) + '" Text="' + self.text + '" Order="' + str(self.order) + '">' + str_xml + '</Choice>'
320
321
    class Meta:
322
        ordering = ['order']
323
324
class WeightScenario(models.Model):
325
    name = models.CharField(max_length=200)
326
    methodology_version = models.ForeignKey(MethodologyVersion)
327
    active = models.BooleanField(blank=False)
328
329
    def question_weight(self, question_id):
330
        '''
331
        returns the weight; default is 1
332
        '''
333
        weight = 1
334
        for w in self.weight_set.all():
335
            if w.question.id == question_id:
336
                weight = w.weight
337
        return weight
338
    def from_xml(self, xmldoc, methodology_version, insert = True):
339
        if not insert:
340
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
341
        self.methodology_version = methodology_version
342
        self.name = xmlMinidom.getStringAttribute(xmldoc, 'Name')
343
        self.active = xmlMinidom.getStringAttribute(xmldoc, 'Active')
344
        self.save()
345
        #Weights
346
        xml_weights = xmldoc.getElementsByTagName('Weight')
347
        for xml_weight in xml_weights:
348
            w = Weight()
349
            w.from_xml(xml_weight, self, insert)
350
    
351
    def to_xml(self):
352
        str_xml = "<Weights>"
353
        for weight in self.weight_set.all():
354
            str_xml += weight.to_xml()
355
        str_xml += "</Weights>"
356
        return '<WeightScenario Id="' + str(self.id) + '" Name="' + self.name + '" Active="' + str(self.active) + '">' + str_xml + '</WeightScenario>'
357
358
    def __str__(self):
359
        return self.name
360
361
class Weight(models.Model):
362
    question = models.ForeignKey(Question)
363
    weight = models.FloatField()
364
    scenario = models.ForeignKey(WeightScenario)
365
    
366
    def from_xml(self, xmldoc, scenario, insert = True):
367
        if not insert:
368
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
369
        self.scenario = scenario
370
        self.weight = xmlMinidom.getStringAttribute(xmldoc, 'weight')
371
        self.question = Question.objects.get(pk=xmlMinidom.getNaturalAttribute(xmldoc, 'question_id'))
372
        self.save()
373
    
374
    def to_xml(self):
375
        return '<Weight Id="' + str(self.id) + '" question_id="' + str(self.question.id) + '" weight="' + str(self.weight) + '"/>'
6
376
7
class Analysis(models.Model):
377
class Analysis(models.Model):
8
    name = models.CharField(max_length=200)
378
    name = models.CharField(max_length=200)
9
    description = models.CharField(max_length=2000)
379
    description = models.CharField(max_length=2000)
10
    comment = models.TextField(null=True,blank=True)
380
    comment = models.TextField(null=True,blank=True)
11
    created = models.DateField(default=datetime.now, blank=True)
381
    created = models.DateField(default=datetime.now, blank=True)
12
    methodology_version = models.ForeignKey('methodology.MethodologyVersion')
382
    methodology_version = models.ForeignKey(MethodologyVersion)
13
    user_login = models.CharField(max_length=50)
383
    user_login = models.CharField(max_length=50)
14
    visible = models.BooleanField(default=True)
384
    visible = models.BooleanField(default=True)
15
    weight_scenario = models.ForeignKey(WeightScenario)
385
    weight_scenario = models.ForeignKey(WeightScenario)
16
    protected = models.BooleanField(default=False)
386
    protected = models.BooleanField(default=False)
387
    
17
    def __str__(self):
388
    def __str__(self):
18
        return self.name
389
        return self.name
390
    
391
    def calculate_scores(self):
392
        '''
393
        Calculates scores for each couple (instance, page) and (instance, question)
394
        Stores them on the db 
395
        '''
396
        #let's get the weight from the scenario if any; the default is 1
397
        try:
398
            weight_scenarios = self.methodology_version.weightscenario_set.filter(active=True)
399
            if len(weight_scenarios) == 1:
400
                weight_scenario = weight_scenarios[0]
401
            else:
402
                raise Exception("There should be exactly one WeightScenario for MethodologyVersion " + str(self.methodology_version.id))
403
        except:
404
            # an empty one will set all weights to 1
405
            weight_scenario = WeightScenario()
406
        #loop through the pages to reset scores to 0; when I calculate a question 
407
        #then I add its score to the corresponding page
408
        #loop on top level pages and recursive calculation on child pages
409
        for page in self.methodology_version.page_set.all():
410
            for instance in self.instance_set.all():
411
                page.calculate_scores(instance, weight_scenario, self.instance_set.all())
412
        self.methodology_version.create_graphs(self.instance_set.all())
413
        
414
#  @staticmethod
415
#     def create_graphs(object, pages, instances):
416
        '''
417
        a static method because I need to create the same 
418
        '''
419
        
19
    def from_xml(self, xmldoc, insert = True):
420
    def from_xml(self, xmldoc, insert = True):
421
        '''
20
        #All but the MethodologyVersion so that we can independently import from xml MethodologyVersion and Analysis
422
        All but the MethodologyVersion so that we can independently import from xml 
423
        MethodologyVersion and Analysis
424
        '''
21
        pass
425
        pass
426
    
22
    def to_xml(self):
427
    def to_xml(self):
23
        str_xml = "<Description>" + self.description + "</Description>"
428
        str_xml = "<Description>" + self.description + "</Description>"
24
        str_xml += "<Comment>" + self.comment + "</Comment>"
429
        str_xml += "<Comment>" + self.comment + "</Comment>"
25
        str_xml += self.weight_scenario.to_xml()
430
        str_xml += self.weight_scenario.to_xml()
26
        str_xml += "<Instances>"
431
        str_xml += "<Instances>"
...
...
35
    It represents one of the entities we evaluate in the assessment
440
    It represents one of the entities we evaluate in the assessment
36
    """
441
    """
37
    name = models.CharField(max_length=200)
442
    name = models.CharField(max_length=200)
38
    name_for_search = models.CharField(max_length=200, default="")
443
    name_for_search = models.CharField(max_length=200, default="")
39
    analysis = models.ForeignKey(Analysis)
444
    analysis = models.ForeignKey(Analysis)
445
    
40
    def __str__(self):
446
    def __str__(self):
41
        return self.name + " - " + self.name_for_search
447
        return self.name + " - " + self.name_for_search
448
    
42
    def from_xml(self, xmldoc, insert = True):
449
    def from_xml(self, xmldoc, insert = True):
43
        pass
450
        pass
451
    
44
    def to_xml(self):
452
    def to_xml(self):
45
        str_xml = "<Answers>"
453
        str_xml = "<Answers>"
46
        for answer in self.answer_set.all():
454
        for answer in self.answer_set.all():
47
            str_xml += answer.to_xml()
455
            str_xml += answer.to_xml()
48
        str_xml += "</Answers>"
456
        str_xml += "</Answers>"
49
             
457
             
50
        return '<Instance Id="' + str(self.id) + '" Name="' + self.name + '" NameForSearch="' + self.name_for_search + '">' + str_xml + "</Instance>"
458
        return '<Instance Id="' + str(self.id) + '" Name="' + self.name + '" NameForSearch="' + self.name_for_search + '">' + str_xml + "</Instance>"
51
    
459
    
460
class PageScore(models.Model):
461
    page = models.ForeignKey(Page)
462
    instance = models.ForeignKey(Instance)
463
    score = models.FloatField()
464
    class Meta:
465
        unique_together = ('page', 'instance',)
466
    
467
52
class Answer(models.Model):
468
class Answer(models.Model):
53
    """
469
    """
54
    
470
    value_integer is the value of the answer
471
    score is value_integer * weight
472
    where weight is associated to the question in a WeightScenario
55
    """
473
    """
56
    instance = models.ForeignKey(Instance)
474
    instance = models.ForeignKey(Instance)
57
    question = models.ForeignKey('methodology.Question')
475
    question = models.ForeignKey(Question)
58
    value_integer = models.IntegerField()
476
    value_integer = models.IntegerField()
477
    score = models.FloatField(default=0)
59
    notes = models.CharField(max_length=2000)
478
    notes = models.CharField(max_length=2000)
479
    
60
    def from_xml(self, xmldoc, insert = True):
480
    def from_xml(self, xmldoc, insert = True):
61
        pass
481
        pass
482
    
62
    def to_xml(self):
483
    def to_xml(self):
63
        str_xml = "<Notes>" + self.notes + "</Notes>"
484
        str_xml = "<Notes>" + self.notes + "</Notes>"
64
        str_xml += '<Question Id="' + str(self.question.id) + '"></Question>'
485
        str_xml += '<Question Id="' + str(self.question.id) + '"></Question>'
65
             
486
             
66
        return '<Answer ValueInteger="' + str(self.value_integer) + '">' + str_xml + "</Answer>"
487
        return '<Answer ValueInteger="' + str(self.value_integer) + '">' + str_xml + "</Answer>"
67
488
489
    class Meta:
490
        unique_together = ('question', 'instance',)
491
68
class Configuration(models.Model):
492
class Configuration(models.Model):
69
    default_methodology_version = models.ForeignKey('methodology.MethodologyVersion', blank=True, null=True)
493
    default_methodology_version = models.ForeignKey(MethodologyVersion, blank=True, null=True)
70
494
71
495
72
class UploadedFile(models.Model):
496
class UploadedFile(models.Model):
73
    '''
497
    '''
74
    Used to save uploaded xml file so that it can be later retrieved and imported
498
    Used to save uploaded xml file so that it can be later retrieved and imported