Federation   F
last analyzed

Complexity

Total Complexity 78

Size/Duplication

Total Lines 315
Duplicated Lines 6.35 %

Importance

Changes 19
Bugs 0 Features 0
Metric Value
wmc 78
c 19
b 0
f 0
dl 20
loc 315
rs 2.1126

25 Methods

Rating   Name   Duplication   Size   Complexity  
C _update_entities() 0 27 7
C process_metadata_entities() 0 28 7
A get_sp_shib1() 0 2 1
A get_aa() 0 8 3
A get_entity() 0 2 1
A get_sp_saml2() 0 2 1
F compute_new_stats() 0 48 9
A certificates() 0 3 1
A _metadata() 0 5 2
A get_entity_metadata() 0 2 1
A get_absolute_url() 0 2 1
A get_idp_saml2() 0 2 1
B process_metadata() 0 15 6
A get_sp() 0 8 3
B _get_or_create_ecategories() 20 20 6
A get_idp_shib1() 0 2 1
A __unicode__() 0 2 1
A _daterange() 0 4 2
A _remove_deleted_entities() 0 10 3
A get_idp() 0 8 3
A get_stat_protocol() 0 10 3
A get_idp_saml1() 0 2 1
D _add_new_entities() 0 35 8
B can_edit() 0 8 5
A get_sp_saml1() 0 2 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Federation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#################################################################
2
# MET v2 Metadate Explorer Tool
3
#
4
# This Software is Open Source. See License: https://github.com/TERENA/met/blob/master/LICENSE.md
5
# Copyright (c) 2012, TERENA All rights reserved.
6
#
7
# This Software is based on MET v1 developed for TERENA by Yaco Sistemas, http://www.yaco.es/
8
# MET v2 was developed for TERENA by Tamim Ziai, DAASI International GmbH, http://www.daasi.de
9
# Current version of MET has been revised for performance improvements by Andrea Biancini,
10
# Consortium GARR, http://www.garr.it
11
##########################################################################
12
13
import pytz
14
import simplejson as json
15
16
from datetime import datetime, time, timedelta
17
18
from django.core.urlresolvers import reverse
19
from django.db import models
20
from django.db.models import Max
21
from django.db.models.signals import pre_save
22
from django.utils.translation import ugettext_lazy as _
23
from django.utils import timezone
24
from django.dispatch import receiver
25
from django.template.defaultfilters import slugify
26
27
from met.metadataparser.xmlparser import MetadataParser
28
29
from met.metadataparser.models.base import Base, XmlDescriptionError
30
from met.metadataparser.models.entity import Entity
31
from met.metadataparser.models.entity_type import EntityType
32
from met.metadataparser.models.entity_stat import EntityStat, stats
33
from met.metadataparser.models.entity_category import EntityCategory
34
from met.metadataparser.models.entity_federations import Entity_Federations
35
36
FEDERATION_TYPES = (
37
    (None, ''),
38
    ('hub-and-spoke', 'Hub and Spoke'),
39
    ('mesh', 'Full Mesh'),
40
)
41
42
43
def update_obj(mobj, obj, attrs=None):
44
    for_attrs = attrs or getattr(mobj, 'all_attrs', [])
45
    for attrb in attrs or for_attrs:
46
        if (getattr(mobj, attrb, None) and
47
            getattr(obj, attrb, None) and
48
                getattr(mobj, attrb) != getattr(obj, attrb)):
49
            setattr(obj, attrb, getattr(mobj, attrb))
50
51
52
class Federation(Base):
53
    """
54
    Model describing an identity federation.
55
    """
56
57
    name = models.CharField(blank=False, null=False, max_length=200,
58
                            unique=True, verbose_name=_(u'Name'))
59
60
    type = models.CharField(blank=True, null=True, max_length=100,
61
                            unique=False, verbose_name=_(u'Type'), choices=FEDERATION_TYPES)
62
63
    url = models.URLField(verbose_name='Federation url',
64
                          blank=True, null=True)
65
66
    fee_schedule_url = models.URLField(verbose_name='Fee schedule url',
67
                                       max_length=150, blank=True, null=True)
68
69
    logo = models.ImageField(upload_to='federation_logo', blank=True,
70
                             null=True, verbose_name=_(u'Federation logo'))
71
72
    is_interfederation = models.BooleanField(default=False, db_index=True,
73
                                             verbose_name=_(u'Is interfederation'))
74
75
    slug = models.SlugField(max_length=200, unique=True)
76
77
    country = models.CharField(blank=True, null=True, max_length=100,
78
                               unique=False, verbose_name=_(u'Country'))
79
80
    metadata_update = models.DateTimeField(blank=True, null=True,
81
                                           unique=False, verbose_name=_(u'Metadata update date and time'))
82
83
    certstats = models.CharField(blank=True, null=True, max_length=200,
84
                                 unique=False, verbose_name=_(u'Certificate Stats'))
85
86
    @property
87
    def certificates(self):
88
        return json.loads(self.certstats)
89
90
    @property
91
    def _metadata(self):
92
        if not hasattr(self, '_metadata_cache'):
93
            self._metadata_cache = self.load_file()
94
        return self._metadata_cache
95
96
    def __unicode__(self):
97
        return self.name
98
99
    def get_entity_metadata(self, entityid):
100
        return self._metadata.get_entity(entityid)
101
102
    def get_entity(self, entityid):
103
        return self.entity_set.get(entityid=entityid)
104
105
    def process_metadata(self):
106
        metadata = self.load_file()
107
108
        if self.file_id and metadata.file_id and metadata.file_id == self.file_id:
109
            return
110
        else:
111
            self.file_id = metadata.file_id
112
113
        if not metadata:
114
            return
115
        if not metadata.is_federation:
116
            raise XmlDescriptionError("XML Haven't federation form")
117
118
        update_obj(metadata.get_federation(), self)
119
        self.certstats = MetadataParser.get_certstats(metadata.rootelem)
120
121
    def _remove_deleted_entities(self, entities_from_xml):
122
        removed = 0
123
        for entity in self.entity_set.all():
124
            # Remove entity relation if does not exist in metadata
125
            if not entity.entityid in entities_from_xml:
126
                Entity_Federations.objects.filter(
127
                    federation=self, entity=entity).delete()
128
                removed += 1
129
130
        return removed
131
132 View Code Duplication
    def _get_or_create_ecategories(self, entity, cached_entity_categories):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
133
        entity_categories = []
134
        efed = Entity_Federations.objects.get_or_create(federation=self, entity=entity)[0]
135
        cur_cached_categories = [
136
            t.category_id for t in efed.entity_categories.all()]
137
        for ecategory in entity.xml_categories:
138
            if ecategory in cur_cached_categories:
139
                break
140
141
            if cached_entity_categories is None:
142
                entity_category, _ = EntityCategory.objects.get_or_create(
143
                    category_id=ecategory)
144
            else:
145
                if ecategory in cached_entity_categories:
146
                    entity_category = cached_entity_categories[ecategory]
147
                else:
148
                    entity_category = EntityCategory.objects.create(
149
                        category_id=ecategory)
150
            entity_categories.append(entity_category)
151
        return entity_categories
152
153
    def _update_entities(self, entities_to_update, entities_to_add):
154
        for e in entities_to_update:
155
            e.save()
156
157
        for e in entities_to_add:
158
            membership = Entity_Federations.objects.get_or_create(federation=self, entity=e)[0]
159
            membership.registration_instant = e.registration_instant.date() if e.registration_instant else None
160
161
            if e.xml_categories:
162
                db_entity_categories = EntityCategory.objects.all()
163
                cached_entity_categories = {
164
                    entity_category.category_id: entity_category for entity_category in db_entity_categories}
165
166
                # Delete categories no more present in XML
167
                membership.entity_categories.clear()
168
169
                # Create all entities, if not alread in database
170
                entity_categories = self._get_or_create_ecategories(e, cached_entity_categories)
171
172
                # Add categories to entity
173
                if len(entity_categories) > 0:
174
                    membership.entity_categories.add(*entity_categories)
175
            else:
176
                # No categories in XML, delete eventual categorie sin DB
177
                membership.entity_categories.clear()
178
179
            membership.save()
180
181
    def _add_new_entities(self, entities, entities_from_xml, request, federation_slug):
182
        db_entity_types = EntityType.objects.all()
183
        cached_entity_types = {
184
            entity_type.xmlname: entity_type for entity_type in db_entity_types}
185
186
        entities_to_add = []
187
        entities_to_update = []
188
189
        for m_id in entities_from_xml:
190
            if request and federation_slug:
191
                request.session['%s_cur_entities' % federation_slug] += 1
192
                request.session.save()
193
194
            created = False
195
            if m_id in entities:
196
                entity = entities[m_id]
197
            else:
198
                entity, created = Entity.objects.get_or_create(entityid=m_id)
199
200
            entityid = entity.entityid
201
            name = entity.name
202
            registration_authority = entity.registration_authority
203
            certstats = entity.certstats
204
            display_protocols = entity._display_protocols
205
206
            entity_from_xml = self._metadata.get_entity(m_id, False)
207
            entity.process_metadata(False, entity_from_xml, cached_entity_types, self)
208
209
            if created or entity.has_changed(entityid, name, registration_authority, certstats, display_protocols):
210
                entities_to_update.append(entity)
211
212
            entities_to_add.append(entity)
213
214
        self._update_entities(entities_to_update, entities_to_add)
215
        return len(entities_to_update)
216
217
    @staticmethod
218
    def _daterange(start_date, end_date):
219
        for n in range(int((end_date - start_date).days + 1)):
220
            yield start_date + timedelta(n)
221
222
    def compute_new_stats(self):
223
        if not self._metadata: return ([], [])
224
        entities_from_xml = self._metadata.get_entities()
225
226
        entities = Entity.objects.filter(entityid__in=entities_from_xml)
227
        entities = entities.prefetch_related('types')
228
        Entity_Federations.objects.filter(federation=self)
229
230
        try:
231
            first_date = EntityStat.objects.filter(
232
                federation=self).aggregate(Max('time'))['time__max']
233
            if not first_date:
234
                raise Exception('Not able to find statistical data in the DB.')
235
        except Exception:
236
            first_date = datetime(2010, 1, 1)
237
            first_date = pytz.utc.localize(first_date)
238
239
        for curtimestamp in self._daterange(first_date, timezone.now() - timedelta(1)):
240
            computed = {}
241
            not_computed = []
242
            entity_stats = []
243
            for feature in stats['features'].keys():
244
                fun = getattr(self, 'get_%s' % feature, None)
245
246
                if callable(fun):
247
                    stat = EntityStat()
248
                    stat.feature = feature
249
                    stat.time = curtimestamp
250
                    stat.federation = self
251
                    stat.value = fun(
252
                        entities, stats['features'][feature], curtimestamp)
253
                    entity_stats.append(stat)
254
                    computed[feature] = stat.value
255
                else:
256
                    not_computed.append(feature)
257
258
            from_time = datetime.combine(curtimestamp, time.min)
259
            if timezone.is_naive(from_time):
260
                from_time = pytz.utc.localize(from_time)
261
            to_time = datetime.combine(curtimestamp, time.max)
262
            if timezone.is_naive(to_time):
263
                to_time = pytz.utc.localize(to_time)
264
265
            EntityStat.objects.filter(
266
                federation=self, time__gte=from_time, time__lte=to_time).delete()
267
            EntityStat.objects.bulk_create(entity_stats)
268
269
        return (computed, not_computed)
270
271
    def process_metadata_entities(self, request=None, federation_slug=None):
272
        if not self._metadata: return
273
        entities_from_xml = self._metadata.get_entities()
274
        removed = self._remove_deleted_entities(entities_from_xml)
275
276
        entities = {}
277
        db_entities = Entity.objects.filter(entityid__in=entities_from_xml)
278
        db_entities = db_entities.prefetch_related('types')
279
        #TODO add prefetch related, federations, entity_categories
280
281
        for entity in db_entities.all():
282
            entities[entity.entityid] = entity
283
284
        if request and federation_slug:
285
            request.session['%s_num_entities' %
286
                            federation_slug] = len(entities_from_xml)
287
            request.session['%s_cur_entities' % federation_slug] = 0
288
            request.session['%s_process_done' % federation_slug] = False
289
            request.session.save()
290
291
        updated = self._add_new_entities(
292
            entities, entities_from_xml, request, federation_slug)
293
294
        if request and federation_slug:
295
            request.session['%s_process_done' % federation_slug] = True
296
            request.session.save()
297
298
        return removed, updated
299
300
    def get_absolute_url(self):
301
        return reverse('federation_view', args=[self.slug])
302
303
    @classmethod
304
    def get_sp(cls, entities, xml_name, ref_date=None):
305
        if ref_date and ref_date < pytz.utc.localize(datetime.now() - timedelta(days=1)):
306
            selected = entities.filter(
307
                types__xmlname=xml_name, entity_federations__registration_instant__lt=ref_date)
308
        else:
309
            selected = entities.filter(types__xmlname=xml_name)
310
        return len(selected)
311
312
    @classmethod
313
    def get_idp(cls, entities, xml_name, ref_date=None):
314
        if ref_date and ref_date < pytz.utc.localize(datetime.now() - timedelta(days=1)):
315
            selected = entities.filter(
316
                types__xmlname=xml_name, entity_federations__registration_instant__lt=ref_date)
317
        else:
318
            selected = entities.filter(types__xmlname=xml_name)
319
        return len(selected)
320
321
    @classmethod
322
    def get_aa(cls, entities, xml_name, ref_date=None):
323
        if ref_date and ref_date < pytz.utc.localize(datetime.now() - timedelta(days=1)):
324
            selected = entities.filter(
325
                types__xmlname=xml_name, entity_federations__registration_instant__lt=ref_date)
326
        else:
327
            selected = entities.filter(types__xmlname=xml_name)
328
        return len(selected)
329
330
    def get_sp_saml1(self, entities, xml_name, ref_date=None):
331
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
332
333
    def get_sp_saml2(self, entities, xml_name, ref_date=None):
334
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
335
336
    def get_sp_shib1(self, entities, xml_name, ref_date=None):
337
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
338
339
    def get_idp_saml1(self, entities, xml_name, ref_date=None):
340
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
341
342
    def get_idp_saml2(self, entities, xml_name, ref_date=None):
343
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
344
345
    def get_idp_shib1(self, entities, xml_name, ref_date=None):
346
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
347
348
    def get_stat_protocol(self, entities, xml_name, service_type, ref_date):
349
        if ref_date and ref_date < pytz.utc.localize(datetime.now() - timedelta(days=1)):
350
            selected = entities.filter(types__xmlname=service_type,
351
                                       _display_protocols__contains=xml_name,
352
                                       entity_federations__registration_instant__lt=ref_date)
353
        else:
354
            selected = entities.filter(types__xmlname=service_type,
355
                                       _display_protocols__contains=xml_name)
356
357
        return len(selected)
358
359
    def can_edit(self, user, delete):
360
        if user.is_superuser:
361
            return True
362
363
        permission = 'delete_federation' if delete else 'change_federation'
364
        if user.has_perm('metadataparser.%s' % permission) and user in self.editor_users.all():
365
            return True
366
        return False
367
368
369
@receiver(pre_save, sender=Federation, dispatch_uid='federation_pre_save')
370
def federation_pre_save(sender, instance, **kwargs):
371
    # Skip pre_save if only file name is saved
372
    if kwargs.has_key('update_fields') and kwargs['update_fields'] == set(['file']):
373
        return
374
375
    #slug = slugify(unicode(instance.name))[:200]
376
    # if instance.file_url and instance.file_url != '':
377
    #    try:
378
    #        instance.fetch_metadata_file(slug)
379
    #    except Exception, e:
380
    #        pass
381
382
    if instance.name:
383
        instance.slug = slugify(unicode(instance))[:200]
384
385
386
@receiver(pre_save, sender=Entity, dispatch_uid='entity_pre_save')
387
def entity_pre_save(sender, instance, **kwargs):
388
    # if refetch and instance.file_url:
389
    #    slug = slugify(unicode(instance.name))[:200]
390
    #    instance.fetch_metadata_file(slug)
391
    #    instance.process_metadata()
392
    pass
393