Switch to unified view

a/OSSEval/analysis/models.py b/OSSEval/analysis/models.py
1
import importlib
1
from django.db import models
2
from django.db import models
2
from datetime import datetime
3
from datetime import datetime
3
from random import randrange
4
from random import randrange
4
from pygal import Radar, Bar
5
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 pygal.style import NeonStyle, DarkSolarizedStyle, LightSolarizedStyle, LightStyle, CleanStyle, RedBlueStyle, DarkColorizedStyle, LightColorizedStyle, TurquoiseStyle, LightGreenStyle, DarkGreenStyle, DarkGreenBlueStyle, BlueStyle
...
...
39
        return '<Methodology Id="' + str(self.id) + '" Name="' + self.name + '" Active="' + str(self.active) + '">' + str_xml + "</Methodology>"
40
        return '<Methodology Id="' + str(self.id) + '" Name="' + self.name + '" Active="' + str(self.active) + '">' + str_xml + "</Methodology>"
40
41
41
class HasPages(models.Model):
42
class HasPages(models.Model):
42
    bar_chart = models.TextField(blank=True)
43
    bar_chart = models.TextField(blank=True)
43
    radar_chart = models.TextField(blank=True)
44
    radar_chart = models.TextField(blank=True)
44
    def create_graphs(self, instances):
45
    def create_graphs(self, instances, include_max = True):
45
        # I draw the graphs for the report
46
        # I draw the graphs for the report
46
        #styles = [NeonStyle, DarkSolarizedStyle, LightSolarizedStyle, LightStyle, CleanStyle, RedBlueStyle, DarkColorizedStyle, LightColorizedStyle, TurquoiseStyle, LightGreenStyle, DarkGreenStyle, DarkGreenBlueStyle, BlueStyle]
47
        #styles = [NeonStyle, DarkSolarizedStyle, LightSolarizedStyle, LightStyle, CleanStyle, RedBlueStyle, DarkColorizedStyle, LightColorizedStyle, TurquoiseStyle, LightGreenStyle, DarkGreenStyle, DarkGreenBlueStyle, BlueStyle]
47
        #style=styles[randrange(len(styles))]
48
        #style=styles[randrange(len(styles))]
48
        style=LightColorizedStyle
49
        style=LightColorizedStyle
49
        bar_chart = Bar(width=300, height=400, explicit_size=True, rounded_bars=5, disable_xml_declaration=True, style=style)
50
        bar_chart = Bar(width=300, height=400, explicit_size=True, rounded_bars=5, disable_xml_declaration=True, style=style)
...
...
54
            radar_chart.title = 'Summary graph'
55
            radar_chart.title = 'Summary graph'
55
        radar_chart.x_labels = []
56
        radar_chart.x_labels = []
56
        instance_scores = {}
57
        instance_scores = {}
57
        for instance in instances:
58
        for instance in instances:
58
            instance_scores[instance.id] = []
59
            instance_scores[instance.id] = []
60
        if include_max:
61
            # I use 0 as an id for the max score
62
            instance_scores[0] = []
59
        for page in self.page_set.all():
63
        for page in self.page_set.all():
60
            radar_chart.x_labels.append(page.name)
64
            radar_chart.x_labels.append(page.name)
61
            for instance in instances:
65
            for instance in instances:
62
                try:
66
                try:
63
                    ps = PageScore.objects.get(page=page, instance=instance)
67
                    ps = PageScore.objects.get(page=page, instance=instance)
64
                    instance_scores[instance.id].append(ps.score)
68
                    instance_scores[instance.id].append(ps.score)
65
                except:
69
                except:
66
                    instance_scores[instance.id].append(0)
70
                    instance_scores[instance.id].append(0)
71
                if include_max:
72
                    instance_scores[0].append(5)
73
                    print str(page.id) + " - " + str(page.max_score)
67
        for instance in instances:
74
        for instance in instances:
68
            radar_chart.add(instance.name, instance_scores[instance.id])
75
            radar_chart.add(instance.name, instance_scores[instance.id])
69
            bar_chart.add(instance.name, sum(instance_scores[instance.id]))
76
            bar_chart.add(instance.name, sum(instance_scores[instance.id]))
77
        if include_max:
78
            radar_chart.add("MAX", instance_scores[0])
79
            bar_chart.add("MAX", sum(instance_scores[0]))
70
        self.radar_chart = radar_chart.render()
80
        self.radar_chart = radar_chart.render()
71
        self.bar_chart = bar_chart.render()
81
        self.bar_chart = bar_chart.render()
72
        self.save()
82
        self.save()
73
    class Meta:
83
    class Meta:
74
        abstract = True
84
        abstract = True
...
...
126
class Page(HasPages):
136
class Page(HasPages):
127
    name = models.CharField(max_length=200)
137
    name = models.CharField(max_length=200)
128
    order = models.IntegerField(null=False,blank=False)
138
    order = models.IntegerField(null=False,blank=False)
129
    parent = models.ForeignKey('self',null=True,blank=True)
139
    parent = models.ForeignKey('self',null=True,blank=True)
130
    methodology_version = models.ForeignKey(MethodologyVersion,null=True,blank=True)
140
    methodology_version = models.ForeignKey(MethodologyVersion,null=True,blank=True)
141
    max_score = 0
131
    def questions(self):
142
    def questions(self):
132
        q = list(self.question_set.all())
143
        q = list(self.question_set.all())
133
        for p in self.page_set.all():
144
        for p in self.page_set.all():
134
            q += p.questions()
145
            q += p.questions()
135
        return q
146
        return q
...
...
178
            str_xml += question.to_xml()
189
            str_xml += question.to_xml()
179
        str_xml += "</Questions>"
190
        str_xml += "</Questions>"
180
        return '<Page Id="' + str(self.id) + '" Name="' + self.name + '" Order="' + str(self.order) + '">' + str_xml + "</Page>"
191
        return '<Page Id="' + str(self.id) + '" Name="' + self.name + '" Order="' + str(self.order) + '">' + str_xml + "</Page>"
181
192
182
    def calculate_scores(self, instance, weight_scenario, instances):
193
    def calculate_scores(self, instance, weight_scenario, instances):
194
        self.max_score = 5
183
#         Let's reset the score to 0
195
#         Let's reset the score to 0
184
        try:
196
        try:
185
            ps = PageScore.objects.get(page=self, instance=instance)
197
            ps = PageScore.objects.get(page=self, instance=instance)
186
        except:
198
        except:
187
            ps = PageScore(page=self, instance=instance, score=0)
199
            ps = PageScore(page=self, instance=instance, score=0)
...
...
319
        return '<Choice Id="' + str(self.id) + '" Text="' + self.text + '" Order="' + str(self.order) + '">' + str_xml + '</Choice>'
331
        return '<Choice Id="' + str(self.id) + '" Text="' + self.text + '" Order="' + str(self.order) + '">' + str_xml + '</Choice>'
320
332
321
    class Meta:
333
    class Meta:
322
        ordering = ['order']
334
        ordering = ['order']
323
335
336
class Weight():
337
    '''
338
    used by WeightScenario so I need to define it first
339
    '''
340
    pass
341
324
class WeightScenario(models.Model):
342
class WeightScenario(models.Model):
325
    name = models.CharField(max_length=200)
343
    name = models.CharField(max_length=200)
326
    methodology_version = models.ForeignKey(MethodologyVersion)
344
    methodology_version = models.ForeignKey(MethodologyVersion)
327
    active = models.BooleanField(blank=False)
345
    active = models.BooleanField(blank=False)
328
346
...
...
333
        weight = 1
351
        weight = 1
334
        for w in self.weight_set.all():
352
        for w in self.weight_set.all():
335
            if w.question.id == question_id:
353
            if w.question.id == question_id:
336
                weight = w.weight
354
                weight = w.weight
337
        return weight
355
        return weight
356
338
    def from_xml(self, xmldoc, methodology_version, insert = True):
357
    def from_xml(self, xmldoc, methodology_version, insert = True):
339
        if not insert:
358
        if not insert:
340
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
359
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
341
        self.methodology_version = methodology_version
360
        self.methodology_version = methodology_version
342
        self.name = xmlMinidom.getStringAttribute(xmldoc, 'Name')
361
        self.name = xmlMinidom.getStringAttribute(xmldoc, 'Name')
...
...
367
        if not insert:
386
        if not insert:
368
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
387
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
369
        self.scenario = scenario
388
        self.scenario = scenario
370
        self.weight = xmlMinidom.getStringAttribute(xmldoc, 'weight')
389
        self.weight = xmlMinidom.getStringAttribute(xmldoc, 'weight')
371
        self.question = Question.objects.get(pk=xmlMinidom.getNaturalAttribute(xmldoc, 'question_id'))
390
        self.question = Question.objects.get(pk=xmlMinidom.getNaturalAttribute(xmldoc, 'question_id'))
391
        # If I am inserting I must check that there's no other Weight for the same 
392
        # couple ('question', 'scenario') as I have a unique_together constraint
393
        if not self.id:
394
            # I look for a Weight for the same question and scenario
395
            try:
396
                w = Weight.objects.get(scenario_id=self.scenario.id, question_id=self.question.id)
397
                w.delete()
398
            except:
399
                # I didn't find one; nothing to do 
400
                pass
372
        self.save()
401
        self.save()
373
    
402
    
374
    def to_xml(self):
403
    def to_xml(self):
375
        return '<Weight Id="' + str(self.id) + '" question_id="' + str(self.question.id) + '" weight="' + str(self.weight) + '"/>'
404
        return '<Weight Id="' + str(self.id) + '" question_id="' + str(self.question.id) + '" weight="' + str(self.weight) + '"/>'
376
405
406
    class Meta:
407
        unique_together = ('question', 'scenario')
408
    
377
class Analysis(models.Model):
409
class Analysis(models.Model):
378
    name = models.CharField(max_length=200)
410
    name = models.CharField(max_length=200)
379
    description = models.CharField(max_length=2000)
411
    description = models.CharField(max_length=2000)
380
    comment = models.TextField(null=True,blank=True)
412
    comment = models.TextField(null=True,blank=True)
381
    created = models.DateField(default=datetime.now, blank=True)
413
    created = models.DateField(default=datetime.now, blank=True)
...
...
386
    protected = models.BooleanField(default=False)
418
    protected = models.BooleanField(default=False)
387
    
419
    
388
    def __str__(self):
420
    def __str__(self):
389
        return self.name
421
        return self.name
390
    
422
    
391
    def calculate_scores(self):
423
    def calculate_scores(self, weight_scenario_id):
392
        '''
424
        '''
393
        Calculates scores for each couple (instance, page) and (instance, question)
425
        Calculates scores for each couple (instance, page) and (instance, question)
394
        Stores them on the db 
426
        Stores them on the db 
395
        '''
427
        '''
396
        #let's get the weight from the scenario if any; the default is 1
428
        #let's get the weight from the scenario if any; the default is 1
397
        try:
429
        try:
398
            weight_scenarios = self.methodology_version.weightscenario_set.filter(active=True)
399
            if len(weight_scenarios) == 1:
430
            if weight_scenario_id > 0:
400
                weight_scenario = weight_scenarios[0]
431
                weight_scenario = WeightScenario.objects.get(pk=weight_scenario_id)
401
            else:
432
            else:
402
                raise Exception("There should be exactly one WeightScenario for MethodologyVersion " + str(self.methodology_version.id))
433
                weight_scenario = self.weight_scenario
403
        except:
434
        except:
404
            # an empty one will set all weights to 1
435
            # an empty one will set all weights to 1
405
            weight_scenario = WeightScenario()
436
            weight_scenario = WeightScenario()
406
        #loop through the pages to reset scores to 0; when I calculate a question 
437
        #loop through the pages to reset scores to 0; when I calculate a question 
407
        #then I add its score to the corresponding page
438
        #then I add its score to the corresponding page
408
        #loop on top level pages and recursive calculation on child pages
439
        #loop on top level pages and recursive calculation on child pages
409
        for page in self.methodology_version.page_set.all():
440
        for page in self.methodology_version.page_set.all():
410
            for instance in self.instance_set.all():
441
            for instance in self.instance_set.all():
411
                page.calculate_scores(instance, weight_scenario, self.instance_set.all())
442
                page.calculate_scores(instance, weight_scenario, self.instance_set.all())
412
        self.methodology_version.create_graphs(self.instance_set.all())
443
        self.methodology_version.create_graphs(self.instance_set.all())
444
        return weight_scenario
413
        
445
        
414
#  @staticmethod
446
#  @staticmethod
415
#     def create_graphs(object, pages, instances):
447
#     def create_graphs(object, pages, instances):
416
        '''
448
        '''
417
        a static method because I need to create the same 
449
        a static method because I need to create the same 
...
...
420
    def from_xml(self, xmldoc, insert = True):
452
    def from_xml(self, xmldoc, insert = True):
421
        '''
453
        '''
422
        All but the MethodologyVersion so that we can independently import from xml 
454
        All but the MethodologyVersion so that we can independently import from xml 
423
        MethodologyVersion and Analysis
455
        MethodologyVersion and Analysis
424
        '''
456
        '''
425
        pass
457
        if not insert:
458
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
459
        self.name = xmlMinidom.getStringAttribute(xmldoc, 'Name')
460
        self.comment = xmlMinidom.getStringAttribute(xmldoc, 'Comment')
461
        self.created = xmlMinidom.getStringAttribute(xmldoc, 'Created')
462
        self.methodology_version = MethodologyVersion.objects.get(pk=xmlMinidom.getNaturalAttribute(xmldoc, 'MethodologyVersionId'))
463
        self.user_login = xmlMinidom.getStringAttribute(xmldoc, 'UserLogin')
464
        self.visible = xmlMinidom.getStringAttribute(xmldoc, 'Visible')
465
        self.protected = xmlMinidom.getStringAttribute(xmldoc, 'Protected')
466
        #self.weight_scenario
467
        xml_weight_scenario = xmldoc.getElementsByTagName('WeightScenario')[0]
468
        ws = WeightScenario()        
469
        ws.from_xml(xml_weight_scenario, self.methodology_version, insert)
470
        self.weight_scenario = ws
471
        self.save()
472
        #Instances
473
        xml_instances = xmldoc.getElementsByTagName('Instance')
474
        for xml_instance in xml_instances:
475
            i = Instance()
476
            i.from_xml(xml_instance, self, insert)
426
    
477
    
427
    def to_xml(self):
478
    def to_xml(self):
428
        str_xml = "<Description>" + self.description + "</Description>"
479
        str_xml = "<Description>" + self.description + "</Description>"
429
        str_xml += "<Comment>" + self.comment + "</Comment>"
480
        str_xml += "<Comment>" + self.comment + "</Comment>"
430
        str_xml += self.weight_scenario.to_xml()
481
        str_xml += self.weight_scenario.to_xml()
431
        str_xml += "<Instances>"
482
        str_xml += "<Instances>"
432
        for instance in self.instance_set.all():
483
        for instance in self.instance_set.all():
433
            str_xml += instance.to_xml()
484
            str_xml += instance.to_xml()
434
        str_xml += "</Instances>"
485
        str_xml += "</Instances>"
435
             
486
             
436
        return '<Analysis Id="' + str(self.id) + '" Name="' + self.name + '"  Created="' + str(self.created) + '" UserLogin="' + self.user_login + '">' + str_xml + "</Analysis>"
487
        return '<Analysis Id="' + str(self.id) + '" MethodologyVersionId="' + str(self.methodology_version.id) + '" Name="' + self.name + '"  Created="' + str(self.created) + '" Visible="' + str(self.visible) + '" Protected="' + str(self.protected) + '" UserLogin="' + self.user_login + '">' + str_xml + "</Analysis>"
437
    
488
    
438
class Instance(models.Model):
489
class Instance(models.Model):
439
    """
490
    """
440
    It represents one of the entities we evaluate in the assessment
491
    It represents one of the entities we evaluate in the assessment
441
    """
492
    """
442
    name = models.CharField(max_length=200)
493
    name = models.CharField(max_length=200)
494
    # name_for_search is not used yet; it is intended to provide a name to be used to search the web
443
    name_for_search = models.CharField(max_length=200, default="")
495
    name_for_search = models.CharField(max_length=200, default="")
444
    analysis = models.ForeignKey(Analysis)
496
    analysis = models.ForeignKey(Analysis)
445
    
497
    
446
    def __str__(self):
498
    def __str__(self):
447
        return self.name + " - " + self.name_for_search
499
        return self.name + " - " + self.name_for_search
448
    
500
    
449
    def from_xml(self, xmldoc, insert = True):
501
    def from_xml(self, xmldoc, analysis, insert = True):
450
        pass
502
        if not insert:
503
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
504
        self.name = xmlMinidom.getStringAttribute(xmldoc, 'Name')
505
        #ActualInstance
506
        self.analysis = analysis
507
        xml_actual_instance = xmldoc.getElementsByTagName(self.analysis.methodology_version.methodology.entity.actual_entity_class)[0]
508
        module = importlib.import_module(self.analysis.methodology_version.methodology.entity.actual_entity_app + ".models")
509
        actual_entity_class = getattr(module, self.analysis.methodology_version.methodology.entity.actual_entity_class)
510
        self.actual_instance = actual_entity_class()
511
        self.actual_instance.from_xml(xml_actual_instance, self, insert)
512
        self.save()
513
        #Answers
514
        xml_answers = xmldoc.getElementsByTagName('Answer')
515
        for xml_answer in xml_answers:
516
            a = Answer()
517
            a.from_xml(xml_answer, self, insert)
451
    
518
    
452
    def to_xml(self):
519
    def to_xml(self):
453
        str_xml = "<Answers>"
520
        str_xml = "<Answers>"
454
        for answer in self.answer_set.all():
521
        for answer in self.answer_set.all():
455
            str_xml += answer.to_xml()
522
            str_xml += answer.to_xml()
456
        str_xml += "</Answers>"
523
        str_xml += "</Answers>"
457
             
524
        str_xml += self.actual_instance.to_xml()
458
        return '<Instance Id="' + str(self.id) + '" Name="' + self.name + '" NameForSearch="' + self.name_for_search + '">' + str_xml + "</Instance>"
525
        return '<Instance Id="' + str(self.id) + '" Name="' + self.name + '">' + str_xml + "</Instance>"
459
    
526
    
460
class PageScore(models.Model):
527
class PageScore(models.Model):
461
    page = models.ForeignKey(Page)
528
    page = models.ForeignKey(Page)
462
    instance = models.ForeignKey(Instance)
529
    instance = models.ForeignKey(Instance)
463
    score = models.FloatField()
530
    score = models.FloatField()
...
...
475
    question = models.ForeignKey(Question)
542
    question = models.ForeignKey(Question)
476
    value_integer = models.IntegerField()
543
    value_integer = models.IntegerField()
477
    score = models.FloatField(default=0)
544
    score = models.FloatField(default=0)
478
    notes = models.CharField(max_length=2000)
545
    notes = models.CharField(max_length=2000)
479
    
546
    
480
    def from_xml(self, xmldoc, insert = True):
547
    def from_xml(self, xmldoc, instance, insert = True):
548
        if not insert:
549
            self.id = xmlMinidom.getNaturalAttribute(xmldoc, 'Id')
550
        self.instance = instance
551
        xml_question = xmldoc.getElementsByTagName('Question')[0]
552
        question = Question.objects.get(pk = xmlMinidom.getNaturalAttribute(xml_question, 'Id'))
553
        self.question = question
554
        self.value_integer = xmlMinidom.getNaturalAttribute(xmldoc, 'ValueInteger')
555
        xml_notes = xmldoc.getElementsByTagName('Notes')[0]
556
        try:
557
            self.notes = xml_notes.firstChild.data
558
        except:
559
            self.notes = ""
560
        # If I am inserting I must check that there's no other answer for the same 
561
        # couple ('question', 'instance') as I have a unique_together constraint
562
        if not self.id:
563
            # I look for an answer for the same question and instance
564
            try:
565
                a = Answer.objects.get(instance_id=self.instance.id, question_id=self.question.id)
566
                a.delete()
567
            except:
568
                # I didn't find one; nothing to do 
481
        pass
569
                pass
570
        self.save()
482
    
571
    
483
    def to_xml(self):
572
    def to_xml(self):
484
        str_xml = "<Notes>" + self.notes + "</Notes>"
573
        str_xml = "<Notes>" + self.notes + "</Notes>"
485
        str_xml += '<Question Id="' + str(self.question.id) + '"></Question>'
574
        str_xml += '<Question Id="' + str(self.question.id) + '"></Question>'
486
             
575
             
487
        return '<Answer ValueInteger="' + str(self.value_integer) + '">' + str_xml + "</Answer>"
576
        return '<Answer Id="' + str(self.id) + '" ValueInteger="' + str(self.value_integer) + '">' + str_xml + "</Answer>"
488
577
489
    class Meta:
578
    class Meta:
490
        unique_together = ('question', 'instance',)
579
        unique_together = ('question', 'instance',)
491
580
492
class Configuration(models.Model):
581
class Configuration(models.Model):
...
...
495
584
496
class UploadedFile(models.Model):
585
class UploadedFile(models.Model):
497
    '''
586
    '''
498
    Used to save uploaded xml file so that it can be later retrieved and imported
587
    Used to save uploaded xml file so that it can be later retrieved and imported
499
    '''
588
    '''
500
    docfile = models.FileField(upload_to='documents/%Y/%m/%d')
589
    docfile = models.FileField(upload_to='documents/%Y/%m/%d')
590
    
591
class MyModel(models.Model):
592
    field1 = models.CharField(max_length=40, blank=False, null=False)
593
    field2 = models.CharField(max_length=60, blank=True, null=True)