Completed
Push — master ( 28b0ba...17cdbf )
by Andrea
01:44
created

Entity.get_most_federated_entities()   D

Complexity

Conditions 8

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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