XmlDescriptionError   A
last analyzed

Complexity

Total Complexity 0

Size/Duplication

Total Lines 5
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 0
c 1
b 0
f 0
dl 0
loc 5
rs 10
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
from os import path
14
from lxml import etree
15
import simplejson as json
16
17
from django.db import models
18
from django.contrib.auth.models import User
19
from django.core import validators
20
from django.core.files.base import ContentFile
21
from django.utils.translation import ugettext_lazy as _
22
23
from pyff.mdrepo import MDRepository
24
from pyff.pipes import Plumbing
25
26
from met.metadataparser.xmlparser import MetadataParser
27
from met.metadataparser.utils import compare_filecontents
28
29
30
class JSONField(models.CharField):
31
    """
32
    JSONField is a generic textfield that neatly serializes/unserializes
33
    JSON objects seamlessly
34
35
    The json spec claims you must use a collection type at the top level of
36
    the data structure.  However the simplesjon decoder and Firefox both encode
37
    and decode non collection types that do not exist inside a collection.
38
    The to_python method relies on the value being an instance of basestring
39
    to ensure that it is encoded.  If a string is the sole value at the
40
    point the field is instanced, to_python attempts to decode the sting because
41
    it is derived from basestring but cannot be encodeded and throws the
42
    exception ValueError: No JSON object could be decoded.
43
    """
44
45
    # Used so to_python() is called
46
    __metaclass__ = models.SubfieldBase
47
    description = _("JSON object")
48
49
    def __init__(self, *args, **kwargs):
50
        super(JSONField, self).__init__(*args, **kwargs)
51
        self.validators.append(validators.MaxLengthValidator(self.max_length))
52
53
    @classmethod
54
    def get_internal_type(cls):
55
        return "TextField"
56
57
    @classmethod
58
    def to_python(cls, value):
59
        """Convert our string value to JSON after we load it from the DB"""
60
        if value == "":
61
            return None
62
63
        try:
64
            if isinstance(value, basestring):
65
                return json.loads(value)
66
        except ValueError:
67
            return value
68
69
        return value
70
71
    def get_prep_value(self, value):
72
        """Convert our JSON object to a string before we save"""
73
74
        if not value or value == "":
75
            return None
76
77
        db_value = json.dumps(value)
78
        return super(JSONField, self).get_prep_value(db_value)
79
80
    def get_db_prep_value(self, value, connection, prepared=False):
81
        """Convert our JSON object to a string before we save"""
82
83
        if not value or value == "":
84
            return None
85
86
        db_value = json.dumps(value)
87
        return super(JSONField, self).get_db_prep_value(db_value, connection, prepared)
88
89
90
class Base(models.Model):
91
    """
92
    Class describing an entity that can be updated from metadata file.
93
    Each object parsed from the XML extends this base class that contains shared methods.
94
    """
95
96
    file_url = models.CharField(verbose_name='Metadata url',
97
                                max_length=1000,
98
                                blank=True, null=True,
99
                                help_text=_(u'Url to fetch metadata file'))
100
    file = models.FileField(upload_to='metadata', blank=True, null=True,
101
                            verbose_name=_(u'metadata xml file'),
102
                            help_text=_("if url is set, metadata url will be "
103
                                        "fetched and replace file value"))
104
    file_id = models.CharField(blank=True, null=True, max_length=500,
105
                               verbose_name=_(u'File ID'))
106
107
    registration_authority = models.CharField(verbose_name=_('Registration Authority'),
108
                                              max_length=200, blank=True, null=True)
109
110
    editor_users = models.ManyToManyField(User, null=True, blank=True,
111
                                          verbose_name=_('editor users'))
112
113
    class Meta(object):
114
        abstract = True
115
116
    class XmlError(Exception):
117
        pass
118
119
    def __unicode__(self):
120
        return self.url or u"Metadata %s" % self.id
121
122
    def load_file(self):
123
        if not hasattr(self, '_loaded_file'):
124
            # Only load file and parse it, don't create/update any objects
125
            if not self.file:
126
                return None
127
            self._loaded_file = MetadataParser(filename=self.file.path)
128
        return self._loaded_file
129
130
    def _get_metadata_stream(self, load_streams):
131
        try:
132
            load = []
133
            select = []
134
135
            count = 1
136
            for stream in load_streams:
137
                curid = "%s%d" % (self.slug, count)
138
                load.append("%s as %s" % (stream[0], curid))
139
                if stream[1] == 'SP' or stream[1] == 'IDP':
140
                    select.append(
141
                        "%s!//md:EntityDescriptor[md:%sSSODescriptor]" % (curid, stream[1]))
142
                else:
143
                    select.append("%s" % curid)
144
                count = count + 1
145
146
            if len(select) > 0:
147
                pipeline = [{'load': load}, {'select': select}]
148
            else:
149
                pipeline = [{'load': load}, 'select']
150
151
            md = MDRepository()
152
            entities = Plumbing(pipeline=pipeline, id=self.slug).process(
153
                md, state={'batch': True, 'stats': {}})
154
            return etree.tostring(entities)
155
        except Exception, e:
156
            raise Exception(
157
                'Getting metadata from %s failed.\nError: %s' % (load_streams, e))
158
159
    def fetch_metadata_file(self, file_name):
160
        file_url = self.file_url
161
        if not file_url or file_url == '':
162
            return
163
164
        metadata_files = []
165
        files = file_url.split("|")
166
        for curfile in files:
167
            cursource = curfile.split(";")
168
            if len(cursource) == 1:
169
                cursource.append("All")
170
            metadata_files.append(cursource)
171
172
        req = self._get_metadata_stream(metadata_files)
173
174
        try:
175
            self.file.seek(0)
176
            original_file_content = self.file.read()
177
            if compare_filecontents(original_file_content, req):
178
                return False
179
        except Exception:
180
            pass
181
182
        filename = path.basename("%s-metadata.xml" % file_name)
183
        self.file.delete(save=False)
184
        self.file.save(filename, ContentFile(req), save=False)
185
        return True
186
187
    @classmethod
188
    def process_metadata(cls):
189
        """
190
        Method that process the metadata file and updates attributes accordingly.
191
        """
192
        raise NotImplementedError()
193
194
195
class XmlDescriptionError(Exception):
196
    """
197
    Class representing an error in the XML file.
198
    """
199
    pass
200
201
202
class Dummy(models.Model):
203
    """
204
    Dummy object necessary to thest Django funcionalities.
205
    """
206
    pass
207