Completed
Push — master ( a94234...8b640b )
by Andrea
01:19
created

Federation   F

Complexity

Total Complexity 83

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 83
c 7
b 0
f 0
dl 0
loc 267
rs 1.5789

23 Methods

Rating   Name   Duplication   Size   Complexity  
A _update_entities() 0 5 2
A get_entity() 0 2 1
A _metadata() 0 5 2
A get_entity_metadata() 0 2 1
B process_metadata() 0 14 6
A _entity_has_changed() 0 10 4
A __unicode__() 0 2 1
B _remove_deleted_entities() 0 17 7
D _add_new_entities() 0 32 8
B process_metadata_entities() 0 24 6
A get_sp_shib1() 0 2 1
A get_sp_saml2() 0 2 1
D compute_new_stats() 0 44 8
A get_absolute_url() 0 2 1
A get_idp_saml2() 0 2 1
B get_sp() 0 11 7
A get_idp_shib1() 0 2 1
A _daterange() 0 4 2
B get_idp() 0 11 7
C get_stat_protocol() 0 14 9
A get_idp_saml1() 0 2 1
B can_edit() 0 8 5
A get_sp_saml1() 0 2 1

How to fix   Complexity   

Complex Class

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 sys
14
import requests
15
import simplejson as json
16
import pytz
17
18
from os import path
19
from urlparse import urlsplit, urlparse
20
from urllib import quote_plus
21
from datetime import datetime, time, timedelta, date
22
23
from django.conf import settings
24
from django.contrib import messages
25
from django.contrib.auth.models import User
26
from django.core import validators
27
from django.core.cache import cache
28
from django.core.files.base import ContentFile
29
from django.core.urlresolvers import reverse
30
from django.db import models, transaction
31
from django.db.models import Count, Max
32
from django.db.models.signals import pre_save
33
from django.db.models.query import QuerySet
34
from django.dispatch import receiver
35
from django.template.defaultfilters import slugify
36
from django.utils.safestring import mark_safe
37
from django.utils.translation import ugettext_lazy as _
38
from django.utils import timezone
39
40
from lxml import etree
41
42
from pyff.mdrepo import MDRepository
43
from pyff.pipes import Plumbing
44
45
from met.metadataparser.utils import compare_filecontents
46
from met.metadataparser.xmlparser import MetadataParser, DESCRIPTOR_TYPES_DISPLAY
47
from met.metadataparser.templatetags import attributemap
48
49
50
TOP_LENGTH = getattr(settings, "TOP_LENGTH", 5)
51
stats = getattr(settings, "STATS")
52
53
FEDERATION_TYPES = (
54
    (None, ''),
55
    ('hub-and-spoke', 'Hub and Spoke'),
56
    ('mesh', 'Full Mesh'),
57
)
58
59
60
def update_obj(mobj, obj, attrs=None):
61
    for_attrs = attrs or getattr(mobj, 'all_attrs', [])
62
    for attrb in attrs or for_attrs:
63
        if (getattr(mobj, attrb, None) and
64
            getattr(obj, attrb, None) and
65
            getattr(mobj, attrb) != getattr(obj, attrb)):
66
            setattr(obj, attrb,  getattr(mobj, attrb))
67
68
class JSONField(models.CharField):
69
    """JSONField is a generic textfield that neatly serializes/unserializes
70
    JSON objects seamlessly
71
72
    The json spec claims you must use a collection type at the top level of
73
    the data structure.  However the simplesjon decoder and Firefox both encode
74
    and decode non collection types that do not exist inside a collection.
75
    The to_python method relies on the value being an instance of basestring
76
    to ensure that it is encoded.  If a string is the sole value at the
77
    point the field is instanced, to_python attempts to decode the sting because
78
    it is derived from basestring but cannot be encodeded and throws the
79
    exception ValueError: No JSON object could be decoded.
80
    """
81
82
    # Used so to_python() is called
83
    __metaclass__ = models.SubfieldBase
84
    description = _("JSON object")
85
86
    def __init__(self, *args, **kwargs):
87
        super(JSONField, self).__init__(*args, **kwargs)
88
        self.validators.append(validators.MaxLengthValidator(self.max_length))
89
90
    @classmethod
91
    def get_internal_type(cls):
92
        return "TextField"
93
94
    @classmethod
95
    def to_python(cls, value):
96
        """Convert our string value to JSON after we load it from the DB"""
97
        if value == "":
98
            return None
99
100
        try:
101
            if isinstance(value, basestring):
102
                return json.loads(value)
103
        except ValueError:
104
            return value
105
106
        return value
107
108
    def get_prep_value(self, value):
109
        """Convert our JSON object to a string before we save"""
110
111
        if not value or value == "":
112
            return None
113
114
        db_value = json.dumps(value)
115
        return super(JSONField, self).get_prep_value(db_value)
116
117
    def get_db_prep_value(self, value, connection, prepared=False):
118
        """Convert our JSON object to a string before we save"""
119
120
        if not value or value == "":
121
            return None
122
123
        db_value = json.dumps(value)
124
        return super(JSONField, self).get_db_prep_value(db_value, connection, prepared)
125
126
127
class Base(models.Model):
128
    file_url = models.CharField(verbose_name='Metadata url',
129
                                max_length=1000,
130
                                blank=True, null=True,
131
                                help_text=_(u'Url to fetch metadata file'))
132
    file = models.FileField(upload_to='metadata', blank=True, null=True,
133
                            verbose_name=_(u'metadata xml file'),
134
                            help_text=_("if url is set, metadata url will be "
135
                                        "fetched and replace file value"))
136
    file_id = models.CharField(blank=True, null=True, max_length=500,
137
                               verbose_name=_(u'File ID'))
138
139
    registration_authority = models.CharField(verbose_name=_('Registration Authority'),
140
                                              max_length=200, blank=True, null=True)
141
142
    editor_users = models.ManyToManyField(User, null=True, blank=True,
143
                                          verbose_name=_('editor users'))
144
145
    class Meta:
146
        abstract = True
147
148
    class XmlError(Exception):
149
        pass
150
151
    def __unicode__(self):
152
        return self.url or u"Metadata %s" % self.id
153
154
    def load_file(self):
155
        if not hasattr(self, '_loaded_file'):
156
            """Only load file and parse it, don't create/update any objects"""
157
            if not self.file:
158
                return None
159
            self._loaded_file = MetadataParser(filename=self.file.path)
160
        return self._loaded_file
161
162
    def _get_metadata_stream(self, load_streams):
163
        try:
164
            load = []
165
            select = []
166
167
            count = 1
168
            for stream in load_streams:
169
                curid = "%s%d" % (self.slug, count)
170
                load.append("%s as %s" % (stream[0], curid))
171
                if stream[1] == 'SP' or stream[1] == 'IDP':
172
                    select.append("%s!//md:EntityDescriptor[md:%sSSODescriptor]" % (curid, stream[1]))
173
                else:
174
                    select.append("%s" % curid)
175
                count = count + 1
176
177
            if len(select) > 0:
178
                pipeline = [{'load': load}, {'select': select}]
179
            else:
180
                pipeline = [{'load': load}, 'select']
181
182
            md = MDRepository()
183
            entities = Plumbing(pipeline=pipeline, id=self.slug).process(md, state={'batch': True, 'stats': {}})
184
            return etree.tostring(entities)
185
        except Exception, e:
186
            raise Exception('Getting metadata from %s failed.\nError: %s' % (load_streams, e))
187
188
    def fetch_metadata_file(self, file_name):
189
        file_url = self.file_url
190
        if not file_url or file_url == '':
191
            return
192
193
        metadata_files = []
194
        files = file_url.split("|")
195
        for curfile in files:
196
            cursource = curfile.split(";")
197
            if len(cursource) == 1:
198
                cursource.append("All")
199
            metadata_files.append(cursource)
200
201
        req = self._get_metadata_stream(metadata_files)
202
203
        try:
204
            self.file.seek(0)
205
            original_file_content = self.file.read()
206
            if compare_filecontents(original_file_content, req):
207
                return False
208
        except:
209
            pass
210
211
        filename = path.basename("%s-metadata.xml" % file_name)
212
        self.file.delete(save=False)
213
        self.file.save(filename, ContentFile(req), save=False)
214
        return True
215
216
    @classmethod
217
    def process_metadata(cls):
218
        raise NotImplementedError()
219
220
221
class XmlDescriptionError(Exception):
222
    pass
223
224
225
class Federation(Base):
226
227
    name = models.CharField(blank=False, null=False, max_length=200,
228
                            unique=True, verbose_name=_(u'Name'))
229
230
    type = models.CharField(blank=True, null=True, max_length=100,
231
                            unique=False, verbose_name=_(u'Type'), choices=FEDERATION_TYPES)
232
233
    url = models.URLField(verbose_name='Federation url',
234
                          blank=True, null=True)
235
    
236
    fee_schedule_url = models.URLField(verbose_name='Fee schedule url',
237
                                       max_length=150, blank=True, null=True)
238
239
    logo = models.ImageField(upload_to='federation_logo', blank=True,
240
                             null=True, verbose_name=_(u'Federation logo'))
241
    is_interfederation = models.BooleanField(default=False, db_index=True,
242
                                         verbose_name=_(u'Is interfederation'))
243
    slug = models.SlugField(max_length=200, unique=True)
244
245
    country = models.CharField(blank=True, null=True, max_length=100,
246
                               unique=False, verbose_name=_(u'Country'))
247
248
    metadata_update = models.DateField(blank=True, null=True,
249
                                       unique=False, verbose_name=_(u'Metadata update date'))
250
251
    @property
252
    def _metadata(self):
253
        if not hasattr(self, '_metadata_cache'):
254
            self._metadata_cache = self.load_file()
255
        return self._metadata_cache
256
257
    def __unicode__(self):
258
        return self.name
259
260
    def get_entity_metadata(self, entityid):
261
        return self._metadata.get_entity(entityid)
262
263
    def get_entity(self, entityid):
264
        return self.entity_set.get(entityid=entityid)
265
266
    def process_metadata(self):
267
        metadata = self.load_file()
268
269
        if self.file_id and metadata.file_id and metadata.file_id == self.file_id:
270
            return
271
        else:
272
            self.file_id = metadata.file_id
273
274
        if not metadata:
275
            return
276
        if not metadata.is_federation:
277
            raise XmlDescriptionError("XML Haven't federation form")
278
279
        update_obj(metadata.get_federation(), self)
280
281
    def _remove_deleted_entities(self, entities_from_xml, request):
282
        entities_to_remove = []
283
        for entity in self.entity_set.all():
284
            """Remove entity relation if does not exist in metadata"""
285
            if not entity.entityid in entities_from_xml:
286
                entities_to_remove.append(entity)
287
288
        if len(entities_to_remove) > 0:
289
            self.entity_set.remove(*entities_to_remove)
290
291
            if request and not entity.federations.exists():
292
                for entity in entities_to_remove:
293
                    messages.warning(request,
294
                                     mark_safe(_("Orphan entity: <a href='%s'>%s</a>" %
295
                                     (entity.get_absolute_url(), entity.entityid))))
296
297
        return len(entities_to_remove)
298
299
    def _update_entities(self, entities_to_update, entities_to_add):
300
        for e in entities_to_update:
301
            e.save()
302
303
        self.entity_set.add(*entities_to_add)
304
305
    @staticmethod
306
    def _entity_has_changed(entity, entityid, name, registration_authority):
307
        if entity.entityid != entityid:
308
            return True
309
        if entity.name != name:
310
            return True
311
        if entity.registration_authority != registration_authority:
312
            return True
313
314
        return False
315
316
    def _add_new_entities(self, entities, entities_from_xml, request, federation_slug):
317
        db_entity_types = EntityType.objects.all()
318
        cached_entity_types = { entity_type.xmlname: entity_type for entity_type in db_entity_types }
319
320
        entities_to_add = []
321
        entities_to_update = []
322
323
        for m_id in entities_from_xml:
324
            if request and federation_slug:
325
                request.session['%s_cur_entities' % federation_slug] += 1
326
                request.session.save()
327
328
            created = False
329
            if m_id in entities:
330
                entity = entities[m_id]
331
            else:
332
                entity, created = Entity.objects.get_or_create(entityid=m_id)
333
334
            entityid = entity.entityid
335
            name = entity.name
336
            registration_authority = entity.registration_authority
337
 
338
            entity_from_xml = self._metadata.get_entity(m_id, True)
339
            entity.process_metadata(False, entity_from_xml, cached_entity_types)
340
341
            if created or self._entity_has_changed(entity, entityid, name, registration_authority):
342
                entities_to_update.append(entity)
343
344
            entities_to_add.append(entity)
345
346
        self._update_entities(entities_to_update, entities_to_add)
347
        return len(entities_to_update) 
348
349
    @staticmethod
350
    def _daterange(start_date, end_date):
351
        for n in range(int ((end_date - start_date).days)):
352
            yield start_date + timedelta(n)
353
354
    def compute_new_stats(self, timestamp=timezone.now()):
355
        entities_from_xml = self._metadata.get_entities()
356
357
        entities = Entity.objects.filter(entityid__in=entities_from_xml)
358
        entities = entities.prefetch_related('types')
359
360
        try:
361
            first_date = EntityStat.objects.all().aggregate(Max('time'))['time__max']
362
            if not first_date:
363
                raise Exception('Not able to find statistical data in the DB.')
364
        except:
365
            first_date = datetime(2010, 1, 1)
366
            first_date = pytz.utc.localize(first_date)
367
      
368
        for curtimestamp in self._daterange(first_date, timestamp):
369
            print "computing stas for day %s" % curtimestamp
370
            computed = {}
371
            not_computed = []
372
            entity_stats = []
373
            for feature in stats['features'].keys():
374
                fun = getattr(self, 'get_%s' % feature, None)
375
    
376
                if callable(fun):
377
                    stat = EntityStat()
378
                    stat.feature = feature
379
                    stat.time = curtimestamp
380
                    stat.federation = self
381
                    stat.value = fun(entities, stats['features'][feature], curtimestamp)
382
                    entity_stats.append(stat)
383
                    computed[feature] = stat.value
384
                else:
385
                    not_computed.append(feature)
386
387
            from_time = datetime.combine(curtimestamp, time.min) 
388
            if timezone.is_naive(from_time):
389
                from_time = pytz.utc.localize(from_time)
390
            to_time = datetime.combine(curtimestamp, time.max)
391
            if timezone.is_naive(to_time):
392
                to_time = pytz.utc.localize(to_time)
393
394
            EntityStat.objects.filter(federation=self, time__gte = from_time, time__lte = to_time).delete()
395
            EntityStat.objects.bulk_create(entity_stats)
396
397
        return (computed, not_computed)
398
399
    def process_metadata_entities(self, request=None, federation_slug=None, timestamp=timezone.now()):
400
        entities_from_xml = self._metadata.get_entities()
401
        removed = self._remove_deleted_entities(entities_from_xml, request)
402
403
        entities = {}
404
        db_entities = Entity.objects.filter(entityid__in=entities_from_xml)
405
        db_entities = db_entities.prefetch_related('types')
406
407
        for entity in db_entities.all():
408
            entities[entity.entityid] = entity
409
410
        if request and federation_slug:
411
            request.session['%s_num_entities' % federation_slug] = len(entities_from_xml)
412
            request.session['%s_cur_entities' % federation_slug] = 0
413
            request.session['%s_process_done' % federation_slug] = False
414
            request.session.save()
415
416
        updated = self._add_new_entities(entities, entities_from_xml, request, federation_slug)
417
418
        if request and federation_slug:
419
            request.session['%s_process_done' % federation_slug] = True
420
            request.session.save()
421
422
        return removed, updated
423
424
    def get_absolute_url(self):
425
        return reverse('federation_view', args=[self.slug])
426
427
    @classmethod
428
    def get_sp(cls, entities, xml_name, ref_date = None):
429
        count = 0
430
        for entity in entities:
431
            reginst = pytz.utc.localize(entity.registration_instant)
432
            if not ref_date or (reginst and reginst > ref_date):
433
                continue
434
            cur_cached_types = [t.xmlname for t in entity.types.all()]
435
            if xml_name in cur_cached_types:
436
                count += 1
437
        return count
438
439
    @classmethod
440
    def get_idp(cls, entities, xml_name, ref_date = None):
441
        count = 0
442
        for entity in entities:
443
            reginst = pytz.utc.localize(entity.registration_instant)
444
            if not ref_date or (reginst and reginst > ref_date):
445
                continue
446
            cur_cached_types = [t.xmlname for t in entity.types.all()]
447
            if xml_name in cur_cached_types:
448
                count += 1
449
        return count
450
451
    def get_sp_saml1(self, entities, xml_name, ref_date = None):
452
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
453
454
    def get_sp_saml2(self, entities, xml_name, ref_date = None):
455
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
456
457
    def get_sp_shib1(self, entities, xml_name, ref_date = None):
458
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
459
460
    def get_idp_saml1(self, entities, xml_name, ref_date = None):
461
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
462
463
    def get_idp_saml2(self, entities, xml_name, ref_date = None):
464
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
465
466
    def get_idp_shib1(self, entities, xml_name, ref_date = None):
467
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
468
469
    def get_stat_protocol(self, entities, xml_name, service_type, ref_date):
470
        count = 0
471
        for entity in entities:
472
            reginst = pytz.utc.localize(entity.registration_instant)
473
            if not ref_date or (reginst and reginst > ref_date):
474
                continue
475
476
            try:
477
                cur_cached_types = [t.xmlname for t in entity.types.all()]
478
                if service_type in cur_cached_types and Entity.READABLE_PROTOCOLS[xml_name] in entity.display_protocols:
479
                    count += 1
480
            except Exception, e:
481
                pass
482
        return count
483
484
    def can_edit(self, user, delete):
485
        if user.is_superuser:
486
            return True
487
488
        permission = 'delete_federation' if delete else 'change_federation'
489
        if user.has_perm('metadataparser.%s' % permission) and user in self.editor_users.all():
490
            return True
491
        return False
492
493
494
class EntityQuerySet(QuerySet):
495
    def iterator(self):
496
        cached_federations = {}
497
        for entity in super(EntityQuerySet, self).iterator():
498
            if entity.file:
499
                continue
500
501
            federations = entity.federations.all()
502
            if federations:
503
                federation = federations[0]
504
            else:
505
                raise ValueError("Can't find entity metadata")
506
507
            for federation in federations:
508
                if not federation.id in cached_federations:
509
                    cached_federations[federation.id] = federation
510
511
                cached_federation = cached_federations[federation.id]
512
                try:
513
                    entity.load_metadata(federation=cached_federation)
514
                except ValueError:
515
                    # Allow entity in federation but not in federation file
516
                    continue
517
                else:
518
                    break
519
520
            yield entity
521
522
523
class EntityManager(models.Manager):
524
    def get_queryset(self):
525
        return EntityQuerySet(self.model, using=self._db)
526
527
528
class EntityType(models.Model):
529
    name = models.CharField(blank=False, max_length=20, unique=True,
530
                            verbose_name=_(u'Name'), db_index=True)
531
    xmlname = models.CharField(blank=False, max_length=20, unique=True,
532
                            verbose_name=_(u'Name in XML'), db_index=True)
533
534
    def __unicode__(self):
535
        return self.name
536
537
538
class Entity(Base):
539
    READABLE_PROTOCOLS = {
540
        'urn:oasis:names:tc:SAML:1.1:protocol': 'SAML 1.1',
541
        'urn:oasis:names:tc:SAML:2.0:protocol': 'SAML 2.0',
542
        'urn:mace:shibboleth:1.0': 'Shiboleth 1.0',
543
    }
544
545
    entityid = models.CharField(blank=False, max_length=200, unique=True,
546
                                verbose_name=_(u'EntityID'), db_index=True)
547
    federations = models.ManyToManyField(Federation,
548
                                         verbose_name=_(u'Federations'))
549
550
    types = models.ManyToManyField(EntityType, verbose_name=_(u'Type'))
551
552
    name = JSONField(blank=True, null=True, max_length=2000,
553
                     verbose_name=_(u'Display Name'))
554
555
    objects = models.Manager()
556
    longlist = EntityManager()
557
558
    curfed = None
559
560
    @property
561
    def registration_authority_xml(self):
562
        return self._get_property('registration_authority')
563
564
    @property
565
    def registration_policy(self):
566
        return self._get_property('registration_policy')
567
568
    @property
569
    def registration_instant(self):
570
        reginstant = self._get_property('registration_instant')
571
        if reginstant is None:
572
            return None
573
        reginstant = "%sZ" % reginstant[0:19]
574
        return datetime.strptime(reginstant, '%Y-%m-%dT%H:%M:%SZ')
575
576
    @property
577
    def protocols(self):
578
        return ' '.join(self._get_property('protocols'))
579
580
    @property
581
    def languages(self):
582
        return ' '.join(self._get_property('languages'))
583
584
    @property
585
    def scopes(self):
586
        return ' '.join(self._get_property('scopes'))
587
588
    @property
589
    def attributes(self):
590
        attributes = self._get_property('attr_requested')
591
        if not attributes:
592
            return []
593
        return attributes['required']
594
595
    @property
596
    def attributes_optional(self):
597
        attributes = self._get_property('attr_requested')
598
        if not attributes:
599
            return []
600
        return attributes['optional']
601
602
    @property
603
    def organization(self):
604
        organization = self._get_property('organization')
605
        if not organization:
606
            return []
607
608
        vals = []
609
        for lang, data in organization.items():
610
            data['lang'] = lang
611
            vals.append(data)
612
613
        return vals
614
615
    @property
616
    def display_name(self):
617
        return self._get_property('displayName')
618
619
    @property
620
    def federations_count(self):
621
        return str(self.federations.all().count())
622
        
623
    @property
624
    def description(self):
625
        return self._get_property('description')
626
627
    @property
628
    def info_url(self):
629
        return self._get_property('infoUrl')
630
631
    @property
632
    def privacy_url(self):
633
        return self._get_property('privacyUrl')
634
635
    @property
636
    def xml(self):
637
         return self._get_property('xml')
638
639
    @property
640
    def xml_types(self):
641
         return self._get_property('entity_types')
642
643
    @property
644
    def display_protocols(self):
645
        protocols = []
646
647
        xml_protocols = self._get_property('protocols')
648
        if xml_protocols:
649
            for proto in xml_protocols:
650
                protocols.append(self.READABLE_PROTOCOLS.get(proto, proto))
651
652
        return protocols
653
654
    def display_attributes(self):
655
        attributes = {}
656
        for [attr, friendly] in self.attributes:
657
            if friendly:
658
                attributes[attr] = friendly
659
            elif attr in attributemap.MAP['fro']:
660
                attributes[attr] = attributemap.MAP['fro'][attr]
661
            else:
662
                attributes[attr] = '?'
663
        return attributes
664
665
    def display_attributes_optional(self):
666
        attributes = {}
667
        for [attr, friendly] in self.attributes_optional:
668
            if friendly:
669
                attributes[attr] = friendly
670
            elif attr in attributemap.MAP['fro']:
671
                attributes[attr] = attributemap.MAP['fro'][attr]
672
            else:
673
                attributes[attr] = '?'
674
        return attributes
675
676
    @property
677
    def contacts(self):
678
        contacts = []
679
        for cur_contact in self._get_property('contacts'):
680
            if cur_contact['name'] and cur_contact['surname']:
681
                contact_name = '%s %s' % (cur_contact['name'], cur_contact['surname'])
682
            elif cur_contact['name']:
683
                contact_name = cur_contact['name']
684
            elif cur_contact['surname']:
685
                contact_name = cur_contact['surname']
686
            else:
687
                contact_name = urlparse(cur_contact['email']).path.partition('?')[0]
688
            c_type = 'undefined'
689
            if cur_contact['type']:
690
                c_type = cur_contact['type']
691
            contacts.append({ 'name': contact_name, 'email': cur_contact['email'], 'type': c_type })
692
        return contacts
693
694
    @property
695
    def logos(self):
696
        logos = []
697
        for cur_logo in self._get_property('logos'):
698
            cur_logo['external'] = True
699
            logos.append(cur_logo)
700
701
        return logos
702
703
    class Meta:
704
        verbose_name = _(u'Entity')
705
        verbose_name_plural = _(u'Entities')
706
707
    def __unicode__(self):
708
        return self.entityid
709
710
    def load_metadata(self, federation=None, entity_data=None):
711
        if hasattr(self, '_entity_cached'):
712
            return
713
714
        if self.file:
715
            self._entity_cached = self.load_file().get_entity(self.entityid)
716
        elif federation:
717
            self._entity_cached = federation.get_entity_metadata(self.entityid)
718
        elif entity_data:
719
            self._entity_cached = entity_data
720
        else:
721
            right_fed = None
722
            first_fed = None
723
            for fed in self.federations.all():
724
                if fed.registration_authority == self.registration_authority:
725
                    right_fed = fed
726
                if first_fed is None:
727
                    first_fed = fed
728
729
            if right_fed is not None:
730
                entity_cached = right_fed.get_entity_metadata(self.entityid)
731
                self._entity_cached = entity_cached
732
            else:
733
                entity_cached = first_fed.get_entity_metadata(self.entityid)
734
                self._entity_cached = entity_cached
735
736
        if not hasattr(self, '_entity_cached'):
737
            raise ValueError("Can't find entity metadata")
738
739
    def _get_property(self, prop, federation=None):
740
        try:
741
            self.load_metadata(federation or self.curfed)
742
        except ValueError:
743
            return None
744
745
        if hasattr(self, '_entity_cached'):
746
            return self._entity_cached.get(prop, None)
747
        else:
748
            raise ValueError("Not metadata loaded")
749
750
    def _get_or_create_etypes(self, cached_entity_types):
751
        entity_types = []
752
        cur_cached_types = [t.xmlname for t in self.types.all()]
753
        for etype in self.xml_types:
754
            if etype in cur_cached_types:
755
               break
756
757
            if cached_entity_types is None:
758
                entity_type, created = EntityType.objects.get_or_create(xmlname=etype,
759
                                                                        name=DESCRIPTOR_TYPES_DISPLAY[etype])
760
            else:
761
                if etype in cached_entity_types:
762
                    entity_type = cached_entity_types[etype]
763
                else:
764
                    entity_type = EntityType.objects.create(xmlname=etype,
765
                                                                name=DESCRIPTOR_TYPES_DISPLAY[etype])
766
            entity_types.append(entity_type)
767
        return entity_types
768
769
    def process_metadata(self, auto_save=True, entity_data=None, cached_entity_types=None):
770
        if not entity_data:
771
            self.load_metadata()
772
773
        if self.entityid.lower() != entity_data.get('entityid').lower():
774
            raise ValueError("EntityID is not the same: %s != %s" % (self.entityid.lower(), entity_data.get('entityid').lower()))
775
776
        self._entity_cached = entity_data
777
        if self.xml_types:
778
            entity_types = self._get_or_create_etypes(cached_entity_types)
779
780
            if len(entity_types) > 0:
781
                self.types.add(*entity_types)
782
783
            newname = self._get_property('displayName')
784
            if newname and newname != '':
785
                self.name = newname
786
787
            if str(self._get_property('registration_authority')) != '':
788
                self.registration_authority = self._get_property('registration_authority')
789
790
            if auto_save:
791
                self.save()
792
793
    def to_dict(self):
794
        self.load_metadata()
795
796
        entity = self._entity_cached.copy()
797
        entity["types"] = [unicode(f) for f in self.types.all()]
798
        entity["federations"] = [{u"name": unicode(f), u"url": f.get_absolute_url()}
799
                                  for f in self.federations.all()]
800
801
        if self.registration_authority:
802
            entity["registration_authority"] = self.registration_authority
803
        if self.registration_instant:
804
            entity["registration_instant"] = '%s' % self.registration_instant
805
806
        if "file_id" in entity.keys():
807
            del entity["file_id"]
808
        if "entity_types" in entity.keys():
809
            del entity["entity_types"]
810
811
        return entity
812
813
    def display_etype(value, separator=', '):
814
            return separator.join([unicode(item) for item in value.all()])
815
816
    @classmethod
817
    def get_most_federated_entities(self, maxlength=TOP_LENGTH, cache_expire=None):
818
        entities = None
819
        if cache_expire:
820
            entities = cache.get("most_federated_entities")
821
822
        if not entities or len(entities) < maxlength:
823
            # Entities with count how many federations belongs to, and sorted by most first
824
            ob_entities = Entity.objects.all().annotate(federationslength=Count("federations")).order_by("-federationslength")
825
            ob_entities = ob_entities.prefetch_related('types', 'federations')
826
            ob_entities = ob_entities[:maxlength]
827
828
            entities = []
829
            for entity in ob_entities:
830
                entities.append({
831
                    'entityid': entity.entityid,
832
                    'name': entity.name,
833
                    'absolute_url': entity.get_absolute_url(),
834
                    'types': [unicode(item) for item in entity.types.all()],
835
                    'federations': [(unicode(item.name), item.get_absolute_url()) for item in entity.federations.all()],
836
                })
837
838
        if cache_expire:
839
            cache.set("most_federated_entities", entities, cache_expire)
840
841
        return entities[:maxlength]
842
843
    def get_absolute_url(self):
844
        return reverse('entity_view', args=[quote_plus(self.entityid)])
845
846
    def can_edit(self, user, delete):
847
        permission = 'delete_entity' if delete else 'change_entity'
848
        if user.is_superuser or (user.has_perm('metadataparser.%s' % permission) and user in self.editor_users.all()):
849
            return True
850
851
        for federation in self.federations.all():
852
            if federation.can_edit(user, False):
853
                return True
854
855
        return False
856
857
class EntityStat(models.Model):
858
    time = models.DateTimeField(blank=False, null=False, 
859
                           verbose_name=_(u'Metadata time stamp'))
860
    feature = models.CharField(max_length=100, blank=False, null=False, db_index=True,
861
                           verbose_name=_(u'Feature name'))
862
863
    value = models.PositiveIntegerField(max_length=100, blank=False, null=False,
864
                           verbose_name=_(u'Feature value'))
865
866
    federation = models.ForeignKey(Federation, blank = False,
867
                                         verbose_name=_(u'Federations'))
868
869
    def __unicode__(self):
870
        return self.feature
871
872
873
class Dummy(models.Model):
874
    pass
875
876
877
@receiver(pre_save, sender=Federation, dispatch_uid='federation_pre_save')
878
def federation_pre_save(sender, instance, **kwargs):
879
    # Skip pre_save if only file name is saved 
880
    if kwargs.has_key('update_fields') and kwargs['update_fields'] == set(['file']):
881
        return
882
883
    slug = slugify(unicode(instance.name))[:200]
884
    if instance.file_url and instance.file_url != '':
885
        instance.fetch_metadata_file(slug)
886
    if instance.name:
887
        instance.slug = slugify(unicode(instance))[:200]
888
889
890
@receiver(pre_save, sender=Entity, dispatch_uid='entity_pre_save')
891
def entity_pre_save(sender, instance, **kwargs):
892
    if instance.file_url:
893
        slug = slugify(unicode(instance.name))[:200]
894
        instance.fetch_metadata_file(slug)
895
        instance.process_metadata()
896